@riverbankcms/sdk 0.151.0 → 0.152.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 (226) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/{EventEmbeddedCheckoutStep-BBHbM22D.mjs → EventEmbeddedCheckoutStep-DVQXWFm6.mjs} +10 -9
  3. package/dist/{PreviewEditorSidebar-BHplI9HE.mjs → PreviewEditorSidebar-Dr4NJi3Q.mjs} +4 -3
  4. package/dist/{PreviewEditorUI-DuyN8nsD.mjs → PreviewEditorUI-DSZXQbyh.mjs} +5 -4
  5. package/dist/{SdkPreviewModeRuntime-CR5ypwxJ.mjs → SdkPreviewModeRuntime-Dl4GyMc3.mjs} +124 -53
  6. package/dist/{SiteChromeCustomizeContext-D5t6joF_.mjs → SiteChromeCustomizeContext-DgBe-FlM.mjs} +20418 -32298
  7. package/dist/ThemedButton-B8ViBnLK.mjs +16350 -0
  8. package/dist/_dts/api/src/bookingOperations.d.ts +5 -3
  9. package/dist/_dts/api/src/commercePortalEndpoints.d.ts +4 -0
  10. package/dist/_dts/api/src/endpoints.d.ts +7 -0
  11. package/dist/_dts/api/src/index.d.ts +1 -1
  12. package/dist/_dts/api/src/navigation/types.d.ts +7 -0
  13. package/dist/_dts/api/src/navigation.d.ts +1 -1
  14. package/dist/_dts/api/src/publicAppointmentBookingContracts.d.ts +10 -1
  15. package/dist/_dts/api/src/siteAssets.d.ts +1 -0
  16. package/dist/_dts/blocks/src/definitions.d.ts +1 -1
  17. package/dist/_dts/blocks/src/index.d.ts +1 -0
  18. package/dist/_dts/blocks/src/pageRenderPreparation.d.ts +1 -0
  19. package/dist/_dts/blocks/src/pageRendererContracts.d.ts +1 -1
  20. package/dist/_dts/blocks/src/primitives/content/utils/sanitizeHref.d.ts +1 -1
  21. package/dist/_dts/blocks/src/system/blocks/event-registration.d.ts +4 -4
  22. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +16 -0
  23. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +5 -0
  24. package/dist/_dts/blocks/src/system/blocks/offerings-preview/model.d.ts +2 -0
  25. package/dist/_dts/blocks/src/system/helpers/normalizeMailingListBlockContent.d.ts +2 -0
  26. package/dist/_dts/blocks/src/system/registry.d.ts +14 -2
  27. package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +0 -5
  28. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +2 -0
  29. package/dist/_dts/blocks/src/system/runtime/api/schemas/events.d.ts +971 -0
  30. package/dist/_dts/blocks/src/system/runtime/api/schemas/waitlist-claims.d.ts +42 -0
  31. package/dist/_dts/blocks/src/system/runtime/api/types/appointments.d.ts +10 -2
  32. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +10 -136
  33. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +48 -6
  34. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +24 -3
  35. package/dist/_dts/blocks/src/system/runtime/hooks/useBookingSubmission.state.d.ts +2 -0
  36. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +3 -1
  37. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.d.ts +1 -1
  38. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +23 -0
  39. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +6 -2
  40. package/dist/_dts/blocks/src/system/runtime/nodes/carousel.client.hooks.d.ts +6 -0
  41. package/dist/_dts/blocks/src/system/runtime/nodes/carousel.client.state.d.ts +7 -0
  42. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/BookingEntryViews.d.ts +42 -0
  43. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventEmbeddedCheckoutStep.d.ts +4 -1
  44. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationBookingLayer.d.ts +19 -9
  45. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationPayment.d.ts +4 -2
  46. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationSurface.d.ts +24 -9
  47. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationWizardContent.d.ts +4 -2
  48. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventSelectionStep.d.ts +5 -4
  49. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/GuestDetailsSection.d.ts +1 -1
  50. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentStatusStep.d.ts +2 -0
  51. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/SingleTicketEntitlementNote.d.ts +11 -0
  52. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/SuccessStep.d.ts +4 -0
  53. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +5 -0
  54. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/VerifyingTimeoutStep.d.ts +2 -0
  55. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.d.ts +31 -0
  56. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/bookingEntry.d.ts +154 -0
  57. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/bookingFocus.d.ts +12 -0
  58. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +2 -2
  59. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +2 -2
  60. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.d.ts +9 -0
  61. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/finishedBooking.d.ts +77 -0
  62. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/index.d.ts +0 -1
  63. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/offeringNoun.d.ts +12 -0
  64. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/paymentResponse.d.ts +2 -1
  65. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationCompletion.d.ts +33 -1
  66. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDerivations.d.ts +11 -0
  67. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDisplay.d.ts +31 -0
  68. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationDraft.state.d.ts +60 -0
  69. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +1 -1
  70. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.d.ts +3 -0
  71. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.d.ts +58 -10
  72. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationSurface.d.ts +17 -0
  73. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +23 -7
  74. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useBookingBarFacts.d.ts +12 -0
  75. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationRecoveryRequest.d.ts +3 -0
  76. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +4 -0
  77. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +4 -11
  78. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +1563 -31
  79. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventSourceRecovery.d.ts +2 -12
  80. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.d.ts +147 -4
  81. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/utils.d.ts +50 -2
  82. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/waitlistClaimRestoration.d.ts +6 -5
  83. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/wizardSteps.d.ts +85 -15
  84. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration.interactive.d.ts +53 -8
  85. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/MonthLookView.d.ts +10 -3
  86. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +16 -0
  87. package/dist/_dts/blocks/src/system/runtime/shared/bookingPaymentOptionCopy.d.ts +5 -1
  88. package/dist/_dts/blocks/src/system/runtime/shared/bookingPaymentOptions.d.ts +37 -1
  89. package/dist/_dts/blocks/src/system/runtime/shared/components/ModalShell.d.ts +9 -2
  90. package/dist/_dts/blocks/src/system/runtime/shared/components/PaymentChoiceRows.d.ts +28 -0
  91. package/dist/_dts/blocks/src/system/runtime/shared/components/StepIndicator.d.ts +6 -8
  92. package/dist/_dts/blocks/src/system/runtime/shared/components/index.d.ts +2 -2
  93. package/dist/_dts/blocks/src/system/runtime/shared/index.d.ts +2 -2
  94. package/dist/_dts/blocks/src/system/runtime/shared/useWaitlistClaim.d.ts +9 -12
  95. package/dist/_dts/blocks/src/system/runtime/shared/useWaitlistClaim.state.d.ts +15 -0
  96. package/dist/_dts/blocks/src/system/runtime/shared/utils/themePortalRoot.d.ts +10 -15
  97. package/dist/_dts/blocks/src/system/transforms/registry/index.d.ts +1 -0
  98. package/dist/_dts/blocks/src/system/transforms/registry/links.d.ts +3 -1
  99. package/dist/_dts/commerce/src/purchase/bookingInstalmentSchedule.d.ts +2 -0
  100. package/dist/_dts/commerce/src/purchase/bookingPaymentSelection.d.ts +6 -0
  101. package/dist/_dts/commerce/src/purchase/confirmationEmailPayment.d.ts +14 -14
  102. package/dist/_dts/commerce/src/purchase/confirmationPayment.d.ts +65 -0
  103. package/dist/_dts/commerce/src/purchase/confirmationPaymentSchema.d.ts +66 -0
  104. package/dist/_dts/commerce/src/purchase/index.d.ts +2 -0
  105. package/dist/_dts/commerce/src/purchase/orderPaymentState.d.ts +8 -0
  106. package/dist/_dts/content-model/src/templates/defaults.d.ts +2 -2
  107. package/dist/_dts/core/src/appointment-booking-terms.d.ts +20 -0
  108. package/dist/_dts/core/src/booking-payment-schemas.d.ts +70 -0
  109. package/dist/_dts/core/src/branded.d.ts +0 -2
  110. package/dist/_dts/core/src/demo-claim-access.d.ts +1 -1
  111. package/dist/_dts/core/src/demo-claim-readiness.d.ts +18 -0
  112. package/dist/_dts/core/src/demo-lifecycle-status.d.ts +1 -1
  113. package/dist/_dts/core/src/demo-warm-pool-target.d.ts +1 -1
  114. package/dist/_dts/core/src/index.d.ts +4 -0
  115. package/dist/_dts/core/src/mailing-list-targeting-availability.d.ts +68 -0
  116. package/dist/_dts/core/src/mailing-list-targeting.d.ts +2 -0
  117. package/dist/_dts/core/src/ticket-quantity.d.ts +13 -0
  118. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +51 -32
  119. package/dist/_dts/editor/src/chrome/chromeLayers.d.ts +7 -0
  120. package/dist/_dts/editor/src/index.d.ts +1 -0
  121. package/dist/_dts/editor-blocks/src/widgets/link/LinkAddressInput.d.ts +8 -0
  122. package/dist/_dts/editor-blocks/src/widgets/link/LinkContentList.d.ts +12 -0
  123. package/dist/_dts/editor-blocks/src/widgets/link/LinkPicker.d.ts +5 -1
  124. package/dist/_dts/editor-blocks/src/widgets/link/LinkPickerDialog.d.ts +3 -3
  125. package/dist/_dts/editor-blocks/src/widgets/link/LinkSummary.d.ts +8 -2
  126. package/dist/_dts/editor-blocks/src/widgets/link/LinkWidget.d.ts +4 -3
  127. package/dist/_dts/editor-blocks/src/widgets/link/LinkWidgetContext.d.ts +2 -1
  128. package/dist/_dts/editor-blocks/src/widgets/link/draft.d.ts +44 -0
  129. package/dist/_dts/editor-blocks/src/widgets/link/presentation.d.ts +13 -0
  130. package/dist/_dts/editor-blocks/src/widgets/link/types.d.ts +14 -0
  131. package/dist/_dts/editor-blocks/src/widgets/link/useRoutableContent.d.ts +2 -0
  132. package/dist/_dts/editor-blocks/src/widgets/link/utils.d.ts +5 -4
  133. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/MailingListTargetingField.d.ts +1 -1
  134. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/targetingState.d.ts +13 -8
  135. package/dist/_dts/media-core/src/fileStorage.d.ts +4 -3
  136. package/dist/_dts/media-core/src/index.d.ts +1 -0
  137. package/dist/_dts/media-core/src/storageObjectKey.d.ts +12 -0
  138. package/dist/_dts/media-react/src/hooks/useSupabaseMediaUploader.d.ts +1 -1
  139. package/dist/_dts/media-react/src/internal/supabaseResumableUpload.d.ts +13 -0
  140. package/dist/_dts/preview-next/src/client/preview/previewModeRootAttribute.d.ts +11 -0
  141. package/dist/_dts/sdk/src/rendering/blocks-page-renderer.d.ts +0 -1
  142. package/dist/_dts/sdk/src/rendering/image.d.ts +10 -0
  143. package/dist/_dts/sdk/src/rendering/index.d.ts +3 -1
  144. package/dist/_dts/sdk/src/rendering/link.d.ts +9 -0
  145. package/dist/_dts/sdk/src/rendering/overrides.d.ts +1 -0
  146. package/dist/_dts/sdk/src/version.d.ts +1 -1
  147. package/dist/_dts/theme-core/src/blocks/opaquePanels.d.ts +3 -3
  148. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  149. package/dist/client/BookingMailingListNote-CBuMKN0w.mjs +386 -0
  150. package/dist/client/{CardListItem-ZDLYh6dL.mjs → CardListItem-Dc9XTXhB.mjs} +3 -4
  151. package/dist/client/CheckIcon-CpyXr-Kg.mjs +35 -0
  152. package/dist/client/{CheckboxChoice-M-3HycLw.mjs → CheckboxChoice-CIqx_o0E.mjs} +1 -1
  153. package/dist/client/{DemoBookingPortalAccess-BMtvPTWE.mjs → DemoBookingPortalAccess-D3VQ_kLj.mjs} +85 -85
  154. package/dist/client/{EventCombined.client-DUm_DOxW.mjs → EventCombined.client-BHGEKise.mjs} +8 -9
  155. package/dist/client/{EventEmbeddedCheckoutStep-DsNNzgRm.mjs → EventEmbeddedCheckoutStep-Bo1Cm23R.mjs} +11 -10
  156. package/dist/client/{EventListing.client-DjNYGUVm.mjs → EventListing.client-D5OQgVPr.mjs} +2 -2
  157. package/dist/client/{LeafletStaticMap-DuFiOHq_.mjs → LeafletStaticMap-CLL5cWrr.mjs} +1 -1
  158. package/dist/client/{ModalShell-CqkBvBIC.mjs → ModalShell-BXzucfR6.mjs} +83 -12
  159. package/dist/client/{RichText-LjPCUGGi.mjs → RichText-BoDmnopf.mjs} +6 -3
  160. package/dist/client/{ThemedButton-DZKjtaDD.mjs → ThemedButton-Ciltp0IK.mjs} +2 -2
  161. package/dist/client/{accordion-Qe--SILD.mjs → accordion-DOkRS64-.mjs} +1 -1
  162. package/dist/client/aspectTransforms-D-TnDdq4.mjs +93 -0
  163. package/dist/client/{bookingForm-3BCl4g_f.mjs → bookingForm-wlMVwo9h.mjs} +289 -149
  164. package/dist/client/bookings.mjs +12212 -10832
  165. package/dist/client/{carousel-h2YjKOS2.mjs → carousel-CGWk-Z0u.mjs} +2 -2
  166. package/dist/client/{carousel.client-BLrd4hmY.mjs → carousel.client-kX5mN1Eg.mjs} +46 -41
  167. package/dist/client/{cart-5NeiigVd.mjs → cart-OBBPlaH4.mjs} +3 -3
  168. package/dist/client/{checkout-G4UWjOEw.mjs → checkout-Bmw2WjQX.mjs} +3 -3
  169. package/dist/client/client.mjs +9541 -6039
  170. package/dist/client/{colorStyles-DTunOThj.mjs → colorStyles-ECDNPE_P.mjs} +23 -86
  171. package/dist/client/{context-GK4-AVAh.mjs → context-BI77UFAn.mjs} +275 -10
  172. package/dist/client/{eventCalendar-Prw3Yo_L.mjs → eventCalendar-qkej2Edn.mjs} +19 -17
  173. package/dist/client/{eventCombined-BUugi6__.mjs → eventCombined-DApKHvat.mjs} +4 -4
  174. package/dist/client/{eventDetails-CBHUda-R.mjs → eventDetails-BQyUDPH0.mjs} +3 -4
  175. package/dist/client/{eventListing-CQeG45Dg.mjs → eventListing-CeAnshlZ.mjs} +4 -4
  176. package/dist/client/{eventRegistration-Dif3p3oJ.mjs → eventRegistration-C3A3NyBO.mjs} +2209 -2144
  177. package/dist/client/{form-Cf0WMtfG.mjs → form-DW8rU7jO.mjs} +6 -6
  178. package/dist/client/{headerSection-Bqdr3JWp.mjs → headerSection-CkIhsoNm.mjs} +9 -9
  179. package/dist/client/hooks.mjs +1289 -380
  180. package/dist/client/{locationMap-BVvPou4O.mjs → locationMap-D4O8wbli.mjs} +4 -4
  181. package/dist/client/{media-47fcnhzD.mjs → media-C5K_0xPd.mjs} +2 -2
  182. package/dist/client/{newsletterForm-Cx-1dnic.mjs → newsletterForm-B7IxSPuz.mjs} +5 -5
  183. package/dist/client/{productDetail-DaUMaJYk.mjs → productDetail-Bqxpwy5N.mjs} +3 -3
  184. package/dist/client/{productList-BKQlWEKE.mjs → productList-u9Lz8V15.mjs} +3 -3
  185. package/dist/client/{propsDecoder-DO5iXphQ.mjs → propsDecoder-Bf1jucUp.mjs} +17333 -14489
  186. package/dist/client/rendering/client.mjs +58216 -55133
  187. package/dist/client/rendering/islands.mjs +19 -19
  188. package/dist/client/rendering.mjs +9099 -5636
  189. package/dist/client/{routeIslandApiConfig-BNm0Z3-g.mjs → routeIslandApiConfig-BgP-aPMl.mjs} +1 -1
  190. package/dist/client/{shop-D7ytcFBC.mjs → shop-B_eykR-_.mjs} +16 -14
  191. package/dist/client/{shop-commerce-D68-hHy2.mjs → shop-commerce-CT0APkFV.mjs} +9 -9
  192. package/dist/client/{shop-commerce-store-Cu7X2lyu.mjs → shop-commerce-store-DcvZxdEK.mjs} +1 -1
  193. package/dist/client/{shopShared-D12rxE69.mjs → shopShared-BVJOiUiJ.mjs} +5 -5
  194. package/dist/client/themePortalRoot-DS92SRVZ.mjs +47 -0
  195. package/dist/client/{TerminalPanel-DOTSUCO0.mjs → useDiscountCodeUi-DPmYDunz.mjs} +60 -77
  196. package/dist/client/{useEventPagination-jgKa67KV.mjs → useEventPagination-8-U55vH5.mjs} +7 -8
  197. package/dist/preview-next/before-render.mjs +7 -0
  198. package/dist/preview-next/client/runtime.mjs +3 -3
  199. package/dist/preview-next/keepAlive.mjs +7 -0
  200. package/dist/preview-next/middleware.mjs +7 -0
  201. package/dist/{sdk-runtime-4cCWO_3i.mjs → sdk-runtime-C5jPdSrk.mjs} +1 -1
  202. package/dist/server/components.mjs +3138 -1373
  203. package/dist/server/config-validation.mjs +1395 -486
  204. package/dist/server/config.mjs +1395 -486
  205. package/dist/server/data.mjs +1289 -380
  206. package/dist/server/index.mjs +8 -1
  207. package/dist/server/next.mjs +3362 -1538
  208. package/dist/server/page-converter.mjs +1357 -453
  209. package/dist/server/prebuild.mjs +1 -1
  210. package/dist/server/rendering/server.mjs +2335 -1044
  211. package/dist/server/rendering.mjs +3152 -1370
  212. package/dist/server/routing.mjs +1437 -490
  213. package/dist/server/server.mjs +1297 -381
  214. package/dist/server/theme-bridge.mjs +1135 -795
  215. package/dist/{spinner-tSdG9YvG.mjs → spinner-B-ab2rJp.mjs} +1 -1
  216. package/dist/styles/index.css +1088 -721
  217. package/package.json +1 -1
  218. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/OrderSummary.d.ts +0 -15
  219. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useWaitlistClaimRestoration.d.ts +0 -22
  220. package/dist/_dts/blocks/src/system/runtime/shared/components/SelectableOptionCard.d.ts +0 -30
  221. package/dist/client/BookingMailingListNote-DlDSs5Vm.mjs +0 -567
  222. package/dist/client/pricing-CMD0kADd.mjs +0 -314
  223. package/dist/client/result-DRW7JWXz.mjs +0 -30
  224. package/dist/client/scrollToBlock-C86BYkyV.mjs +0 -17
  225. package/dist/client/themePortalRoot-AGs4cE7t.mjs +0 -30
  226. package/dist/spinner-BL3kW9Uh.mjs +0 -803
@@ -323,6 +323,29 @@ function canonicalEventPricingDisplay(pricing) {
323
323
  default: return assertNever$3(pricing);
324
324
  }
325
325
  }
326
+ /**
327
+ * The single renderer for a precomputed booking price `display` ADT. This is
328
+ * the one place booking price variants map to user-facing strings — every call
329
+ * site (booking entry, events calendar, event/course details, listings, portal)
330
+ * should route through here so labels never drift. Adding a variant to the ADT
331
+ * breaks compilation here (via `assertNever`) until it is handled.
332
+ */
333
+ function formatBookingPriceDisplay(display, options = {}) {
334
+ switch (display.kind) {
335
+ case "free": return "Free";
336
+ case "exact": return formatCurrencyAmount(display.amount, display.currency, options);
337
+ case "from": return `From ${formatCurrencyAmount(display.amount, display.currency, options)}`;
338
+ case "range": return `${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)}`;
339
+ 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)}`;
340
+ case "unavailable": return "Pricing unavailable";
341
+ case "external": return display.label;
342
+ default: return assertNever$3(display);
343
+ }
344
+ }
345
+ /** Render an event's price directly from its pricing value. */
346
+ function formatEventPricing(pricing, options = {}) {
347
+ return formatBookingPriceDisplay(publicEventPricingDisplay(pricing), options);
348
+ }
326
349
  //#endregion
327
350
  //#region ../core/src/event-page-surface-subject.ts
328
351
  function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
@@ -466,6 +489,7 @@ function parseBookingTimeZone(timeZone) {
466
489
  }
467
490
  //#endregion
468
491
  //#region ../core/src/booking-date-time.ts
492
+ const DEFAULT_LOCALE = "en-US";
469
493
  const FALLBACK_TIMEZONE = "UTC";
470
494
  const START_OF_DAY = {
471
495
  hour: 0,
@@ -588,6 +612,34 @@ function toScheduledBookingDateTime(input) {
588
612
  if (result.ok) return result.value;
589
613
  throw new Error(formatParseError(result));
590
614
  }
615
+ function formatBookingInstant(isoString, options) {
616
+ const date = new Date(isoString);
617
+ if (Number.isNaN(date.getTime())) return isoString;
618
+ return getCachedDateTimeFormatter(options.locale ?? DEFAULT_LOCALE, getSafeTimeZone(options.timeZone), options.format).format(date);
619
+ }
620
+ function formatBookingDate(input, format = {
621
+ weekday: "long",
622
+ year: "numeric",
623
+ month: "long",
624
+ day: "numeric"
625
+ }) {
626
+ return formatBookingInstant(input.startsAt, {
627
+ locale: input.locale,
628
+ timeZone: input.timeZone,
629
+ format
630
+ });
631
+ }
632
+ function formatBookingTime(input, format = {
633
+ hour: "numeric",
634
+ minute: "2-digit",
635
+ hour12: true
636
+ }) {
637
+ return formatBookingInstant(input.startsAt, {
638
+ locale: input.locale,
639
+ timeZone: input.timeZone,
640
+ format
641
+ });
642
+ }
591
643
  function getBookingLocalDateKey(input) {
592
644
  return formatDateKey(getLocalScheduleDate(new Date(input.startsAt), input.timeZone));
593
645
  }
@@ -849,6 +901,14 @@ function formError(field) {
849
901
  }
850
902
  //#endregion
851
903
  //#region ../core/src/mailing-list-targeting.ts
904
+ const DEFAULT_MAILING_LIST_TARGETING = { mode: "default" };
905
+ function mailingListTargetingChoiceIds(targeting) {
906
+ switch (targeting.provider) {
907
+ case "native": return targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
908
+ case "brevo": return targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
909
+ case "mailchimp": return targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
910
+ }
911
+ }
852
912
  function planMailingListChoicePresentation(targeting) {
853
913
  if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
854
914
  switch (targeting.provider) {
@@ -888,6 +948,103 @@ function planMailingListChoicePresentation(targeting) {
888
948
  }
889
949
  }
890
950
  //#endregion
951
+ //#region ../core/src/mailing-list-targeting-availability.ts
952
+ function resolveMailingListDefaultDestination(provider) {
953
+ switch (provider.provider) {
954
+ case "native": return provider.topics.find((item) => item.id === provider.defaultTopicId) ?? null;
955
+ case "brevo": return provider.lists.find((item) => item.id === provider.defaultListId) ?? null;
956
+ case "mailchimp": return provider.audiences.find((item) => item.id === provider.defaultAudienceId) ?? null;
957
+ }
958
+ }
959
+ function assessMailingListTargeting(context, targeting) {
960
+ if (context?.tag !== "configured") return {
961
+ tag: "unavailable",
962
+ reason: "setup_missing",
963
+ targeting
964
+ };
965
+ const catalog = context.provider;
966
+ if (targeting.mode === "custom") return assessCustomTargeting(catalog, targeting);
967
+ const destination = resolveMailingListDefaultDestination(catalog);
968
+ return destination === null ? {
969
+ tag: "unavailable",
970
+ reason: "default_missing",
971
+ targeting
972
+ } : {
973
+ tag: "available",
974
+ value: {
975
+ mode: "default",
976
+ targeting,
977
+ provider: catalog.provider,
978
+ destination
979
+ }
980
+ };
981
+ }
982
+ function assessCustomTargeting(catalog, targeting) {
983
+ switch (targeting.provider) {
984
+ case "native": return catalog.provider === "native" ? assessMatchedChoices({
985
+ provider: "native",
986
+ catalog,
987
+ targeting
988
+ }, catalog.topics) : {
989
+ tag: "unavailable",
990
+ reason: "provider_mismatch",
991
+ targeting
992
+ };
993
+ case "brevo": return catalog.provider === "brevo" ? assessMatchedChoices({
994
+ provider: "brevo",
995
+ catalog,
996
+ targeting
997
+ }, catalog.lists) : {
998
+ tag: "unavailable",
999
+ reason: "provider_mismatch",
1000
+ targeting
1001
+ };
1002
+ case "mailchimp": {
1003
+ if (catalog.provider !== "mailchimp") return {
1004
+ tag: "unavailable",
1005
+ reason: "provider_mismatch",
1006
+ targeting
1007
+ };
1008
+ const audience = catalog.audiences.find((item) => item.id === targeting.audienceId);
1009
+ return audience === void 0 ? {
1010
+ tag: "unavailable",
1011
+ reason: "audience_missing",
1012
+ value: {
1013
+ mode: "custom",
1014
+ provider: "mailchimp",
1015
+ catalog,
1016
+ targeting
1017
+ }
1018
+ } : assessMatchedChoices({
1019
+ provider: "mailchimp",
1020
+ catalog,
1021
+ targeting,
1022
+ audience
1023
+ }, audience.interests);
1024
+ }
1025
+ }
1026
+ }
1027
+ function assessMatchedChoices(matched, choices) {
1028
+ const selected = new Set(mailingListTargetingChoiceIds(matched.targeting));
1029
+ const available = new Set(choices.map((item) => item.id));
1030
+ const missingIds = [...selected].filter((id) => !available.has(id));
1031
+ const value = {
1032
+ mode: "custom",
1033
+ ...matched,
1034
+ choices,
1035
+ selectedChoices: choices.filter((item) => selected.has(item.id))
1036
+ };
1037
+ return missingIds.length > 0 ? {
1038
+ tag: "unavailable",
1039
+ reason: "choices_missing",
1040
+ missingIds,
1041
+ value
1042
+ } : {
1043
+ tag: "available",
1044
+ value
1045
+ };
1046
+ }
1047
+ //#endregion
891
1048
  //#region ../core/src/mailing-list-runtime.ts
892
1049
  function parseMailingListRuntimeContext(value) {
893
1050
  if (!isRecord$13(value)) return invalidRuntimeContext();
@@ -952,57 +1109,47 @@ function parseBrevoRuntimeProvider(value) {
952
1109
  function parseMailchimpRuntimeProvider(value) {
953
1110
  if (!Array.isArray(value.audiences)) return null;
954
1111
  const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
955
- if (defaultAudienceId === null) return null;
956
- const audiences = [];
957
- for (const audience of value.audiences) {
958
- if (!isRecord$13(audience)) return null;
959
- const id = parseMailchimpAudienceId(audience.id);
960
- const name = parseName(audience.name);
961
- if (id === null || name === null || !Array.isArray(audience.interests)) return null;
962
- const interests = parseMailchimpInterests(audience.interests);
963
- if (interests === null) return null;
964
- audiences.push({
965
- id,
966
- name,
967
- interests
968
- });
969
- }
970
- return {
1112
+ const audiences = Array.from(value.audiences, parseMailchimpAudience);
1113
+ return defaultAudienceId !== null && audiences.every((audience) => audience !== null) ? {
971
1114
  provider: "mailchimp",
972
1115
  defaultAudienceId,
973
1116
  audiences
974
- };
1117
+ } : null;
975
1118
  }
976
- function parseMailchimpInterests(value) {
977
- const interests = [];
978
- for (const interest of value) {
979
- if (!isRecord$13(interest)) return null;
980
- const id = parseMailchimpInterestId(interest.id);
981
- const name = parseName(interest.name);
982
- const categoryName = parseName(interest.categoryName);
983
- if (id === null || name === null || categoryName === null) return null;
984
- interests.push({
985
- id,
986
- name,
987
- categoryName
988
- });
989
- }
990
- return interests;
1119
+ function parseMailchimpAudience(value) {
1120
+ if (!isRecord$13(value) || !Array.isArray(value.interests)) return null;
1121
+ const id = parseMailchimpAudienceId(value.id);
1122
+ const name = parseName(value.name);
1123
+ const interests = Array.from(value.interests, parseMailchimpInterest);
1124
+ return id !== null && name !== null && interests.every((interest) => interest !== null) ? {
1125
+ id,
1126
+ name,
1127
+ interests
1128
+ } : null;
1129
+ }
1130
+ function parseMailchimpInterest(value) {
1131
+ if (!isRecord$13(value)) return null;
1132
+ const id = parseMailchimpInterestId(value.id);
1133
+ const name = parseName(value.name);
1134
+ const categoryName = parseName(value.categoryName);
1135
+ return id !== null && name !== null && categoryName !== null ? {
1136
+ id,
1137
+ name,
1138
+ categoryName
1139
+ } : null;
991
1140
  }
992
1141
  function parseNamedItems(value, parseId) {
993
1142
  if (!Array.isArray(value)) return null;
994
- const items = [];
995
- for (const item of value) {
1143
+ const items = Array.from(value, (item) => {
996
1144
  if (!isRecord$13(item)) return null;
997
1145
  const id = parseId(item.id);
998
1146
  const name = parseName(item.name);
999
- if (id === null || name === null) return null;
1000
- items.push({
1147
+ return id !== null && name !== null ? {
1001
1148
  id,
1002
1149
  name
1003
- });
1004
- }
1005
- return items;
1150
+ } : null;
1151
+ });
1152
+ return items.every((item) => item !== null) ? items : null;
1006
1153
  }
1007
1154
  function parseName(value) {
1008
1155
  if (typeof value !== "string") return null;
@@ -1018,6 +1165,7 @@ function invalidRuntimeContext() {
1018
1165
  //#endregion
1019
1166
  //#region ../core/src/mailing-list-targeting-parser.ts
1020
1167
  function parseMailingListPlacementTargeting(value) {
1168
+ if (value == null) return ok(DEFAULT_MAILING_LIST_TARGETING);
1021
1169
  if (!isRecord$12(value)) return targetingError("shape");
1022
1170
  if (value.mode === "default") return ok({ mode: "default" });
1023
1171
  if (value.mode !== "custom") return targetingError("mode");
@@ -1116,14 +1264,11 @@ function parseNonEmptyUniqueIds(value, parseId) {
1116
1264
  }
1117
1265
  function parseUniqueIds(value, parseId) {
1118
1266
  if (!Array.isArray(value)) return targetingError("targets");
1119
- const parsed = [];
1120
- for (const item of value) {
1267
+ return Array.from(value).reduce((parsed, item) => {
1268
+ if (!parsed.ok) return parsed;
1121
1269
  const id = parseId(item);
1122
- if (id === null) return targetingError("targets");
1123
- if (parsed.includes(id)) return targetingError("duplicate_target");
1124
- parsed.push(id);
1125
- }
1126
- return ok(parsed);
1270
+ return id === null ? targetingError("targets") : parsed.value.includes(id) ? targetingError("duplicate_target") : ok([...parsed.value, id]);
1271
+ }, ok([]));
1127
1272
  }
1128
1273
  function isRecord$12(value) {
1129
1274
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1552,6 +1697,45 @@ function resolveSiteVerticalBookingConfig(rawConfig) {
1552
1697
  };
1553
1698
  }
1554
1699
  //#endregion
1700
+ //#region ../core/src/event-preset-vocabulary.ts
1701
+ const EVENT_PRESET_VOCABULARY = {
1702
+ event: {
1703
+ label: "Event",
1704
+ pluralLabel: "Events",
1705
+ description: "Any event, repeating or manually dated, that does not fit the other categories."
1706
+ },
1707
+ class: {
1708
+ label: "Class",
1709
+ pluralLabel: "Classes",
1710
+ description: "Regular scheduled classes and group sessions."
1711
+ },
1712
+ course: {
1713
+ label: "Course",
1714
+ pluralLabel: "Courses",
1715
+ description: "A fixed set of dates that people book together."
1716
+ },
1717
+ term: {
1718
+ label: "Term",
1719
+ pluralLabel: "Terms",
1720
+ description: "A fixed term of sessions that people book together."
1721
+ },
1722
+ workshop: {
1723
+ label: "Workshop",
1724
+ pluralLabel: "Workshops",
1725
+ description: "One-off workshops, day sessions, or special events."
1726
+ },
1727
+ retreat: {
1728
+ label: "Retreat",
1729
+ pluralLabel: "Retreats",
1730
+ description: "Multi-day or residential immersive experiences."
1731
+ },
1732
+ performance: {
1733
+ label: "Performance",
1734
+ pluralLabel: "Performances",
1735
+ description: "Audience-facing shows, concerts, recitals, or showcases."
1736
+ }
1737
+ };
1738
+ //#endregion
1555
1739
  //#region ../media-core/src/typeGuards.ts
1556
1740
  function isRecord$11(value) {
1557
1741
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -12223,6 +12407,14 @@ bookingPaymentOptionSchemas.flexibleBalanceConfig;
12223
12407
  bookingPaymentOptionSchemas.paymentCollectionOption;
12224
12408
  const bookingPaymentTermsSchema = bookingPaymentOptionSchemas.paymentTerms;
12225
12409
  bookingPaymentOptionSchemas.paymentOptionsChoice;
12410
+ z.strictObject({
12411
+ currency: z.string().trim().length(3),
12412
+ totalCents: nonNegativeIntegerSchema,
12413
+ dueNowCents: nonNegativeIntegerSchema,
12414
+ laterPaymentAmountsCents: z.array(nonNegativeIntegerSchema),
12415
+ paymentOption: z.union([createBookingPaymentOptionSchemas("strict").paymentCollectionOption, z.strictObject({ kind: z.literal("not_required") })]),
12416
+ promisedDiscountId: z.string().min(1).nullable().optional()
12417
+ });
12226
12418
  //#endregion
12227
12419
  //#region ../blocks/src/system/blocks/shared/bookingPaymentTerms.ts
12228
12420
  const blockBookingPaymentTermsSchema = bookingPaymentTermsSchema;
@@ -12463,6 +12655,10 @@ const publicEventBaseSchema = z.object({
12463
12655
  eventDestinationMode: eventDestinationModeSchema.optional(),
12464
12656
  scheduleScope: eventScheduleScopeSchema$2.optional(),
12465
12657
  recurrence: publicEventRecurrenceSchema.optional(),
12658
+ scheduleSummary: z.object({
12659
+ recurrenceDescription: z.string(),
12660
+ startTime: z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/).nullable()
12661
+ }).nullable().optional(),
12466
12662
  id: z.string(),
12467
12663
  occurrenceId: z.string(),
12468
12664
  seriesId: z.string(),
@@ -33868,6 +34064,15 @@ const formBlockDefinition = {
33868
34064
  runtime: formBlockRuntime
33869
34065
  };
33870
34066
  //#endregion
34067
+ //#region ../blocks/src/system/helpers/normalizeMailingListBlockContent.ts
34068
+ /** Fill the declared default without admitting or rewriting invalid custom settings. */
34069
+ function normalizeMailingListBlockContent(record) {
34070
+ return record.targeting == null ? {
34071
+ ...record,
34072
+ targeting: DEFAULT_MAILING_LIST_TARGETING
34073
+ } : record;
34074
+ }
34075
+ //#endregion
33871
34076
  //#region ../blocks/src/system/blocks/contact-newsletter.ts
33872
34077
  const contactNewsletterLayout = styledSection({
33873
34078
  children: sectionContainer([el("div", { className: "contact-newsletter-grid" }, [el("div", { className: "contact-newsletter-column contact-newsletter-contact" }, [el("div", { className: "contact-newsletter-copy" }, [text({
@@ -34031,6 +34236,10 @@ const contactNewsletterBlockDefinition = {
34031
34236
  ],
34032
34237
  icon: "MailPlus"
34033
34238
  }),
34239
+ contentNormalizer: {
34240
+ fieldIds: ["targeting"],
34241
+ normalize: normalizeMailingListBlockContent
34242
+ },
34034
34243
  dataSchemas: { form: formDataSchema.optional() },
34035
34244
  dataLoaders: { form: {
34036
34245
  endpoint: "getPublicFormById",
@@ -38373,70 +38582,274 @@ const occurrenceContextSchema = z.object({
38373
38582
  overrides: z.record(z.string(), z.unknown()).nullable().optional()
38374
38583
  }).nullable();
38375
38584
  //#endregion
38376
- //#region ../blocks/src/system/runtime/api/readAuthority.ts
38377
- const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
38378
- kind: z.literal("preview"),
38379
- token: z.string().min(1)
38380
- }).strict()]);
38381
- const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
38382
- kind: z.literal("unsupported"),
38383
- reason: z.enum([
38384
- "unsupportedSubject",
38385
- "invalidEventSeriesId",
38386
- "invalidEventSeriesBookingScope",
38387
- "invalidEventOfferingId",
38388
- "invalidEventOfferingBookingScope",
38389
- "invalidEventOfferingCatalogue"
38390
- ])
38391
- });
38392
- const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
38393
- const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
38394
- if (unsupported.success) return unsupported.data;
38395
- const parsed = parseEventPageSurfaceSubject(value);
38396
- if (parsed.ok) return parsed.value;
38397
- ctx.addIssue({
38398
- code: z.ZodIssueCode.custom,
38399
- message: `Invalid event page-surface subject (${parsed.error.kind})`
38400
- });
38401
- return z.NEVER;
38402
- });
38403
- function resolveEventRegistrationScope(context) {
38404
- const occurrenceSeriesId = context.occurrenceContext?.seriesId;
38405
- if (occurrenceSeriesId) return {
38406
- kind: "occurrenceSeries",
38407
- seriesId: occurrenceSeriesId
38585
+ //#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
38586
+ /**
38587
+ * Compute remaining spots for an event from `availableSpots` with a fallback
38588
+ * to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
38589
+ * for uncapped events so call sites can render no capacity indicator at all.
38590
+ *
38591
+ * The single source of truth for capacity math across event cards, agenda
38592
+ * rows, and timetable cells — keep wording variations in the call sites.
38593
+ */
38594
+ function getEventAvailability(event) {
38595
+ if (event.capacity == null) return {
38596
+ available: null,
38597
+ isFull: false
38408
38598
  };
38409
- if (context.subject?.kind === "eventSeries") return {
38410
- kind: "pageSurfaceSeries",
38411
- seriesId: context.subject.eventSeriesId
38599
+ const available = event.availableSpots ?? event.capacity - event.registeredCount;
38600
+ if (available <= 0) return {
38601
+ available: 0,
38602
+ isFull: true
38412
38603
  };
38413
- if (context.subject?.kind === "eventOffering") return {
38414
- kind: "pageSurfaceOffering",
38415
- offeringId: context.subject.eventOfferingId,
38416
- seriesIds: context.subject.catalogueEventSeriesIds
38604
+ return {
38605
+ available,
38606
+ isFull: false
38417
38607
  };
38418
- if (context.subject?.kind === "unsupported") return {
38419
- kind: "unsupportedPageSurfaceSubject",
38420
- reason: context.subject.reason
38608
+ }
38609
+ /**
38610
+ * Pick the capacity row a booking decision should read: the selected
38611
+ * attendance mode's availability when the visitor has chosen one and the
38612
+ * event publishes it, otherwise the event-level aggregate.
38613
+ *
38614
+ * The mode row is used wholesale. A mode with `capacity: null` on a capped
38615
+ * event is uncapped *by design*: the registration write path applies the
38616
+ * same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
38617
+ * back to the event's `availableSpots` here would block bookings the server
38618
+ * accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
38619
+ */
38620
+ function selectEventCapacitySource(event, attendanceMode) {
38621
+ if (!attendanceMode) return event;
38622
+ return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
38623
+ }
38624
+ /**
38625
+ * Whether the visitor's next step is the waitlist: the source is full *and*
38626
+ * a waitlist is open. The one place this is decided — cards, rows, the
38627
+ * wizard's steps and its action bar all route through here so they agree,
38628
+ * including for producers that omit `availableSpots` and rely on the
38629
+ * `capacity - registeredCount` fallback.
38630
+ */
38631
+ function canJoinEventWaitlist(event, options) {
38632
+ const source = selectEventCapacitySource(event, options?.attendanceMode);
38633
+ return getEventAvailability(source).isFull && source.waitlistEnabled === true;
38634
+ }
38635
+ /**
38636
+ * Remaining places at or below which an event reads as scarce and the exact
38637
+ * count is worth showing. Above it, no number is shown at all: "147 places
38638
+ * left" is not something a visitor acts on, and it is the figure most likely
38639
+ * to be stale on a cached page.
38640
+ *
38641
+ * max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
38642
+ *
38643
+ * The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
38644
+ * 5% floor stops large events staying silent until three remain.
38645
+ * capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
38646
+ */
38647
+ function lowCapacityThreshold(capacity) {
38648
+ return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
38649
+ }
38650
+ function getRemainingPlaces(source) {
38651
+ const { available, isFull } = getEventAvailability(source);
38652
+ if (available === null || source.capacity === null) return { kind: "unlimited" };
38653
+ if (isFull) return {
38654
+ kind: "full",
38655
+ waitlist: canJoinEventWaitlist(source)
38421
38656
  };
38422
- return { kind: "unscoped" };
38657
+ if (available <= lowCapacityThreshold(source.capacity)) return {
38658
+ kind: "low",
38659
+ remaining: available
38660
+ };
38661
+ return { kind: "open" };
38423
38662
  }
38424
- function resolveEventRegistrationScopedEvents(events, context) {
38425
- const scope = resolveEventRegistrationScope(context);
38426
- switch (scope.kind) {
38427
- case "occurrenceSeries":
38428
- case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
38429
- case "pageSurfaceOffering": {
38430
- const seriesIds = new Set(scope.seriesIds);
38431
- return events.filter((event) => seriesIds.has(event.seriesId));
38432
- }
38433
- case "unsupportedPageSurfaceSubject": return [];
38434
- case "unscoped": return [...events];
38663
+ /**
38664
+ * The one place a remaining-places count becomes text. Returns `null` when
38665
+ * nothing should be shown: uncapped events, and capped events above the
38666
+ * scarcity threshold. Surfaces that need their own full-state wording pass
38667
+ * it in rather than re-deriving the count.
38668
+ */
38669
+ function formatRemainingPlaces(source, wording = {}) {
38670
+ const remaining = getRemainingPlaces(source);
38671
+ switch (remaining.kind) {
38672
+ case "unlimited":
38673
+ case "open": return null;
38674
+ case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
38675
+ case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
38676
+ }
38677
+ }
38678
+ function formatPlacesLeft(count, compact) {
38679
+ if (compact) return `${count} left`;
38680
+ return count === 1 ? "1 place left" : `${count} places left`;
38681
+ }
38682
+ function formatTicketTypeRemaining(remainingCapacity) {
38683
+ if (remainingCapacity === null || remainingCapacity > 3) return null;
38684
+ if (remainingCapacity <= 0) return "None left for this ticket type";
38685
+ return `${formatPlacesLeft(remainingCapacity, false)} for this ticket type`;
38686
+ }
38687
+ /**
38688
+ * Which capacity figure wins when they disagree.
38689
+ *
38690
+ * For a hybrid event that publishes both attendance-mode rows, **the mode
38691
+ * rows are authoritative and the event-level aggregate is only a summary**.
38692
+ * The registration write path admits or refuses per mode
38693
+ * (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
38694
+ * aggregate can read full while a mode still has room, and can read open
38695
+ * while the mode the visitor wants is full. Believing the aggregate on a
38696
+ * card is how a "places available" event turns into a dead end at step two.
38697
+ *
38698
+ * Everything else — single-mode events, events that publish no rows, and the
38699
+ * aggregate-only helpers {@link getEventAvailability} /
38700
+ * {@link getRemainingPlaces} that existing callers rely on — keeps reading
38701
+ * the aggregate, unchanged.
38702
+ */
38703
+ function findHybridModeRows(event) {
38704
+ if (event.deliveryMode !== "hybrid") return null;
38705
+ const rows = event.attendanceModes ?? [];
38706
+ const inPerson = rows.find((row) => row.mode === "in_person");
38707
+ const online = rows.find((row) => row.mode === "online");
38708
+ return inPerson && online ? {
38709
+ inPerson,
38710
+ online
38711
+ } : null;
38712
+ }
38713
+ /** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
38714
+ function remainingPlacesRank(remaining) {
38715
+ switch (remaining.kind) {
38716
+ case "unlimited": return 4;
38717
+ case "open": return 3;
38718
+ case "low": return 2;
38719
+ case "full": return remaining.waitlist ? 1 : 0;
38720
+ }
38721
+ }
38722
+ function isMoreOpen(candidate, than) {
38723
+ const rank = remainingPlacesRank(candidate);
38724
+ const otherRank = remainingPlacesRank(than);
38725
+ if (rank !== otherRank) return rank > otherRank;
38726
+ return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
38727
+ }
38728
+ /** True when `candidate` has less room left than `than`. */
38729
+ function isScarcer(candidate, than) {
38730
+ return isMoreOpen(than, candidate);
38731
+ }
38732
+ const ATTENDANCE_MODE_LABEL = {
38733
+ in_person: "In person",
38734
+ online: "Online"
38735
+ };
38736
+ /** Wording for a mode with room to spare, where the count is deliberately withheld. */
38737
+ const PLACES_AVAILABLE = "Places available";
38738
+ /**
38739
+ * The browse-surface label for an event's remaining places. Single-mode
38740
+ * events get exactly the aggregate label {@link formatRemainingPlaces}
38741
+ * produces, and so does a hybrid event whose two modes tell the same story.
38742
+ * When the modes differ, both sides are named: "available" on a card is a
38743
+ * promise the registration wizard would otherwise have to break.
38744
+ *
38745
+ * Hybrid examples, in person full with 3 online places left:
38746
+ * default -> "Online: 3 places left · In person: Full"
38747
+ * { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
38748
+ * { compact: true } -> "Online: 3 left"
38749
+ */
38750
+ function formatEventRemainingPlaces(event, wording = {}) {
38751
+ const rows = findHybridModeRows(event);
38752
+ if (!rows) return formatRemainingPlaces(event, wording);
38753
+ const inPerson = formatRemainingPlaces(rows.inPerson, wording);
38754
+ const online = formatRemainingPlaces(rows.online, wording);
38755
+ if (inPerson === online) return inPerson;
38756
+ if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
38757
+ return formatBothHybridSides(rows, {
38758
+ inPerson,
38759
+ online
38760
+ });
38761
+ }
38762
+ /**
38763
+ * Both sides, the one with room first: that is the half a visitor can act on,
38764
+ * and it should not arrive after a full stop's worth of bad news.
38765
+ */
38766
+ function formatBothHybridSides(rows, labels) {
38767
+ 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(" · ");
38768
+ }
38769
+ function namedModePlaces(mode, label) {
38770
+ return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
38771
+ }
38772
+ /**
38773
+ * Pills and timetable cells have room for one short phrase, so they name the
38774
+ * single mode that carries news rather than listing both: the scarce side
38775
+ * when there is a count to show, and otherwise the side that has closed —
38776
+ * "In person: Full", never "Online only", which reads as a statement about
38777
+ * how the event is delivered rather than about its remaining places.
38778
+ */
38779
+ function formatCompactHybridPlaces(rows, wording) {
38780
+ const inPerson = getRemainingPlaces(rows.inPerson);
38781
+ const online = getRemainingPlaces(rows.online);
38782
+ if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
38783
+ if (inPerson.kind === "full" || online.kind === "full") {
38784
+ const fullSide = inPerson.kind === "full" ? "in_person" : "online";
38785
+ const openSide = inPerson.kind === "full" ? "online" : "in_person";
38786
+ const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
38787
+ return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
38435
38788
  }
38789
+ const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
38790
+ return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
38791
+ }
38792
+ function rowFor(rows, mode) {
38793
+ return mode === "online" ? rows.online : rows.inPerson;
38436
38794
  }
38437
38795
  //#endregion
38796
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDisplay.ts
38797
+ const registrationDisplaySchema = z.object({
38798
+ venue: z.enum(["shown", "hidden"]),
38799
+ placesLeft: z.enum(["shown", "hidden"])
38800
+ });
38801
+ function resolveRegistrationDisplay(source) {
38802
+ return {
38803
+ venue: source.showVenue === false ? "hidden" : "shown",
38804
+ placesLeft: source.showCapacity === false ? "hidden" : "shown"
38805
+ };
38806
+ }
38807
+ /** Venue stays in domain data; views receive only the permitted presentation. */
38808
+ function shownVenue(event, display) {
38809
+ return display.venue === "shown" ? event.venue?.name ?? null : null;
38810
+ }
38811
+ /** Full/waitlist explanations remain visible even when optional counts are hidden. */
38812
+ function placesLeftLabel(source, display, wording = {}) {
38813
+ if ("kind" in source) return ticketPlacesLabel(source.remaining, display);
38814
+ if (source.deliveryMode === "hybrid" && source.attendanceModes?.length) return hybridPlacesLabel(source, display, wording);
38815
+ return capacityPlacesLabel(source, display, wording);
38816
+ }
38817
+ function capacityPlacesLabel(source, display, wording) {
38818
+ const remaining = getRemainingPlaces(source);
38819
+ if (display.placesLeft === "hidden" && remaining.kind !== "full") return null;
38820
+ switch (remaining.kind) {
38821
+ case "full": return formatRemainingPlaces(source, wording);
38822
+ case "low": return formatRemainingPlaces(source, wording);
38823
+ case "unlimited": return wording.unlimited ?? null;
38824
+ case "open": return wording.open ?? null;
38825
+ default: return assertNever$3(remaining);
38826
+ }
38827
+ }
38828
+ function hybridPlacesLabel(event, display, wording) {
38829
+ if (display.placesLeft === "shown") return formatEventRemainingPlaces(event, wording);
38830
+ return (event.attendanceModes ?? []).flatMap((mode) => {
38831
+ const label = placesLeftLabel(mode, display, wording);
38832
+ return label ? [`${mode.mode === "online" ? "Online" : "In person"}: ${label}`] : [];
38833
+ }).join(" · ") || null;
38834
+ }
38835
+ function ticketPlacesLabel(remaining, display) {
38836
+ if (remaining !== null && remaining > 0 && display.placesLeft === "hidden") return null;
38837
+ return formatTicketTypeRemaining(remaining);
38838
+ }
38839
+ //#endregion
38840
+ //#region ../blocks/src/system/runtime/api/readAuthority.ts
38841
+ const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
38842
+ kind: z.literal("preview"),
38843
+ token: z.string().min(1)
38844
+ }).strict()]);
38845
+ //#endregion
38438
38846
  //#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
38439
38847
  /**
38848
+ * Date and time presentation for events: the single place an event's
38849
+ * timestamps become strings or date buckets. Calendar-grid primitives are
38850
+ * re-exported from @riverbankcms/calendar so block code has one import.
38851
+ */
38852
+ /**
38440
38853
  * The English locale a site's public dates are written in, read from the
38441
38854
  * site's own time zone (Will, 2026-09-26: "date style from site time zone").
38442
38855
  *
@@ -38456,6 +38869,28 @@ function eventDateLocale(siteTimeZone) {
38456
38869
  if (siteTimeZone === "Pacific/Auckland" || siteTimeZone === "Pacific/Chatham") return "en-NZ";
38457
38870
  return "en-US";
38458
38871
  }
38872
+ function formatEventCompactDateRail(isoTimestamp, options, letterCase = "capitals") {
38873
+ const locale = options?.locale ?? "en-US";
38874
+ const dateTime = toScheduledBookingDateTime({
38875
+ startsAt: isoTimestamp,
38876
+ timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
38877
+ locale
38878
+ });
38879
+ const cased = (value) => letterCase === "capitals" ? value.toLocaleUpperCase(locale) : value;
38880
+ const weekday = cased(formatBookingDate(dateTime, { weekday: "short" }));
38881
+ const month = cased(formatBookingDate(dateTime, { month: "short" }).slice(0, 3));
38882
+ return {
38883
+ weekday,
38884
+ day: formatBookingDate(dateTime, { day: "numeric" }),
38885
+ month,
38886
+ label: formatBookingDate(dateTime, {
38887
+ weekday: "long",
38888
+ year: "numeric",
38889
+ month: "long",
38890
+ day: "numeric"
38891
+ })
38892
+ };
38893
+ }
38459
38894
  //#endregion
38460
38895
  //#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
38461
38896
  function isRecord$7(value) {
@@ -38475,6 +38910,15 @@ function resolveRuntimeEventDestination(event) {
38475
38910
  };
38476
38911
  }
38477
38912
  //#endregion
38913
+ //#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
38914
+ /**
38915
+ * Format an event's price directly from its pricing value (without the full
38916
+ * event). Same output as {@link formatEventPrice}.
38917
+ */
38918
+ function formatEventPriceDisplay(pricing) {
38919
+ return formatEventPricing(pricing, { decimals: "auto" });
38920
+ }
38921
+ //#endregion
38478
38922
  //#region ../blocks/src/system/runtime/nodes/events/domain/listWeek.ts
38479
38923
  /** Civil weeks use the site zone, including weeks containing a clock change. */
38480
38924
  function buildEventListWeek(anchor, startOfWeek, timeZone) {
@@ -38509,52 +38953,6 @@ function eventListWindowEvents(events, window) {
38509
38953
  });
38510
38954
  }
38511
38955
  //#endregion
38512
- //#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
38513
- const previewStageSchema = z.enum(["preview", "published"]);
38514
- const registrationButtonVariantSchema = blockButtonRoleSchema;
38515
- const eventButtonVariantSchema = blockButtonRoleSchema;
38516
- const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
38517
- const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
38518
- const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
38519
- const columnOptionSchema = z.enum([
38520
- "2",
38521
- "3",
38522
- "4"
38523
- ]);
38524
- const weekStartSchema = z.enum(["sunday", "monday"]);
38525
- const filterAutoShowSchema = z.enum([
38526
- "auto",
38527
- "always",
38528
- "never"
38529
- ]);
38530
- const calendarTabViewsSchema = z.enum([
38531
- "both",
38532
- "month",
38533
- "week"
38534
- ]);
38535
- //#endregion
38536
- //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
38537
- /**
38538
- * Determine which wizard steps to show based on context and payment requirements.
38539
- *
38540
- * @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
38541
- * @param needsPaymentStep - Whether user must complete payment method selection
38542
- * @returns Array of wizard steps to display
38543
- */
38544
- function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep, paymentActive = false) {
38545
- const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
38546
- "select-event",
38547
- "select-tickets",
38548
- "enter-details"
38549
- ];
38550
- if (!needsPaymentStep) return baseSteps;
38551
- return paymentActive ? [
38552
- ...baseSteps,
38553
- "select-payment",
38554
- "payment"
38555
- ] : [...baseSteps, "select-payment"];
38556
- }
38557
- //#endregion
38558
38956
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
38559
38957
  function eventMatchesOccurrenceId(event, occurrenceId) {
38560
38958
  return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
@@ -38578,6 +38976,424 @@ function resolveEventRequiresPayment(input) {
38578
38976
  return publicEventPricingType(input.event.pricing) === "paid";
38579
38977
  }
38580
38978
  //#endregion
38979
+ //#region ../blocks/src/system/runtime/nodes/event-registration/utils.ts
38980
+ /**
38981
+ * The locale a site's booking dates are written in: the one the site's time
38982
+ * zone places it in (`eventDateLocale`, shared with event cards), or British
38983
+ * English when the site names no zone.
38984
+ */
38985
+ function bookingDateLocale(siteTimeZone) {
38986
+ return siteTimeZone ? eventDateLocale(siteTimeZone) : "en-GB";
38987
+ }
38988
+ function bookingInstant(startsAt, input) {
38989
+ return toScheduledBookingDateTime({
38990
+ startsAt,
38991
+ timeZone: toBookingTimeZone(input.timeZone),
38992
+ locale: bookingDateLocale(input.siteTimeZone)
38993
+ });
38994
+ }
38995
+ /** "7:00pm": a twelve-hour clock with a lowercase, unspaced day period. */
38996
+ function bookingClock(startsAt, input) {
38997
+ return formatBookingTime(bookingInstant(startsAt, input), {
38998
+ hour: "numeric",
38999
+ minute: "2-digit",
39000
+ hour12: true
39001
+ }).replace(/\s*([ap])\.?\s?m\.?$/i, (_match, period) => `${period.toLowerCase()}m`);
39002
+ }
39003
+ /** "7:00pm – 8:15pm", or just the start when there's no distinct end. */
39004
+ function clockRange(input) {
39005
+ const start = bookingClock(input.startsAt, input);
39006
+ const end = input.endsAt ? bookingClock(input.endsAt, input) : null;
39007
+ return end && end !== start ? `${start} – ${end}` : start;
39008
+ }
39009
+ /** A stored local clock has no date or DST offset; UTC preserves its digits. */
39010
+ function formatBookingScheduleSummary(event) {
39011
+ const summary = event.scheduleSummary;
39012
+ if (!summary) return null;
39013
+ const time = summary.startTime ? formatBookingWhen({
39014
+ startsAt: `1970-01-01T${summary.startTime}:00Z`,
39015
+ timeZone: "UTC",
39016
+ siteTimeZone: event.siteTimeZone
39017
+ }, "time") : null;
39018
+ return [summary.recurrenceDescription, time].filter(Boolean).join(" · ");
39019
+ }
39020
+ /** The one booking date formatter; every variant derives from it. */
39021
+ function formatBookingWhen(input, variant) {
39022
+ const instant = bookingInstant(input.startsAt, input);
39023
+ const day = formatBookingDate(instant, {
39024
+ weekday: "short",
39025
+ day: "numeric",
39026
+ month: "short"
39027
+ });
39028
+ switch (variant) {
39029
+ case "day": return day;
39030
+ case "dayLong": return formatBookingDate(instant, {
39031
+ weekday: "long",
39032
+ day: "numeric",
39033
+ month: "long"
39034
+ });
39035
+ case "month": return formatBookingDate(instant, { month: "long" });
39036
+ case "monthYear": return formatBookingDate(instant, {
39037
+ month: "long",
39038
+ year: "numeric"
39039
+ });
39040
+ case "time": return bookingClock(input.startsAt, input);
39041
+ case "timeRange": return clockRange(input);
39042
+ case "short": return `${day}, ${bookingClock(input.startsAt, input)}`;
39043
+ case "long": return `${day} · ${clockRange(input)}`;
39044
+ }
39045
+ }
39046
+ /** A single instant in the short form, e.g. "Mon 5 Oct, 7:00pm". */
39047
+ function formatEventDateTime(isoTimestamp, options) {
39048
+ return formatBookingWhen({
39049
+ startsAt: isoTimestamp,
39050
+ timeZone: options?.timeZone,
39051
+ siteTimeZone: options?.siteTimeZone
39052
+ }, "short");
39053
+ }
39054
+ /** The title an occurrence was given on its own, when it overrides the event's. */
39055
+ function occurrenceTitleOverride(occurrence) {
39056
+ const title = occurrence?.overrides?.title;
39057
+ return typeof title === "string" && title.trim() ? title.trim() : null;
39058
+ }
39059
+ /** The event as this page names it: an occurrence's own title wins. */
39060
+ function withOccurrenceTitle(event, occurrence) {
39061
+ const title = occurrenceTitleOverride(occurrence);
39062
+ return event && title ? {
39063
+ ...event,
39064
+ title
39065
+ } : event;
39066
+ }
39067
+ function courseSchedule(sessions, siteTimeZone) {
39068
+ const first = sessions[0];
39069
+ if (!first) return null;
39070
+ const last = sessions[sessions.length - 1] ?? first;
39071
+ const firstLabel = formatEventDateTime(first.startsAt, {
39072
+ timeZone: first.timeZone,
39073
+ siteTimeZone
39074
+ });
39075
+ if (last.startsAt === first.startsAt) return `${sessions.length} session${sessions.length === 1 ? "" : "s"} · ${firstLabel}`;
39076
+ const lastLabel = formatEventDateTime(last.startsAt, {
39077
+ timeZone: last.timeZone,
39078
+ siteTimeZone
39079
+ });
39080
+ return `${sessions.length} sessions · ${firstLabel} – ${lastLabel}`;
39081
+ }
39082
+ /**
39083
+ * When a booking happens, in one line: the occurrence's date and time, or a
39084
+ * course's session count and first and last dates. `short` by default (bar,
39085
+ * confirmation); `long` adds the end time (card, panel header).
39086
+ */
39087
+ function formatRegistrationSchedule(event, occurrence, variant = "short") {
39088
+ if (event.registrationSubject.kind === "wholeSeries") return courseSchedule(event.registrationSubject.sessions, event.siteTimeZone);
39089
+ const input = scheduleInput(event, occurrence ?? null);
39090
+ return input ? formatBookingWhen(input, variant) : null;
39091
+ }
39092
+ /** The occurrence's own times when the page names one, otherwise the event's. */
39093
+ function scheduleInput(event, occurrence) {
39094
+ const times = occurrence?.startsAt ? {
39095
+ startsAt: occurrence.startsAt,
39096
+ endsAt: occurrence.endsAt ?? null
39097
+ } : {
39098
+ startsAt: event.startsAt,
39099
+ endsAt: event.endsAt
39100
+ };
39101
+ if (!times.startsAt) return null;
39102
+ return {
39103
+ ...times,
39104
+ timeZone: occurrence?.timeZone ?? event.timeZone ?? null,
39105
+ siteTimeZone: event.siteTimeZone
39106
+ };
39107
+ }
39108
+ //#endregion
39109
+ //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
39110
+ function registrationRoute(input) {
39111
+ const base = input.chooseEvent ? [
39112
+ "select-event",
39113
+ "select-tickets",
39114
+ "enter-details"
39115
+ ] : ["select-tickets", "enter-details"];
39116
+ switch (input.payment) {
39117
+ case "none": return base;
39118
+ case "options": return [...base, "select-payment"];
39119
+ case "optionsAndPayment": return [
39120
+ ...base,
39121
+ "select-payment",
39122
+ "payment"
39123
+ ];
39124
+ }
39125
+ }
39126
+ const REGISTRATION_STEP_LABELS = [
39127
+ "Event",
39128
+ "Date",
39129
+ "Tickets",
39130
+ "Places",
39131
+ "Your details",
39132
+ "How you’ll pay",
39133
+ "Payment"
39134
+ ];
39135
+ /** Several dates of one series are a date choice; anything else is an event choice. */
39136
+ function getEventSelectionMode(events) {
39137
+ const firstSeriesId = events[0]?.seriesId;
39138
+ return events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId) ? "date" : "event";
39139
+ }
39140
+ /** The label facts for a surface: what the visitor chooses from, and what they book. */
39141
+ function registrationStepLabelContext(scopedEvents, bookedEvent) {
39142
+ return {
39143
+ eventSelection: getEventSelectionMode(scopedEvents),
39144
+ booksWholeSeries: bookedEvent?.registrationSubject.kind === "wholeSeries"
39145
+ };
39146
+ }
39147
+ /** One label owner for the live progress and the server placeholder. */
39148
+ function registrationStepLabel(step, context) {
39149
+ switch (step) {
39150
+ case "select-event": return context.eventSelection === "date" ? "Date" : "Event";
39151
+ case "select-tickets": return context.booksWholeSeries ? "Places" : "Tickets";
39152
+ case "enter-details": return "Your details";
39153
+ case "select-payment": return "How you’ll pay";
39154
+ case "payment": return "Payment";
39155
+ }
39156
+ }
39157
+ function bookingButtonText(buttonText) {
39158
+ return buttonText?.trim() || "Book your place";
39159
+ }
39160
+ function placesLabel(event, input, compact) {
39161
+ return placesLeftLabel(selectEventCapacitySource(event, input.attendanceMode), input.display, { compact });
39162
+ }
39163
+ function isFull(event, mode) {
39164
+ return formatRemainingPlaces(selectEventCapacitySource(event, mode), {
39165
+ full: "full",
39166
+ waitlist: "full"
39167
+ }) === "full";
39168
+ }
39169
+ function priceLabel(event) {
39170
+ return formatEventPriceDisplay(event.pricing).trim() || null;
39171
+ }
39172
+ /** The lowest price a display can mean; null when it names no paid amount. */
39173
+ function lowestPaidAmount(display) {
39174
+ switch (display.kind) {
39175
+ case "exact":
39176
+ case "from": return display.amount;
39177
+ case "range": return display.minAmount;
39178
+ case "mixed": return display.paidFromAmount;
39179
+ case "free":
39180
+ case "unavailable":
39181
+ case "external": return null;
39182
+ }
39183
+ }
39184
+ /**
39185
+ * One price for a set of dates: the shared label when every date costs the
39186
+ * same, otherwise "From" the lowest paid amount, through the one formatter.
39187
+ */
39188
+ function seriesPriceLabel(events) {
39189
+ const firstEvent = events[0];
39190
+ if (!firstEvent) return null;
39191
+ const displays = events.map((event) => publicEventPricingDisplay(event.pricing));
39192
+ const labels = new Set(displays.map((display) => formatBookingPriceDisplay(display, { decimals: "auto" })));
39193
+ if (labels.size === 1) return [...labels][0] || null;
39194
+ const lowest = displays.flatMap((display) => {
39195
+ const amount = lowestPaidAmount(display);
39196
+ return amount === null || !("currency" in display) ? [] : [{
39197
+ amount,
39198
+ currency: display.currency
39199
+ }];
39200
+ }).reduce((best, candidate) => best === null || candidate.amount < best.amount ? candidate : best, null);
39201
+ return lowest ? formatBookingPriceDisplay({
39202
+ kind: "from",
39203
+ amount: lowest.amount,
39204
+ currency: lowest.currency
39205
+ }, { decimals: "auto" }) : priceLabel(firstEvent);
39206
+ }
39207
+ function dateTile(event) {
39208
+ if (event.registrationSubject.kind === "wholeSeries" || !event.startsAt) return null;
39209
+ const rail = formatEventCompactDateRail(event.startsAt, { timeZone: event.timeZone });
39210
+ return {
39211
+ month: rail.month,
39212
+ day: rail.day
39213
+ };
39214
+ }
39215
+ /** "Mon 5 Oct": the compact date the chips and the bar share. */
39216
+ function formatBookingShortDate(event) {
39217
+ return formatBookingWhen(event, "day");
39218
+ }
39219
+ function byStart(left, right) {
39220
+ return Date.parse(left.startsAt) - Date.parse(right.startsAt);
39221
+ }
39222
+ function singleEntry(event, input) {
39223
+ const isCourse = event.registrationSubject.kind === "wholeSeries";
39224
+ return {
39225
+ kind: "single",
39226
+ title: event.title,
39227
+ when: formatRegistrationSchedule(event, null, "long"),
39228
+ shortWhen: formatRegistrationSchedule(event),
39229
+ venue: shownVenue(event, input.display),
39230
+ price: priceLabel(event),
39231
+ places: placesLabel(event, input, false),
39232
+ tile: dateTile(event),
39233
+ action: isCourse ? `Book the ${input.noun.label}` : bookingButtonText(input.buttonText)
39234
+ };
39235
+ }
39236
+ function chooseDateEntry(input) {
39237
+ const ordered = [...input.events].sort(byStart);
39238
+ const first = ordered[0];
39239
+ const shown = ordered.slice(0, 4);
39240
+ return {
39241
+ kind: "chooseDate",
39242
+ title: first?.title ?? "",
39243
+ schedule: (first ? formatBookingScheduleSummary(first) : null) ?? (ordered.length === 1 ? "1 date" : `${ordered.length} dates`),
39244
+ venue: first ? shownVenue(first, input.display) : null,
39245
+ price: seriesPriceLabel(ordered),
39246
+ nextDate: first ? formatBookingShortDate(first) : null,
39247
+ dateCount: ordered.length,
39248
+ dates: shown.map((event) => ({
39249
+ selectionId: event.selectionId,
39250
+ label: formatBookingShortDate(event),
39251
+ places: placesLabel(event, input, true),
39252
+ full: isFull(event, input.attendanceMode)
39253
+ })),
39254
+ moreCount: Math.max(0, ordered.length - shown.length),
39255
+ action: "Choose a date"
39256
+ };
39257
+ }
39258
+ /** The card before a booking starts; the server renders this too. */
39259
+ function idleBookingEntry(input) {
39260
+ if (input.event) return singleEntry(input.event, input);
39261
+ const only = input.events.length === 1 ? input.events[0] : void 0;
39262
+ if (only) return singleEntry(only, input);
39263
+ if (getEventSelectionMode(input.events) === "date") return chooseDateEntry(input);
39264
+ return {
39265
+ kind: "chooseEvent",
39266
+ title: input.events[0]?.title ?? "",
39267
+ count: input.events.length,
39268
+ noun: input.noun.pluralLabel,
39269
+ action: "Choose an event"
39270
+ };
39271
+ }
39272
+ //#endregion
39273
+ //#region ../blocks/src/system/blocks/offerings-preview/model.ts
39274
+ const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
39275
+ const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
39276
+ const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
39277
+ {
39278
+ preset: "class",
39279
+ sourceKey: "eventPreset:class",
39280
+ dataKey: "classEvents",
39281
+ label: "Classes"
39282
+ },
39283
+ {
39284
+ preset: "workshop",
39285
+ sourceKey: "eventPreset:workshop",
39286
+ dataKey: "workshopEvents",
39287
+ label: "Workshops"
39288
+ },
39289
+ {
39290
+ preset: "retreat",
39291
+ sourceKey: "eventPreset:retreat",
39292
+ dataKey: "retreatEvents",
39293
+ label: "Retreats"
39294
+ },
39295
+ {
39296
+ preset: "performance",
39297
+ sourceKey: "eventPreset:performance",
39298
+ dataKey: "performanceEvents",
39299
+ label: "Performances"
39300
+ }
39301
+ ];
39302
+ const OFFERINGS_PREVIEW_EVENT_PRESETS = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
39303
+ const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
39304
+ const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
39305
+ Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
39306
+ kind: "eventPreset",
39307
+ preset: definition.preset
39308
+ }]));
39309
+ function isOfferingsPreviewSourceKey(value) {
39310
+ return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
39311
+ }
39312
+ function normalizeOfferingsPreviewGroupOrder(value) {
39313
+ if (value == null) return { kind: "legacy" };
39314
+ if (value.length === 0) return { kind: "default" };
39315
+ const keys = uniqueOfferingsPreviewSourceKeys(value);
39316
+ return keys.length > 0 ? {
39317
+ kind: "custom",
39318
+ keys
39319
+ } : { kind: "default" };
39320
+ }
39321
+ function inferLegacyOfferingsPreviewGroupOrder(overrides) {
39322
+ const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
39323
+ return keys.length >= 2 ? keys : [];
39324
+ }
39325
+ function normalizeOfferingsPreviewContentForWrite(value) {
39326
+ if (!isRecord$6(value)) return null;
39327
+ const groupOrderValue = value.groupOrder;
39328
+ if (Array.isArray(groupOrderValue)) {
39329
+ const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
39330
+ return {
39331
+ ...value,
39332
+ groupOrder: normalized.kind === "custom" ? normalized.keys : []
39333
+ };
39334
+ }
39335
+ if (groupOrderValue != null) return {
39336
+ ...value,
39337
+ groupOrder: []
39338
+ };
39339
+ const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$6) : []);
39340
+ return legacyOrder.length > 0 ? {
39341
+ ...value,
39342
+ groupOrder: legacyOrder
39343
+ } : value;
39344
+ }
39345
+ /** Which offerings a site preset row names; one owner for "the course preset means whole-series runs". */
39346
+ function sourceFromPresetSummary(preset) {
39347
+ if (preset.engine === "event_series" && preset.canonicalBehaviour === "course") return { kind: "wholeSeries" };
39348
+ if (preset.engine !== "event_series") return null;
39349
+ if (isOfferingsPreviewEventPreset(preset.canonicalBehaviour)) return {
39350
+ kind: "eventPreset",
39351
+ preset: preset.canonicalBehaviour
39352
+ };
39353
+ return null;
39354
+ }
39355
+ function uniqueOfferingsPreviewSourceKeys(values) {
39356
+ const keys = [];
39357
+ for (const value of values) {
39358
+ if (!isOfferingsPreviewSourceKey(value)) continue;
39359
+ if (!keys.includes(value)) keys.push(value);
39360
+ }
39361
+ return keys;
39362
+ }
39363
+ function isRecord$6(value) {
39364
+ return typeof value === "object" && value !== null && !Array.isArray(value);
39365
+ }
39366
+ function isOfferingsPreviewEventPreset(value) {
39367
+ return typeof value === "string" && OFFERINGS_PREVIEW_EVENT_PRESETS.includes(value);
39368
+ }
39369
+ //#endregion
39370
+ //#region ../blocks/src/system/runtime/nodes/event-registration/offeringNoun.ts
39371
+ function offeringKindForEvent(event) {
39372
+ return event.registrationSubject.kind === "wholeSeries" ? "course" : event.eventPreset ?? "event";
39373
+ }
39374
+ function presetKind(preset) {
39375
+ const source = sourceFromPresetSummary(preset);
39376
+ if (!source) return null;
39377
+ return source.kind === "wholeSeries" ? "course" : source.preset;
39378
+ }
39379
+ /**
39380
+ * What the site calls this kind of offering, in lower case for use mid-sentence
39381
+ * ("Book the course", "3 workshops"). Site preset labels win; the canonical
39382
+ * name stands in when the site has none.
39383
+ */
39384
+ function offeringNounForEvent(event, presets) {
39385
+ if (!event) return lowerCased(EVENT_PRESET_VOCABULARY.event);
39386
+ const kind = offeringKindForEvent(event);
39387
+ const preset = presets?.find((candidate) => presetKind(candidate) === kind);
39388
+ return lowerCased(preset?.label.trim() && preset.pluralLabel.trim() ? preset : EVENT_PRESET_VOCABULARY[kind]);
39389
+ }
39390
+ function lowerCased(noun) {
39391
+ return {
39392
+ label: noun.label.trim().toLocaleLowerCase(),
39393
+ pluralLabel: noun.pluralLabel.trim().toLocaleLowerCase()
39394
+ };
39395
+ }
39396
+ //#endregion
38581
39397
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
38582
39398
  function toRegistrationEvent(event) {
38583
39399
  return {
@@ -38622,6 +39438,181 @@ function toWholeSeriesRegistrationEvents(events) {
38622
39438
  return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
38623
39439
  }
38624
39440
  //#endregion
39441
+ //#region ../blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.ts
39442
+ /**
39443
+ * The booking card the server renders on CMS pages (issue 1581), and the
39444
+ * offering noun it resolves for the wizard. Kept beside the island contract
39445
+ * so the server and the client agree on one card.
39446
+ */
39447
+ const bookingHostSchema = z.enum(["overlay", "inPlace"]);
39448
+ const tileSchema = z.object({
39449
+ month: z.string(),
39450
+ day: z.string()
39451
+ }).nullable();
39452
+ const idleBookingEntrySchema = z.discriminatedUnion("kind", [
39453
+ z.object({
39454
+ kind: z.literal("single"),
39455
+ title: z.string(),
39456
+ when: z.string().nullable(),
39457
+ shortWhen: z.string().nullable(),
39458
+ venue: z.string().nullable(),
39459
+ price: z.string().nullable(),
39460
+ places: z.string().nullable(),
39461
+ tile: tileSchema,
39462
+ action: z.string()
39463
+ }),
39464
+ z.object({
39465
+ kind: z.literal("chooseDate"),
39466
+ title: z.string(),
39467
+ schedule: z.string(),
39468
+ venue: z.string().nullable(),
39469
+ price: z.string().nullable(),
39470
+ nextDate: z.string().nullable(),
39471
+ dateCount: z.number(),
39472
+ dates: z.array(z.object({
39473
+ selectionId: z.string(),
39474
+ label: z.string(),
39475
+ places: z.string().nullable(),
39476
+ full: z.boolean()
39477
+ })),
39478
+ moreCount: z.number(),
39479
+ action: z.string()
39480
+ }),
39481
+ z.object({
39482
+ kind: z.literal("chooseEvent"),
39483
+ title: z.string(),
39484
+ count: z.number(),
39485
+ noun: z.string(),
39486
+ action: z.string()
39487
+ })
39488
+ ]);
39489
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
39490
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
39491
+ }
39492
+ function scopedRegistrationEvents(source, scopedSourceEvents) {
39493
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
39494
+ return {
39495
+ scopedEvents,
39496
+ preSelected: findPreSelectedEvent({
39497
+ scopedEvents,
39498
+ occurrenceContext: source.occurrenceContext
39499
+ })
39500
+ };
39501
+ }
39502
+ function nounFor(scoped, presets) {
39503
+ return offeringNounForEvent(scoped.preSelected ?? scoped.scopedEvents[0] ?? null, presets);
39504
+ }
39505
+ /** What the site calls the offering this block books, from its preset rows. */
39506
+ function resolveOfferingNoun(source, scopedSourceEvents) {
39507
+ return nounFor(scopedRegistrationEvents(source, scopedSourceEvents), source.bookingOfferingPresets);
39508
+ }
39509
+ function idleEntry(source, scopedSourceEvents, display) {
39510
+ const scoped = scopedRegistrationEvents(source, scopedSourceEvents);
39511
+ if (scoped.scopedEvents.length === 0) return null;
39512
+ return idleBookingEntry({
39513
+ events: scoped.scopedEvents,
39514
+ event: withOccurrenceTitle(scoped.preSelected, source.occurrenceContext),
39515
+ display,
39516
+ buttonText: source.buttonText,
39517
+ noun: nounFor(scoped, source.bookingOfferingPresets),
39518
+ attendanceMode: null
39519
+ });
39520
+ }
39521
+ /** The server's card on CMS pages, or null where the shell still applies. */
39522
+ function bookingCardDisplay(source, scopedSourceEvents, display) {
39523
+ if ((source.host ?? "overlay") !== "overlay") return null;
39524
+ const entry = idleEntry(source, scopedSourceEvents, display);
39525
+ if (!entry) return null;
39526
+ return {
39527
+ kind: "event-registration",
39528
+ state: "booking-card",
39529
+ className: source.className ?? null,
39530
+ entry,
39531
+ buttonVariant: source.buttonVariant ?? null,
39532
+ buttonText: source.buttonText?.trim() ?? ""
39533
+ };
39534
+ }
39535
+ const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
39536
+ kind: z.literal("unsupported"),
39537
+ reason: z.enum([
39538
+ "unsupportedSubject",
39539
+ "invalidEventSeriesId",
39540
+ "invalidEventSeriesBookingScope",
39541
+ "invalidEventOfferingId",
39542
+ "invalidEventOfferingBookingScope",
39543
+ "invalidEventOfferingCatalogue"
39544
+ ])
39545
+ });
39546
+ const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
39547
+ const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
39548
+ if (unsupported.success) return unsupported.data;
39549
+ const parsed = parseEventPageSurfaceSubject(value);
39550
+ if (parsed.ok) return parsed.value;
39551
+ ctx.addIssue({
39552
+ code: z.ZodIssueCode.custom,
39553
+ message: `Invalid event page-surface subject (${parsed.error.kind})`
39554
+ });
39555
+ return z.NEVER;
39556
+ });
39557
+ function resolveEventRegistrationScope(context) {
39558
+ const occurrenceSeriesId = context.occurrenceContext?.seriesId;
39559
+ if (occurrenceSeriesId) return {
39560
+ kind: "occurrenceSeries",
39561
+ seriesId: occurrenceSeriesId
39562
+ };
39563
+ if (context.subject?.kind === "eventSeries") return {
39564
+ kind: "pageSurfaceSeries",
39565
+ seriesId: context.subject.eventSeriesId
39566
+ };
39567
+ if (context.subject?.kind === "eventOffering") return {
39568
+ kind: "pageSurfaceOffering",
39569
+ offeringId: context.subject.eventOfferingId,
39570
+ seriesIds: context.subject.catalogueEventSeriesIds
39571
+ };
39572
+ if (context.subject?.kind === "unsupported") return {
39573
+ kind: "unsupportedPageSurfaceSubject",
39574
+ reason: context.subject.reason
39575
+ };
39576
+ return { kind: "unscoped" };
39577
+ }
39578
+ function resolveEventRegistrationScopedEvents(events, context) {
39579
+ const scope = resolveEventRegistrationScope(context);
39580
+ switch (scope.kind) {
39581
+ case "occurrenceSeries":
39582
+ case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
39583
+ case "pageSurfaceOffering": {
39584
+ const seriesIds = new Set(scope.seriesIds);
39585
+ return events.filter((event) => seriesIds.has(event.seriesId));
39586
+ }
39587
+ case "unsupportedPageSurfaceSubject": return [];
39588
+ case "unscoped": return [...events];
39589
+ }
39590
+ }
39591
+ //#endregion
39592
+ //#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
39593
+ const previewStageSchema = z.enum(["preview", "published"]);
39594
+ const registrationButtonVariantSchema = blockButtonRoleSchema;
39595
+ const eventButtonVariantSchema = blockButtonRoleSchema;
39596
+ const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
39597
+ const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
39598
+ const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
39599
+ const columnOptionSchema = z.enum([
39600
+ "2",
39601
+ "3",
39602
+ "4"
39603
+ ]);
39604
+ const weekStartSchema = z.enum(["sunday", "monday"]);
39605
+ const filterAutoShowSchema = z.enum([
39606
+ "auto",
39607
+ "always",
39608
+ "never"
39609
+ ]);
39610
+ const calendarTabViewsSchema = z.enum([
39611
+ "both",
39612
+ "month",
39613
+ "week"
39614
+ ]);
39615
+ //#endregion
38625
39616
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.ts
38626
39617
  const { limit: _rawLimit, eventSurface: _rawEventSurface, scheduleScope: _rawScheduleScope, detailLevel: _rawDetailLevel, ...stringEventQueryShape } = publicEventLoaderQuerySchemaShape;
38627
39618
  const eventRecoveryRequestSchema = z.object({
@@ -38664,27 +39655,17 @@ function parseEventLoaderRecoveryRequest(value) {
38664
39655
  const EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS = [
38665
39656
  "Date",
38666
39657
  "Tickets",
38667
- "Details"
38668
- ];
38669
- const EVENT_REGISTRATION_SHELL_STEP_LABELS = [
38670
- "Event",
38671
- "Date",
38672
- "Tickets",
38673
- "Details",
38674
- "Payment options",
38675
- "Payment"
39658
+ "Your details"
38676
39659
  ];
38677
39660
  const eventRegistrationPublicEventSchema = z.preprocess(normalizeEventRegistrationEventInput, publicEventSchema);
38678
- const eventRegistrationHydrationSchemaShape = {
39661
+ const eventRegistrationCommonSchemaShape = {
38679
39662
  demoEmailSuppressed: z.boolean().optional(),
38680
39663
  occurrenceContext: occurrenceContextSchema.optional(),
38681
39664
  subject: eventRegistrationPageSurfaceSubjectSchema.nullable().optional(),
38682
39665
  maxTickets: z.string().optional(),
38683
- showVenue: z.boolean().optional(),
38684
- showCapacity: z.boolean().optional(),
38685
39666
  successMessage: z.string().optional(),
38686
39667
  buttonVariant: registrationButtonVariantSchema.optional(),
38687
- overlayEnabled: z.boolean().optional(),
39668
+ host: bookingHostSchema.optional(),
38688
39669
  spamProtectionEnabled: z.boolean().optional(),
38689
39670
  supportEmail: z.string().optional(),
38690
39671
  sitePasses: publicPassProductCollectionSchema.nullable().optional(),
@@ -38709,32 +39690,51 @@ const eventRegistrationSourceSchema = z.object({
38709
39690
  portalToken: z.string().nullable().optional(),
38710
39691
  previewToken: z.string().nullable().optional(),
38711
39692
  readAuthority: browserReadAuthoritySchema.optional(),
38712
- ...eventRegistrationHydrationSchemaShape,
39693
+ ...eventRegistrationCommonSchemaShape,
39694
+ showVenue: z.boolean().optional(),
39695
+ showCapacity: z.boolean().optional(),
38713
39696
  events: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
38714
39697
  wholeSeriesRunEvents: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
38715
39698
  eventLoaderFailures: z.record(z.string(), z.unknown()).nullable().optional(),
38716
39699
  className: z.string().nullable().optional(),
38717
39700
  buttonText: z.string().nullable().optional(),
38718
39701
  maxTickets: z.string().nullable().optional(),
38719
- stage: previewStageSchema.optional()
39702
+ stage: previewStageSchema.optional(),
39703
+ bookingOfferingPresets: z.array(z.custom((value) => typeof value === "object" && value !== null && typeof value.label === "string")).nullable().optional()
38720
39704
  });
38721
- const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [z.object({
38722
- kind: z.literal("event-registration"),
38723
- state: z.literal("generic-shell"),
38724
- className: z.string().nullable(),
38725
- titleText: z.string(),
38726
- bodyText: z.string(),
38727
- stepLabels: z.array(z.enum(EVENT_REGISTRATION_SHELL_STEP_LABELS)).min(1),
38728
- summaryButtonText: z.string()
38729
- }), z.object({
38730
- kind: z.literal("event-registration"),
38731
- state: z.literal("registration-closed"),
38732
- className: z.string().nullable(),
38733
- titleText: z.string(),
38734
- bodyText: z.string()
38735
- })]);
39705
+ const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [
39706
+ z.object({
39707
+ kind: z.literal("event-registration"),
39708
+ state: z.literal("generic-shell"),
39709
+ className: z.string().nullable(),
39710
+ titleText: z.string(),
39711
+ bodyText: z.string(),
39712
+ stepLabels: z.array(z.enum(REGISTRATION_STEP_LABELS)).min(1),
39713
+ summaryButtonText: z.string()
39714
+ }),
39715
+ z.object({
39716
+ kind: z.literal("event-registration"),
39717
+ state: z.literal("registration-closed"),
39718
+ className: z.string().nullable(),
39719
+ titleText: z.string(),
39720
+ bodyText: z.string()
39721
+ }),
39722
+ z.object({
39723
+ kind: z.literal("event-registration"),
39724
+ state: z.literal("booking-card"),
39725
+ className: z.string().nullable(),
39726
+ entry: idleBookingEntrySchema,
39727
+ buttonVariant: registrationButtonVariantSchema.nullable(),
39728
+ buttonText: z.string()
39729
+ })
39730
+ ]);
38736
39731
  const eventRegistrationHydrationSchema = z.object({
38737
- ...eventRegistrationHydrationSchemaShape,
39732
+ ...eventRegistrationCommonSchemaShape,
39733
+ display: registrationDisplaySchema,
39734
+ offeringNoun: z.object({
39735
+ label: z.string(),
39736
+ pluralLabel: z.string()
39737
+ }).optional(),
38738
39738
  eventSource: eventRegistrationEventSourceSchema
38739
39739
  });
38740
39740
  const eventRegistrationRenderSchema = z.object({
@@ -38749,7 +39749,7 @@ z.object({
38749
39749
  readAuthority: browserReadAuthoritySchema.optional(),
38750
39750
  stage: previewStageSchema.optional()
38751
39751
  });
38752
- function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
39752
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display) {
38753
39753
  const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
38754
39754
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
38755
39755
  kind: "event-registration",
@@ -38758,6 +39758,8 @@ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvent
38758
39758
  titleText: "Registration closed",
38759
39759
  bodyText: "This run is no longer open for bookings."
38760
39760
  };
39761
+ const card = bookingCardDisplay(source, scopedSourceEvents, display);
39762
+ if (card) return card;
38761
39763
  const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
38762
39764
  return {
38763
39765
  kind: "event-registration",
@@ -38774,11 +39776,14 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
38774
39776
  if (selectedRun) {
38775
39777
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
38776
39778
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
38777
- return getWizardStepsForContext(true, resolveEventRequiresPayment({
38778
- eligibility: null,
38779
- selectedTicketType: null,
38780
- event: selectedEvent
38781
- })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
39779
+ return shellStepLabels(registrationRoute({
39780
+ chooseEvent: false,
39781
+ payment: resolveEventRequiresPayment({
39782
+ eligibility: null,
39783
+ selectedTicketType: null,
39784
+ event: selectedEvent
39785
+ }) ? "optionsAndPayment" : "none"
39786
+ }), registrationStepLabelContext([selectedEvent], selectedEvent));
38782
39787
  }
38783
39788
  const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
38784
39789
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -38787,14 +39792,15 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
38787
39792
  occurrenceContext: source.occurrenceContext
38788
39793
  });
38789
39794
  const hasPreSelectedContext = Boolean(source.occurrenceContext || preSelectedEvent);
38790
- return getWizardStepsForContext(hasPreSelectedContext, hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
39795
+ const needsPaymentStep = hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
38791
39796
  eligibility: null,
38792
39797
  selectedTicketType: null,
38793
39798
  event: preSelectedEvent
38794
- }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
38795
- }
38796
- function toScopedEventRegistrationEvents(scopedSourceEvents) {
38797
- return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
39799
+ }) : false;
39800
+ return shellStepLabels(registrationRoute({
39801
+ chooseEvent: !hasPreSelectedContext,
39802
+ payment: needsPaymentStep ? "optionsAndPayment" : "none"
39803
+ }), registrationStepLabelContext(scopedEvents, preSelectedEvent));
38798
39804
  }
38799
39805
  function scopeEventRegistrationSourceEvents(source, eventSource) {
38800
39806
  return resolveEventRegistrationScopedEvents(eventSource.kind === "loaded" ? eventSource.events : [], {
@@ -38810,29 +39816,12 @@ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
38810
39816
  const runs = groupPublicEventsByRun(scopedSourceEvents);
38811
39817
  return runs.length === 1 ? runs[0] : null;
38812
39818
  }
38813
- function shellLabelForRegistrationStep(step, events) {
38814
- switch (step) {
38815
- case "select-event": return isDateSelection(events) ? "Date" : "Event";
38816
- case "select-tickets": return "Tickets";
38817
- case "enter-details": return "Details";
38818
- case "select-payment": return "Payment options";
38819
- case "payment": return "Payment";
38820
- case "error":
38821
- case "success":
38822
- case "submitting":
38823
- case "verifying-payment":
38824
- case "verifying-timeout":
38825
- case "payment-status":
38826
- case "payment-failed": return "Details";
38827
- }
38828
- }
38829
- function isDateSelection(events) {
38830
- const firstSeriesId = events[0]?.seriesId;
38831
- return Boolean(events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId));
39819
+ function shellStepLabels(route, context) {
39820
+ return route.map((step) => registrationStepLabel(step, context));
38832
39821
  }
38833
39822
  function buildEventRegistrationShellBodyText(stepLabels) {
38834
- if (stepLabels.includes("Payment options")) return "Preparing tickets, details, and payment options.";
38835
- if (stepLabels[0] === "Tickets") return "Preparing tickets and booking details.";
39823
+ if (stepLabels.includes("How you’ll pay")) return "Preparing tickets, details, and payment options.";
39824
+ if (stepLabels[0] === "Tickets" || stepLabels[0] === "Places") return "Preparing tickets and booking details.";
38836
39825
  return "Preparing dates, tickets, and booking details.";
38837
39826
  }
38838
39827
  const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
@@ -38842,6 +39831,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
38842
39831
  render: createZodCodec(eventRegistrationRenderSchema)
38843
39832
  },
38844
39833
  buildRender: (source) => {
39834
+ const display = resolveRegistrationDisplay(source);
38845
39835
  const eventSource = resolveEventRegistrationEventSource({
38846
39836
  events: source.events,
38847
39837
  wholeSeriesRunEvents: source.wholeSeriesRunEvents,
@@ -38849,7 +39839,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
38849
39839
  });
38850
39840
  const scopedSourceEvents = scopeEventRegistrationSourceEvents(source, eventSource);
38851
39841
  return {
38852
- display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
39842
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display),
38853
39843
  hydration: {
38854
39844
  occurrenceContext: source.occurrenceContext ?? null,
38855
39845
  subject: source.subject ?? null,
@@ -38859,29 +39849,29 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
38859
39849
  } : eventSource,
38860
39850
  demoEmailSuppressed: source.demoEmailSuppressed,
38861
39851
  maxTickets: source.maxTickets ?? void 0,
38862
- showVenue: source.showVenue,
38863
- showCapacity: source.showCapacity,
39852
+ display,
38864
39853
  successMessage: source.successMessage,
38865
39854
  buttonVariant: source.buttonVariant,
38866
- overlayEnabled: source.overlayEnabled,
39855
+ host: source.host ?? "overlay",
38867
39856
  spamProtectionEnabled: source.spamProtectionEnabled,
38868
39857
  supportEmail: source.supportEmail,
38869
39858
  sitePasses: source.sitePasses ?? null,
38870
39859
  siteMemberships: source.siteMemberships ?? null,
38871
39860
  mailingList: source.mailingList,
38872
- siteName: source.siteName
39861
+ siteName: source.siteName,
39862
+ offeringNoun: resolveOfferingNoun(source, scopedSourceEvents)
38873
39863
  }
38874
39864
  };
38875
39865
  }
38876
39866
  });
38877
39867
  function normalizeEventRegistrationEventInput(value) {
38878
- if (!isRecord$6(value)) return value;
39868
+ if (!isRecord$5(value)) return value;
38879
39869
  return {
38880
39870
  ...value,
38881
39871
  destination: resolveRuntimeEventDestination(value)
38882
39872
  };
38883
39873
  }
38884
- function isRecord$6(value) {
39874
+ function isRecord$5(value) {
38885
39875
  return typeof value === "object" && value !== null && !Array.isArray(value);
38886
39876
  }
38887
39877
  const eventRegistrationBlockDefinition = {
@@ -38921,7 +39911,7 @@ const eventRegistrationBlockDefinition = {
38921
39911
  description: "Main heading for the registration form",
38922
39912
  required: false,
38923
39913
  multiline: false,
38924
- defaultValue: "Register for this event"
39914
+ defaultValue: ""
38925
39915
  },
38926
39916
  {
38927
39917
  id: "description",
@@ -38993,11 +39983,11 @@ const eventRegistrationBlockDefinition = {
38993
39983
  {
38994
39984
  id: "buttonText",
38995
39985
  type: "text",
38996
- label: "Submit button text",
38997
- description: "Text for the registration submit button",
39986
+ label: "Button text",
39987
+ description: "Text on the booking button",
38998
39988
  required: false,
38999
39989
  multiline: false,
39000
- defaultValue: "Complete Registration",
39990
+ defaultValue: "Book your place",
39001
39991
  maxLength: 40
39002
39992
  },
39003
39993
  createButtonVariantField()
@@ -39029,21 +40019,21 @@ const eventRegistrationBlockDefinition = {
39029
40019
  successMessage: { $bind: { from: "content.successMessage" } },
39030
40020
  buttonText: { $bind: {
39031
40021
  from: "content.buttonText",
39032
- fallback: "Complete Registration"
40022
+ fallback: "Book your place"
39033
40023
  } },
39034
40024
  buttonVariant: { $bind: {
39035
40025
  from: "content.buttonVariant",
39036
40026
  fallback: "primary"
39037
40027
  } },
39038
- overlayEnabled: { $bind: {
39039
- from: "$root.eventRegistrationOverlayEnabled",
39040
- fallback: false
39041
- } },
39042
40028
  events: { $bind: { from: "data.events" } },
39043
40029
  wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
39044
40030
  loaderFailures: { $bind: { from: "$root.loaderFailures" } },
39045
40031
  mailingList: { $bind: { from: "$root.mailingList" } },
39046
40032
  siteName: { $bind: { from: "$root.siteName" } },
40033
+ bookingOfferingPresets: { $bind: {
40034
+ from: "$root.bookingOfferingPresets",
40035
+ fallback: []
40036
+ } },
39047
40037
  sitePasses: { $bind: { from: "data.sitePasses" } },
39048
40038
  siteMemberships: { $bind: { from: "data.siteMemberships" } },
39049
40039
  stage: { $bind: {
@@ -41613,7 +42603,21 @@ const eventCalendarBlockDefinition = {
41613
42603
  list: { label: "List" }
41614
42604
  }
41615
42605
  }, eventCalendarContentShape),
41616
- normalizeContent: normalizeEventCalendarContent,
42606
+ contentNormalizer: {
42607
+ fieldIds: [
42608
+ "variant",
42609
+ "displayMode",
42610
+ "listLayout",
42611
+ "defaultCalendarView",
42612
+ "eventSurface",
42613
+ "filterEventPresets",
42614
+ "scheduleScope",
42615
+ "filterCategoryIds",
42616
+ "filterStaffMemberId",
42617
+ "filterVenueId"
42618
+ ],
42619
+ normalize: normalizeEventCalendarContent
42620
+ },
41617
42621
  dataSchemas: {
41618
42622
  events: publicEventsArraySchema.optional(),
41619
42623
  calendarWindow: calendarWindowPageSchema.optional()
@@ -42086,90 +43090,6 @@ const recurringScheduleBlockDefinition = {
42086
43090
  } }
42087
43091
  };
42088
43092
  //#endregion
42089
- //#region ../blocks/src/system/blocks/offerings-preview/model.ts
42090
- const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
42091
- const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
42092
- const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
42093
- {
42094
- preset: "class",
42095
- sourceKey: "eventPreset:class",
42096
- dataKey: "classEvents",
42097
- label: "Classes"
42098
- },
42099
- {
42100
- preset: "workshop",
42101
- sourceKey: "eventPreset:workshop",
42102
- dataKey: "workshopEvents",
42103
- label: "Workshops"
42104
- },
42105
- {
42106
- preset: "retreat",
42107
- sourceKey: "eventPreset:retreat",
42108
- dataKey: "retreatEvents",
42109
- label: "Retreats"
42110
- },
42111
- {
42112
- preset: "performance",
42113
- sourceKey: "eventPreset:performance",
42114
- dataKey: "performanceEvents",
42115
- label: "Performances"
42116
- }
42117
- ];
42118
- OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
42119
- const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
42120
- const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
42121
- Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
42122
- kind: "eventPreset",
42123
- preset: definition.preset
42124
- }]));
42125
- function isOfferingsPreviewSourceKey(value) {
42126
- return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
42127
- }
42128
- function normalizeOfferingsPreviewGroupOrder(value) {
42129
- if (value == null) return { kind: "legacy" };
42130
- if (value.length === 0) return { kind: "default" };
42131
- const keys = uniqueOfferingsPreviewSourceKeys(value);
42132
- return keys.length > 0 ? {
42133
- kind: "custom",
42134
- keys
42135
- } : { kind: "default" };
42136
- }
42137
- function inferLegacyOfferingsPreviewGroupOrder(overrides) {
42138
- const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
42139
- return keys.length >= 2 ? keys : [];
42140
- }
42141
- function normalizeOfferingsPreviewContentForWrite(value) {
42142
- if (!isRecord$5(value)) return null;
42143
- const groupOrderValue = value.groupOrder;
42144
- if (Array.isArray(groupOrderValue)) {
42145
- const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
42146
- return {
42147
- ...value,
42148
- groupOrder: normalized.kind === "custom" ? normalized.keys : []
42149
- };
42150
- }
42151
- if (groupOrderValue != null) return {
42152
- ...value,
42153
- groupOrder: []
42154
- };
42155
- const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$5) : []);
42156
- return legacyOrder.length > 0 ? {
42157
- ...value,
42158
- groupOrder: legacyOrder
42159
- } : value;
42160
- }
42161
- function uniqueOfferingsPreviewSourceKeys(values) {
42162
- const keys = [];
42163
- for (const value of values) {
42164
- if (!isOfferingsPreviewSourceKey(value)) continue;
42165
- if (!keys.includes(value)) keys.push(value);
42166
- }
42167
- return keys;
42168
- }
42169
- function isRecord$5(value) {
42170
- return typeof value === "object" && value !== null && !Array.isArray(value);
42171
- }
42172
- //#endregion
42173
43093
  //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
42174
43094
  const groupOverrideFields = [
42175
43095
  {
@@ -42533,7 +43453,22 @@ const offeringsPreviewEventDataSchemas = Object.fromEntries(OFFERINGS_PREVIEW_EV
42533
43453
  const offeringsPreviewEventDataLoaders = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, eventPresetLoader(definition.preset)]));
42534
43454
  const offeringsPreviewBlockDefinition = {
42535
43455
  manifest: offeringsPreviewManifest,
42536
- normalizeContent: normalizeOfferingsPreviewBlockContent,
43456
+ contentNormalizer: {
43457
+ fieldIds: [
43458
+ "displayMode",
43459
+ "heading",
43460
+ "description",
43461
+ "itemLimit",
43462
+ "groupOrder",
43463
+ "groupOverrides",
43464
+ "_cardStyles",
43465
+ "buttonVariant",
43466
+ "buttonText",
43467
+ "emptyMessage",
43468
+ "_sectionStyles"
43469
+ ],
43470
+ normalize: normalizeOfferingsPreviewBlockContent
43471
+ },
42537
43472
  dataSchemas: {
42538
43473
  ...offeringsPreviewEventDataSchemas,
42539
43474
  routeCandidates: publicRouteCandidatesSchema.optional(),
@@ -46131,11 +47066,11 @@ function buildNewsletterDisplay(source) {
46131
47066
  successMessage: DEFAULT_MAILING_LIST_SUCCESS_MESSAGE,
46132
47067
  privacyNote: null
46133
47068
  };
46134
- const targeting = source.targeting ?? { mode: "default" };
46135
- const choices = resolveRuntimeChoices(context, targeting);
47069
+ const assessment = assessMailingListTargeting(context, source.targeting ?? { mode: "default" });
47070
+ const choices = resolveRuntimeChoices(assessment);
46136
47071
  return {
46137
47072
  kind: "newsletter-form",
46138
- availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
47073
+ availability: assessment.tag === "available" ? "ready" : "unavailable",
46139
47074
  className: source.className ?? null,
46140
47075
  buttonLabel: form.buttonLabel,
46141
47076
  successMessage: form.successMessage,
@@ -46192,60 +47127,12 @@ function projectNewsletterFormHydrationTarget(source) {
46192
47127
  default: return assertNever$3(source);
46193
47128
  }
46194
47129
  }
46195
- function resolveRuntimeChoices(context, targeting) {
46196
- if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
46197
- switch (targeting.provider) {
46198
- case "native": {
46199
- const offered = new Set(targeting.selection.offeredTopicIds);
46200
- return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
46201
- id: item.id,
46202
- label: item.name
46203
- })) : [];
46204
- }
46205
- case "brevo": {
46206
- const offered = new Set(targeting.selection.offeredListIds);
46207
- return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
46208
- id: String(item.id),
46209
- label: item.name
46210
- })) : [];
46211
- }
46212
- case "mailchimp": {
46213
- const offered = new Set(targeting.selection.offeredInterestIds);
46214
- 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) => ({
46215
- id: item.id,
46216
- label: item.name
46217
- })) ?? [];
46218
- }
46219
- }
46220
- }
46221
- function runtimeTargetingIsAvailable(context, targeting, choices) {
46222
- if (context?.tag !== "configured") return false;
46223
- const provider = context.provider;
46224
- if (targeting.mode === "default") switch (provider.provider) {
46225
- case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
46226
- case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
46227
- case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
46228
- }
46229
- if (targeting.provider !== provider.provider) return false;
46230
- switch (targeting.provider) {
46231
- case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
46232
- case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
46233
- case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
46234
- }
46235
- }
46236
- function nativeTargetIsAvailable(provider, targeting, choices) {
46237
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
46238
- return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
46239
- }
46240
- function brevoTargetIsAvailable(provider, targeting, choices) {
46241
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
46242
- return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
46243
- }
46244
- function mailchimpTargetIsAvailable(provider, targeting, choices) {
46245
- const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
46246
- if (!audience) return false;
46247
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
46248
- return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
47130
+ function resolveRuntimeChoices(assessment) {
47131
+ if (!("value" in assessment) || assessment.value.mode !== "custom" || !("selectedChoices" in assessment.value) || assessment.value.targeting.selection.tag !== "visitor_choice") return [];
47132
+ return assessment.value.selectedChoices.map((item) => ({
47133
+ id: String(item.id),
47134
+ label: item.name
47135
+ }));
46249
47136
  }
46250
47137
  function parseRequiredMailingListSignupBlockId(value) {
46251
47138
  const parsed = parseMailingListBlockId(value);
@@ -46354,6 +47241,10 @@ function sanitizeNewsletterSignupPublicContent(content) {
46354
47241
  }
46355
47242
  const newsletterSignupBlockDefinition = {
46356
47243
  manifest: newsletterSignupManifest,
47244
+ contentNormalizer: {
47245
+ fieldIds: ["targeting"],
47246
+ normalize: normalizeMailingListBlockContent
47247
+ },
46357
47248
  runtime: newsletterFormBlockRuntime
46358
47249
  };
46359
47250
  //#endregion
@@ -47086,7 +47977,20 @@ const locationMapBlockDefinition = {
47086
47977
  })], { gap: "md" })
47087
47978
  })
47088
47979
  }),
47089
- normalizeContent: normalizeLocationMapContent
47980
+ contentNormalizer: {
47981
+ fieldIds: [
47982
+ "heading",
47983
+ "locationSource",
47984
+ "venueId",
47985
+ "location",
47986
+ "showAddressText",
47987
+ "showDirections",
47988
+ "directionsLabel",
47989
+ "zoom",
47990
+ "heightPx"
47991
+ ],
47992
+ normalize: normalizeLocationMapContent
47993
+ }
47090
47994
  };
47091
47995
  //#endregion
47092
47996
  //#region ../blocks/src/system/blocks/index.ts
@@ -48291,17 +49195,7 @@ const linksTransforms = [{
48291
49195
  id: "links.resolve",
48292
49196
  kind: "formatter",
48293
49197
  summary: "Resolve LinkValue objects to href strings using the route map",
48294
- run: (value, _options, context) => {
48295
- if (!value || typeof value !== "object") return null;
48296
- const link = value;
48297
- const kind = typeof link.kind === "string" ? link.kind : null;
48298
- if (kind === "internal") return resolveInternalLink(link, context);
48299
- if (kind === "page") return resolvePageLink(link, context);
48300
- if (kind === "entry") return resolveEntryLink(link, context);
48301
- if (kind === "external" || kind === "url") return resolveExternalLink(link);
48302
- if (!kind && typeof link.href === "string") return resolveExternalLink(link);
48303
- return null;
48304
- }
49198
+ run: (value, _options, context) => resolveAuthoredLinkHref(value, context)
48305
49199
  }, {
48306
49200
  id: "links.label",
48307
49201
  kind: "formatter",
@@ -48328,6 +49222,21 @@ const linksTransforms = [{
48328
49222
  return null;
48329
49223
  }
48330
49224
  }];
49225
+ function resolveAuthoredLinkHref(value, context = {}) {
49226
+ if (!value || typeof value !== "object") return null;
49227
+ const link = value;
49228
+ switch (link.kind) {
49229
+ case "internal": return resolveInternalLink(link, context);
49230
+ case "page": return resolvePageLink(link, context);
49231
+ case "entry": return resolveEntryLink(link, context);
49232
+ case "external":
49233
+ case "url": return resolveExternalLink(link);
49234
+ default: return resolvePreResolvedLink(link);
49235
+ }
49236
+ }
49237
+ function resolvePreResolvedLink(link) {
49238
+ return !(typeof link.kind === "string" ? link.kind : null) && typeof link.href === "string" ? resolveExternalLink(link) : null;
49239
+ }
48331
49240
  const eventCalendarSelectionParam = {
48332
49241
  id: "events.calendarSelectionParam",
48333
49242
  kind: "formatter",