@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
@@ -16166,6 +16166,29 @@ function canonicalEventPricingDisplay(pricing) {
16166
16166
  default: return assertNever$1(pricing);
16167
16167
  }
16168
16168
  }
16169
+ /**
16170
+ * The single renderer for a precomputed booking price `display` ADT. This is
16171
+ * the one place booking price variants map to user-facing strings — every call
16172
+ * site (booking entry, events calendar, event/course details, listings, portal)
16173
+ * should route through here so labels never drift. Adding a variant to the ADT
16174
+ * breaks compilation here (via `assertNever`) until it is handled.
16175
+ */
16176
+ function formatBookingPriceDisplay(display, options = {}) {
16177
+ switch (display.kind) {
16178
+ case "free": return "Free";
16179
+ case "exact": return formatCurrencyAmount(display.amount, display.currency, options);
16180
+ case "from": return `From ${formatCurrencyAmount(display.amount, display.currency, options)}`;
16181
+ case "range": return `${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)}`;
16182
+ 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)}`;
16183
+ case "unavailable": return "Pricing unavailable";
16184
+ case "external": return display.label;
16185
+ default: return assertNever$1(display);
16186
+ }
16187
+ }
16188
+ /** Render an event's price directly from its pricing value. */
16189
+ function formatEventPricing(pricing, options = {}) {
16190
+ return formatBookingPriceDisplay(publicEventPricingDisplay(pricing), options);
16191
+ }
16169
16192
  //#endregion
16170
16193
  //#region ../core/src/event-page-surface-subject.ts
16171
16194
  function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
@@ -16309,6 +16332,7 @@ function parseBookingTimeZone(timeZone) {
16309
16332
  }
16310
16333
  //#endregion
16311
16334
  //#region ../core/src/booking-date-time.ts
16335
+ const DEFAULT_LOCALE = "en-US";
16312
16336
  const FALLBACK_TIMEZONE = "UTC";
16313
16337
  const START_OF_DAY = {
16314
16338
  hour: 0,
@@ -16431,6 +16455,34 @@ function toScheduledBookingDateTime(input) {
16431
16455
  if (result.ok) return result.value;
16432
16456
  throw new Error(formatParseError(result));
16433
16457
  }
16458
+ function formatBookingInstant(isoString, options) {
16459
+ const date = new Date(isoString);
16460
+ if (Number.isNaN(date.getTime())) return isoString;
16461
+ return getCachedDateTimeFormatter(options.locale ?? DEFAULT_LOCALE, getSafeTimeZone(options.timeZone), options.format).format(date);
16462
+ }
16463
+ function formatBookingDate(input, format = {
16464
+ weekday: "long",
16465
+ year: "numeric",
16466
+ month: "long",
16467
+ day: "numeric"
16468
+ }) {
16469
+ return formatBookingInstant(input.startsAt, {
16470
+ locale: input.locale,
16471
+ timeZone: input.timeZone,
16472
+ format
16473
+ });
16474
+ }
16475
+ function formatBookingTime(input, format = {
16476
+ hour: "numeric",
16477
+ minute: "2-digit",
16478
+ hour12: true
16479
+ }) {
16480
+ return formatBookingInstant(input.startsAt, {
16481
+ locale: input.locale,
16482
+ timeZone: input.timeZone,
16483
+ format
16484
+ });
16485
+ }
16434
16486
  function getBookingLocalDateKey(input) {
16435
16487
  return formatDateKey$1(getLocalScheduleDate(new Date(input.startsAt), input.timeZone));
16436
16488
  }
@@ -16644,6 +16696,14 @@ function formError(field) {
16644
16696
  }
16645
16697
  //#endregion
16646
16698
  //#region ../core/src/mailing-list-targeting.ts
16699
+ const DEFAULT_MAILING_LIST_TARGETING = { mode: "default" };
16700
+ function mailingListTargetingChoiceIds(targeting) {
16701
+ switch (targeting.provider) {
16702
+ case "native": return targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
16703
+ case "brevo": return targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
16704
+ case "mailchimp": return targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
16705
+ }
16706
+ }
16647
16707
  function planMailingListChoicePresentation(targeting) {
16648
16708
  if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
16649
16709
  switch (targeting.provider) {
@@ -16683,6 +16743,103 @@ function planMailingListChoicePresentation(targeting) {
16683
16743
  }
16684
16744
  }
16685
16745
  //#endregion
16746
+ //#region ../core/src/mailing-list-targeting-availability.ts
16747
+ function resolveMailingListDefaultDestination(provider) {
16748
+ switch (provider.provider) {
16749
+ case "native": return provider.topics.find((item) => item.id === provider.defaultTopicId) ?? null;
16750
+ case "brevo": return provider.lists.find((item) => item.id === provider.defaultListId) ?? null;
16751
+ case "mailchimp": return provider.audiences.find((item) => item.id === provider.defaultAudienceId) ?? null;
16752
+ }
16753
+ }
16754
+ function assessMailingListTargeting(context, targeting) {
16755
+ if (context?.tag !== "configured") return {
16756
+ tag: "unavailable",
16757
+ reason: "setup_missing",
16758
+ targeting
16759
+ };
16760
+ const catalog = context.provider;
16761
+ if (targeting.mode === "custom") return assessCustomTargeting(catalog, targeting);
16762
+ const destination = resolveMailingListDefaultDestination(catalog);
16763
+ return destination === null ? {
16764
+ tag: "unavailable",
16765
+ reason: "default_missing",
16766
+ targeting
16767
+ } : {
16768
+ tag: "available",
16769
+ value: {
16770
+ mode: "default",
16771
+ targeting,
16772
+ provider: catalog.provider,
16773
+ destination
16774
+ }
16775
+ };
16776
+ }
16777
+ function assessCustomTargeting(catalog, targeting) {
16778
+ switch (targeting.provider) {
16779
+ case "native": return catalog.provider === "native" ? assessMatchedChoices({
16780
+ provider: "native",
16781
+ catalog,
16782
+ targeting
16783
+ }, catalog.topics) : {
16784
+ tag: "unavailable",
16785
+ reason: "provider_mismatch",
16786
+ targeting
16787
+ };
16788
+ case "brevo": return catalog.provider === "brevo" ? assessMatchedChoices({
16789
+ provider: "brevo",
16790
+ catalog,
16791
+ targeting
16792
+ }, catalog.lists) : {
16793
+ tag: "unavailable",
16794
+ reason: "provider_mismatch",
16795
+ targeting
16796
+ };
16797
+ case "mailchimp": {
16798
+ if (catalog.provider !== "mailchimp") return {
16799
+ tag: "unavailable",
16800
+ reason: "provider_mismatch",
16801
+ targeting
16802
+ };
16803
+ const audience = catalog.audiences.find((item) => item.id === targeting.audienceId);
16804
+ return audience === void 0 ? {
16805
+ tag: "unavailable",
16806
+ reason: "audience_missing",
16807
+ value: {
16808
+ mode: "custom",
16809
+ provider: "mailchimp",
16810
+ catalog,
16811
+ targeting
16812
+ }
16813
+ } : assessMatchedChoices({
16814
+ provider: "mailchimp",
16815
+ catalog,
16816
+ targeting,
16817
+ audience
16818
+ }, audience.interests);
16819
+ }
16820
+ }
16821
+ }
16822
+ function assessMatchedChoices(matched, choices) {
16823
+ const selected = new Set(mailingListTargetingChoiceIds(matched.targeting));
16824
+ const available = new Set(choices.map((item) => item.id));
16825
+ const missingIds = [...selected].filter((id) => !available.has(id));
16826
+ const value = {
16827
+ mode: "custom",
16828
+ ...matched,
16829
+ choices,
16830
+ selectedChoices: choices.filter((item) => selected.has(item.id))
16831
+ };
16832
+ return missingIds.length > 0 ? {
16833
+ tag: "unavailable",
16834
+ reason: "choices_missing",
16835
+ missingIds,
16836
+ value
16837
+ } : {
16838
+ tag: "available",
16839
+ value
16840
+ };
16841
+ }
16842
+ //#endregion
16686
16843
  //#region ../core/src/mailing-list-runtime.ts
16687
16844
  function parseMailingListRuntimeContext(value) {
16688
16845
  if (!isRecord$6(value)) return invalidRuntimeContext();
@@ -16747,57 +16904,47 @@ function parseBrevoRuntimeProvider(value) {
16747
16904
  function parseMailchimpRuntimeProvider(value) {
16748
16905
  if (!Array.isArray(value.audiences)) return null;
16749
16906
  const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
16750
- if (defaultAudienceId === null) return null;
16751
- const audiences = [];
16752
- for (const audience of value.audiences) {
16753
- if (!isRecord$6(audience)) return null;
16754
- const id = parseMailchimpAudienceId(audience.id);
16755
- const name = parseName(audience.name);
16756
- if (id === null || name === null || !Array.isArray(audience.interests)) return null;
16757
- const interests = parseMailchimpInterests(audience.interests);
16758
- if (interests === null) return null;
16759
- audiences.push({
16760
- id,
16761
- name,
16762
- interests
16763
- });
16764
- }
16765
- return {
16907
+ const audiences = Array.from(value.audiences, parseMailchimpAudience);
16908
+ return defaultAudienceId !== null && audiences.every((audience) => audience !== null) ? {
16766
16909
  provider: "mailchimp",
16767
16910
  defaultAudienceId,
16768
16911
  audiences
16769
- };
16912
+ } : null;
16770
16913
  }
16771
- function parseMailchimpInterests(value) {
16772
- const interests = [];
16773
- for (const interest of value) {
16774
- if (!isRecord$6(interest)) return null;
16775
- const id = parseMailchimpInterestId(interest.id);
16776
- const name = parseName(interest.name);
16777
- const categoryName = parseName(interest.categoryName);
16778
- if (id === null || name === null || categoryName === null) return null;
16779
- interests.push({
16780
- id,
16781
- name,
16782
- categoryName
16783
- });
16784
- }
16785
- return interests;
16914
+ function parseMailchimpAudience(value) {
16915
+ if (!isRecord$6(value) || !Array.isArray(value.interests)) return null;
16916
+ const id = parseMailchimpAudienceId(value.id);
16917
+ const name = parseName(value.name);
16918
+ const interests = Array.from(value.interests, parseMailchimpInterest);
16919
+ return id !== null && name !== null && interests.every((interest) => interest !== null) ? {
16920
+ id,
16921
+ name,
16922
+ interests
16923
+ } : null;
16924
+ }
16925
+ function parseMailchimpInterest(value) {
16926
+ if (!isRecord$6(value)) return null;
16927
+ const id = parseMailchimpInterestId(value.id);
16928
+ const name = parseName(value.name);
16929
+ const categoryName = parseName(value.categoryName);
16930
+ return id !== null && name !== null && categoryName !== null ? {
16931
+ id,
16932
+ name,
16933
+ categoryName
16934
+ } : null;
16786
16935
  }
16787
16936
  function parseNamedItems(value, parseId) {
16788
16937
  if (!Array.isArray(value)) return null;
16789
- const items = [];
16790
- for (const item of value) {
16938
+ const items = Array.from(value, (item) => {
16791
16939
  if (!isRecord$6(item)) return null;
16792
16940
  const id = parseId(item.id);
16793
16941
  const name = parseName(item.name);
16794
- if (id === null || name === null) return null;
16795
- items.push({
16942
+ return id !== null && name !== null ? {
16796
16943
  id,
16797
16944
  name
16798
- });
16799
- }
16800
- return items;
16945
+ } : null;
16946
+ });
16947
+ return items.every((item) => item !== null) ? items : null;
16801
16948
  }
16802
16949
  function parseName(value) {
16803
16950
  if (typeof value !== "string") return null;
@@ -16813,6 +16960,7 @@ function invalidRuntimeContext() {
16813
16960
  //#endregion
16814
16961
  //#region ../core/src/mailing-list-targeting-parser.ts
16815
16962
  function parseMailingListPlacementTargeting(value) {
16963
+ if (value == null) return ok(DEFAULT_MAILING_LIST_TARGETING);
16816
16964
  if (!isRecord$5(value)) return targetingError("shape");
16817
16965
  if (value.mode === "default") return ok({ mode: "default" });
16818
16966
  if (value.mode !== "custom") return targetingError("mode");
@@ -16911,14 +17059,11 @@ function parseNonEmptyUniqueIds(value, parseId) {
16911
17059
  }
16912
17060
  function parseUniqueIds(value, parseId) {
16913
17061
  if (!Array.isArray(value)) return targetingError("targets");
16914
- const parsed = [];
16915
- for (const item of value) {
17062
+ return Array.from(value).reduce((parsed, item) => {
17063
+ if (!parsed.ok) return parsed;
16916
17064
  const id = parseId(item);
16917
- if (id === null) return targetingError("targets");
16918
- if (parsed.includes(id)) return targetingError("duplicate_target");
16919
- parsed.push(id);
16920
- }
16921
- return ok(parsed);
17065
+ return id === null ? targetingError("targets") : parsed.value.includes(id) ? targetingError("duplicate_target") : ok([...parsed.value, id]);
17066
+ }, ok([]));
16922
17067
  }
16923
17068
  function isRecord$5(value) {
16924
17069
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -16967,6 +17112,45 @@ const SURFACE_EVENT_PRESET_VALUES = [
16967
17112
  "performance"
16968
17113
  ];
16969
17114
  //#endregion
17115
+ //#region ../core/src/event-preset-vocabulary.ts
17116
+ const EVENT_PRESET_VOCABULARY = {
17117
+ event: {
17118
+ label: "Event",
17119
+ pluralLabel: "Events",
17120
+ description: "Any event, repeating or manually dated, that does not fit the other categories."
17121
+ },
17122
+ class: {
17123
+ label: "Class",
17124
+ pluralLabel: "Classes",
17125
+ description: "Regular scheduled classes and group sessions."
17126
+ },
17127
+ course: {
17128
+ label: "Course",
17129
+ pluralLabel: "Courses",
17130
+ description: "A fixed set of dates that people book together."
17131
+ },
17132
+ term: {
17133
+ label: "Term",
17134
+ pluralLabel: "Terms",
17135
+ description: "A fixed term of sessions that people book together."
17136
+ },
17137
+ workshop: {
17138
+ label: "Workshop",
17139
+ pluralLabel: "Workshops",
17140
+ description: "One-off workshops, day sessions, or special events."
17141
+ },
17142
+ retreat: {
17143
+ label: "Retreat",
17144
+ pluralLabel: "Retreats",
17145
+ description: "Multi-day or residential immersive experiences."
17146
+ },
17147
+ performance: {
17148
+ label: "Performance",
17149
+ pluralLabel: "Performances",
17150
+ description: "Audience-facing shows, concerts, recitals, or showcases."
17151
+ }
17152
+ };
17153
+ //#endregion
16970
17154
  //#region ../media-core/src/aspectTransforms.ts
16971
17155
  function parseAspectRatioValue(value) {
16972
17156
  if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
@@ -24210,6 +24394,15 @@ const formBlockDefinition = {
24210
24394
  runtime: formBlockRuntime
24211
24395
  };
24212
24396
  //#endregion
24397
+ //#region ../blocks/src/system/helpers/normalizeMailingListBlockContent.ts
24398
+ /** Fill the declared default without admitting or rewriting invalid custom settings. */
24399
+ function normalizeMailingListBlockContent(record) {
24400
+ return record.targeting == null ? {
24401
+ ...record,
24402
+ targeting: DEFAULT_MAILING_LIST_TARGETING
24403
+ } : record;
24404
+ }
24405
+ //#endregion
24213
24406
  //#region ../blocks/src/system/blocks/contact-newsletter.ts
24214
24407
  const contactNewsletterLayout = styledSection({
24215
24408
  children: sectionContainer([el("div", { className: "contact-newsletter-grid" }, [el("div", { className: "contact-newsletter-column contact-newsletter-contact" }, [el("div", { className: "contact-newsletter-copy" }, [text({
@@ -24373,6 +24566,10 @@ const contactNewsletterBlockDefinition = {
24373
24566
  ],
24374
24567
  icon: "MailPlus"
24375
24568
  }),
24569
+ contentNormalizer: {
24570
+ fieldIds: ["targeting"],
24571
+ normalize: normalizeMailingListBlockContent
24572
+ },
24376
24573
  dataSchemas: { form: formDataSchema.optional() },
24377
24574
  dataLoaders: { form: {
24378
24575
  endpoint: "getPublicFormById",
@@ -27518,6 +27715,14 @@ bookingPaymentOptionSchemas.flexibleBalanceConfig;
27518
27715
  bookingPaymentOptionSchemas.paymentCollectionOption;
27519
27716
  const bookingPaymentTermsSchema = bookingPaymentOptionSchemas.paymentTerms;
27520
27717
  bookingPaymentOptionSchemas.paymentOptionsChoice;
27718
+ z.strictObject({
27719
+ currency: z.string().trim().length(3),
27720
+ totalCents: nonNegativeIntegerSchema,
27721
+ dueNowCents: nonNegativeIntegerSchema,
27722
+ laterPaymentAmountsCents: z.array(nonNegativeIntegerSchema),
27723
+ paymentOption: z.union([createBookingPaymentOptionSchemas("strict").paymentCollectionOption, z.strictObject({ kind: z.literal("not_required") })]),
27724
+ promisedDiscountId: z.string().min(1).nullable().optional()
27725
+ });
27521
27726
  //#endregion
27522
27727
  //#region ../blocks/src/system/blocks/shared/bookingPaymentTerms.ts
27523
27728
  const blockBookingPaymentTermsSchema = bookingPaymentTermsSchema;
@@ -27758,6 +27963,10 @@ const publicEventBaseSchema = z.object({
27758
27963
  eventDestinationMode: eventDestinationModeSchema.optional(),
27759
27964
  scheduleScope: eventScheduleScopeSchema$2.optional(),
27760
27965
  recurrence: publicEventRecurrenceSchema.optional(),
27966
+ scheduleSummary: z.object({
27967
+ recurrenceDescription: z.string(),
27968
+ startTime: z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/).nullable()
27969
+ }).nullable().optional(),
27761
27970
  id: z.string(),
27762
27971
  occurrenceId: z.string(),
27763
27972
  seriesId: z.string(),
@@ -28168,67 +28377,266 @@ const occurrenceContextSchema = z.object({
28168
28377
  overrides: z.record(z.string(), z.unknown()).nullable().optional()
28169
28378
  }).nullable();
28170
28379
  //#endregion
28171
- //#region ../blocks/src/system/runtime/api/readAuthority.ts
28172
- const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
28173
- kind: z.literal("preview"),
28174
- token: z.string().min(1)
28175
- }).strict()]);
28176
- const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
28177
- kind: z.literal("unsupported"),
28178
- reason: z.enum([
28179
- "unsupportedSubject",
28180
- "invalidEventSeriesId",
28181
- "invalidEventSeriesBookingScope",
28182
- "invalidEventOfferingId",
28183
- "invalidEventOfferingBookingScope",
28184
- "invalidEventOfferingCatalogue"
28185
- ])
28186
- });
28187
- const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
28188
- const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
28189
- if (unsupported.success) return unsupported.data;
28190
- const parsed = parseEventPageSurfaceSubject(value);
28191
- if (parsed.ok) return parsed.value;
28192
- ctx.addIssue({
28193
- code: z.ZodIssueCode.custom,
28194
- message: `Invalid event page-surface subject (${parsed.error.kind})`
28195
- });
28196
- return z.NEVER;
28197
- });
28198
- function resolveEventRegistrationScope(context) {
28199
- const occurrenceSeriesId = context.occurrenceContext?.seriesId;
28200
- if (occurrenceSeriesId) return {
28201
- kind: "occurrenceSeries",
28202
- seriesId: occurrenceSeriesId
28380
+ //#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
28381
+ /**
28382
+ * Compute remaining spots for an event from `availableSpots` with a fallback
28383
+ * to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
28384
+ * for uncapped events so call sites can render no capacity indicator at all.
28385
+ *
28386
+ * The single source of truth for capacity math across event cards, agenda
28387
+ * rows, and timetable cells — keep wording variations in the call sites.
28388
+ */
28389
+ function getEventAvailability(event) {
28390
+ if (event.capacity == null) return {
28391
+ available: null,
28392
+ isFull: false
28203
28393
  };
28204
- if (context.subject?.kind === "eventSeries") return {
28205
- kind: "pageSurfaceSeries",
28206
- seriesId: context.subject.eventSeriesId
28394
+ const available = event.availableSpots ?? event.capacity - event.registeredCount;
28395
+ if (available <= 0) return {
28396
+ available: 0,
28397
+ isFull: true
28207
28398
  };
28208
- if (context.subject?.kind === "eventOffering") return {
28209
- kind: "pageSurfaceOffering",
28210
- offeringId: context.subject.eventOfferingId,
28211
- seriesIds: context.subject.catalogueEventSeriesIds
28399
+ return {
28400
+ available,
28401
+ isFull: false
28212
28402
  };
28213
- if (context.subject?.kind === "unsupported") return {
28214
- kind: "unsupportedPageSurfaceSubject",
28215
- reason: context.subject.reason
28403
+ }
28404
+ /**
28405
+ * Pick the capacity row a booking decision should read: the selected
28406
+ * attendance mode's availability when the visitor has chosen one and the
28407
+ * event publishes it, otherwise the event-level aggregate.
28408
+ *
28409
+ * The mode row is used wholesale. A mode with `capacity: null` on a capped
28410
+ * event is uncapped *by design*: the registration write path applies the
28411
+ * same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
28412
+ * back to the event's `availableSpots` here would block bookings the server
28413
+ * accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
28414
+ */
28415
+ function selectEventCapacitySource(event, attendanceMode) {
28416
+ if (!attendanceMode) return event;
28417
+ return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
28418
+ }
28419
+ /**
28420
+ * Whether the visitor's next step is the waitlist: the source is full *and*
28421
+ * a waitlist is open. The one place this is decided — cards, rows, the
28422
+ * wizard's steps and its action bar all route through here so they agree,
28423
+ * including for producers that omit `availableSpots` and rely on the
28424
+ * `capacity - registeredCount` fallback.
28425
+ */
28426
+ function canJoinEventWaitlist(event, options) {
28427
+ const source = selectEventCapacitySource(event, options?.attendanceMode);
28428
+ return getEventAvailability(source).isFull && source.waitlistEnabled === true;
28429
+ }
28430
+ /**
28431
+ * Remaining places at or below which an event reads as scarce and the exact
28432
+ * count is worth showing. Above it, no number is shown at all: "147 places
28433
+ * left" is not something a visitor acts on, and it is the figure most likely
28434
+ * to be stale on a cached page.
28435
+ *
28436
+ * max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
28437
+ *
28438
+ * The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
28439
+ * 5% floor stops large events staying silent until three remain.
28440
+ * capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
28441
+ */
28442
+ function lowCapacityThreshold(capacity) {
28443
+ return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
28444
+ }
28445
+ function getRemainingPlaces(source) {
28446
+ const { available, isFull } = getEventAvailability(source);
28447
+ if (available === null || source.capacity === null) return { kind: "unlimited" };
28448
+ if (isFull) return {
28449
+ kind: "full",
28450
+ waitlist: canJoinEventWaitlist(source)
28216
28451
  };
28217
- return { kind: "unscoped" };
28452
+ if (available <= lowCapacityThreshold(source.capacity)) return {
28453
+ kind: "low",
28454
+ remaining: available
28455
+ };
28456
+ return { kind: "open" };
28218
28457
  }
28219
- function resolveEventRegistrationScopedEvents(events, context) {
28220
- const scope = resolveEventRegistrationScope(context);
28221
- switch (scope.kind) {
28222
- case "occurrenceSeries":
28223
- case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
28224
- case "pageSurfaceOffering": {
28225
- const seriesIds = new Set(scope.seriesIds);
28226
- return events.filter((event) => seriesIds.has(event.seriesId));
28227
- }
28228
- case "unsupportedPageSurfaceSubject": return [];
28229
- case "unscoped": return [...events];
28458
+ /**
28459
+ * The one place a remaining-places count becomes text. Returns `null` when
28460
+ * nothing should be shown: uncapped events, and capped events above the
28461
+ * scarcity threshold. Surfaces that need their own full-state wording pass
28462
+ * it in rather than re-deriving the count.
28463
+ */
28464
+ function formatRemainingPlaces(source, wording = {}) {
28465
+ const remaining = getRemainingPlaces(source);
28466
+ switch (remaining.kind) {
28467
+ case "unlimited":
28468
+ case "open": return null;
28469
+ case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
28470
+ case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
28471
+ }
28472
+ }
28473
+ function formatPlacesLeft(count, compact) {
28474
+ if (compact) return `${count} left`;
28475
+ return count === 1 ? "1 place left" : `${count} places left`;
28476
+ }
28477
+ function formatTicketTypeRemaining(remainingCapacity) {
28478
+ if (remainingCapacity === null || remainingCapacity > 3) return null;
28479
+ if (remainingCapacity <= 0) return "None left for this ticket type";
28480
+ return `${formatPlacesLeft(remainingCapacity, false)} for this ticket type`;
28481
+ }
28482
+ /**
28483
+ * Which capacity figure wins when they disagree.
28484
+ *
28485
+ * For a hybrid event that publishes both attendance-mode rows, **the mode
28486
+ * rows are authoritative and the event-level aggregate is only a summary**.
28487
+ * The registration write path admits or refuses per mode
28488
+ * (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
28489
+ * aggregate can read full while a mode still has room, and can read open
28490
+ * while the mode the visitor wants is full. Believing the aggregate on a
28491
+ * card is how a "places available" event turns into a dead end at step two.
28492
+ *
28493
+ * Everything else — single-mode events, events that publish no rows, and the
28494
+ * aggregate-only helpers {@link getEventAvailability} /
28495
+ * {@link getRemainingPlaces} that existing callers rely on — keeps reading
28496
+ * the aggregate, unchanged.
28497
+ */
28498
+ function findHybridModeRows(event) {
28499
+ if (event.deliveryMode !== "hybrid") return null;
28500
+ const rows = event.attendanceModes ?? [];
28501
+ const inPerson = rows.find((row) => row.mode === "in_person");
28502
+ const online = rows.find((row) => row.mode === "online");
28503
+ return inPerson && online ? {
28504
+ inPerson,
28505
+ online
28506
+ } : null;
28507
+ }
28508
+ /** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
28509
+ function remainingPlacesRank(remaining) {
28510
+ switch (remaining.kind) {
28511
+ case "unlimited": return 4;
28512
+ case "open": return 3;
28513
+ case "low": return 2;
28514
+ case "full": return remaining.waitlist ? 1 : 0;
28515
+ }
28516
+ }
28517
+ function isMoreOpen(candidate, than) {
28518
+ const rank = remainingPlacesRank(candidate);
28519
+ const otherRank = remainingPlacesRank(than);
28520
+ if (rank !== otherRank) return rank > otherRank;
28521
+ return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
28522
+ }
28523
+ /** True when `candidate` has less room left than `than`. */
28524
+ function isScarcer(candidate, than) {
28525
+ return isMoreOpen(than, candidate);
28526
+ }
28527
+ const ATTENDANCE_MODE_LABEL = {
28528
+ in_person: "In person",
28529
+ online: "Online"
28530
+ };
28531
+ /** Wording for a mode with room to spare, where the count is deliberately withheld. */
28532
+ const PLACES_AVAILABLE = "Places available";
28533
+ /**
28534
+ * The browse-surface label for an event's remaining places. Single-mode
28535
+ * events get exactly the aggregate label {@link formatRemainingPlaces}
28536
+ * produces, and so does a hybrid event whose two modes tell the same story.
28537
+ * When the modes differ, both sides are named: "available" on a card is a
28538
+ * promise the registration wizard would otherwise have to break.
28539
+ *
28540
+ * Hybrid examples, in person full with 3 online places left:
28541
+ * default -> "Online: 3 places left · In person: Full"
28542
+ * { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
28543
+ * { compact: true } -> "Online: 3 left"
28544
+ */
28545
+ function formatEventRemainingPlaces(event, wording = {}) {
28546
+ const rows = findHybridModeRows(event);
28547
+ if (!rows) return formatRemainingPlaces(event, wording);
28548
+ const inPerson = formatRemainingPlaces(rows.inPerson, wording);
28549
+ const online = formatRemainingPlaces(rows.online, wording);
28550
+ if (inPerson === online) return inPerson;
28551
+ if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
28552
+ return formatBothHybridSides(rows, {
28553
+ inPerson,
28554
+ online
28555
+ });
28556
+ }
28557
+ /**
28558
+ * Both sides, the one with room first: that is the half a visitor can act on,
28559
+ * and it should not arrive after a full stop's worth of bad news.
28560
+ */
28561
+ function formatBothHybridSides(rows, labels) {
28562
+ 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(" · ");
28563
+ }
28564
+ function namedModePlaces(mode, label) {
28565
+ return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
28566
+ }
28567
+ /**
28568
+ * Pills and timetable cells have room for one short phrase, so they name the
28569
+ * single mode that carries news rather than listing both: the scarce side
28570
+ * when there is a count to show, and otherwise the side that has closed —
28571
+ * "In person: Full", never "Online only", which reads as a statement about
28572
+ * how the event is delivered rather than about its remaining places.
28573
+ */
28574
+ function formatCompactHybridPlaces(rows, wording) {
28575
+ const inPerson = getRemainingPlaces(rows.inPerson);
28576
+ const online = getRemainingPlaces(rows.online);
28577
+ if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
28578
+ if (inPerson.kind === "full" || online.kind === "full") {
28579
+ const fullSide = inPerson.kind === "full" ? "in_person" : "online";
28580
+ const openSide = inPerson.kind === "full" ? "online" : "in_person";
28581
+ const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
28582
+ return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
28583
+ }
28584
+ const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
28585
+ return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
28586
+ }
28587
+ function rowFor(rows, mode) {
28588
+ return mode === "online" ? rows.online : rows.inPerson;
28589
+ }
28590
+ //#endregion
28591
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDisplay.ts
28592
+ const registrationDisplaySchema = z.object({
28593
+ venue: z.enum(["shown", "hidden"]),
28594
+ placesLeft: z.enum(["shown", "hidden"])
28595
+ });
28596
+ function resolveRegistrationDisplay(source) {
28597
+ return {
28598
+ venue: source.showVenue === false ? "hidden" : "shown",
28599
+ placesLeft: source.showCapacity === false ? "hidden" : "shown"
28600
+ };
28601
+ }
28602
+ /** Venue stays in domain data; views receive only the permitted presentation. */
28603
+ function shownVenue(event, display) {
28604
+ return display.venue === "shown" ? event.venue?.name ?? null : null;
28605
+ }
28606
+ /** Full/waitlist explanations remain visible even when optional counts are hidden. */
28607
+ function placesLeftLabel(source, display, wording = {}) {
28608
+ if ("kind" in source) return ticketPlacesLabel(source.remaining, display);
28609
+ if (source.deliveryMode === "hybrid" && source.attendanceModes?.length) return hybridPlacesLabel(source, display, wording);
28610
+ return capacityPlacesLabel(source, display, wording);
28611
+ }
28612
+ function capacityPlacesLabel(source, display, wording) {
28613
+ const remaining = getRemainingPlaces(source);
28614
+ if (display.placesLeft === "hidden" && remaining.kind !== "full") return null;
28615
+ switch (remaining.kind) {
28616
+ case "full": return formatRemainingPlaces(source, wording);
28617
+ case "low": return formatRemainingPlaces(source, wording);
28618
+ case "unlimited": return wording.unlimited ?? null;
28619
+ case "open": return wording.open ?? null;
28620
+ default: return assertNever$1(remaining);
28230
28621
  }
28231
28622
  }
28623
+ function hybridPlacesLabel(event, display, wording) {
28624
+ if (display.placesLeft === "shown") return formatEventRemainingPlaces(event, wording);
28625
+ return (event.attendanceModes ?? []).flatMap((mode) => {
28626
+ const label = placesLeftLabel(mode, display, wording);
28627
+ return label ? [`${mode.mode === "online" ? "Online" : "In person"}: ${label}`] : [];
28628
+ }).join(" · ") || null;
28629
+ }
28630
+ function ticketPlacesLabel(remaining, display) {
28631
+ if (remaining !== null && remaining > 0 && display.placesLeft === "hidden") return null;
28632
+ return formatTicketTypeRemaining(remaining);
28633
+ }
28634
+ //#endregion
28635
+ //#region ../blocks/src/system/runtime/api/readAuthority.ts
28636
+ const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
28637
+ kind: z.literal("preview"),
28638
+ token: z.string().min(1)
28639
+ }).strict()]);
28232
28640
  //#endregion
28233
28641
  //#region ../calendar/src/grid.ts
28234
28642
  const MONTH_NAMES = [
@@ -28316,6 +28724,11 @@ function buildCalendarViewport(input) {
28316
28724
  //#endregion
28317
28725
  //#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
28318
28726
  /**
28727
+ * Date and time presentation for events: the single place an event's
28728
+ * timestamps become strings or date buckets. Calendar-grid primitives are
28729
+ * re-exported from @riverbankcms/calendar so block code has one import.
28730
+ */
28731
+ /**
28319
28732
  * The English locale a site's public dates are written in, read from the
28320
28733
  * site's own time zone (Will, 2026-09-26: "date style from site time zone").
28321
28734
  *
@@ -28335,6 +28748,28 @@ function eventDateLocale(siteTimeZone) {
28335
28748
  if (siteTimeZone === "Pacific/Auckland" || siteTimeZone === "Pacific/Chatham") return "en-NZ";
28336
28749
  return "en-US";
28337
28750
  }
28751
+ function formatEventCompactDateRail(isoTimestamp, options, letterCase = "capitals") {
28752
+ const locale = options?.locale ?? "en-US";
28753
+ const dateTime = toScheduledBookingDateTime({
28754
+ startsAt: isoTimestamp,
28755
+ timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
28756
+ locale
28757
+ });
28758
+ const cased = (value) => letterCase === "capitals" ? value.toLocaleUpperCase(locale) : value;
28759
+ const weekday = cased(formatBookingDate(dateTime, { weekday: "short" }));
28760
+ const month = cased(formatBookingDate(dateTime, { month: "short" }).slice(0, 3));
28761
+ return {
28762
+ weekday,
28763
+ day: formatBookingDate(dateTime, { day: "numeric" }),
28764
+ month,
28765
+ label: formatBookingDate(dateTime, {
28766
+ weekday: "long",
28767
+ year: "numeric",
28768
+ month: "long",
28769
+ day: "numeric"
28770
+ })
28771
+ };
28772
+ }
28338
28773
  //#endregion
28339
28774
  //#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
28340
28775
  function isRecord$4(value) {
@@ -28354,6 +28789,15 @@ function resolveRuntimeEventDestination(event) {
28354
28789
  };
28355
28790
  }
28356
28791
  //#endregion
28792
+ //#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
28793
+ /**
28794
+ * Format an event's price directly from its pricing value (without the full
28795
+ * event). Same output as {@link formatEventPrice}.
28796
+ */
28797
+ function formatEventPriceDisplay(pricing) {
28798
+ return formatEventPricing(pricing, { decimals: "auto" });
28799
+ }
28800
+ //#endregion
28357
28801
  //#region ../blocks/src/system/runtime/nodes/events/domain/listWeek.ts
28358
28802
  /** Civil weeks use the site zone, including weeks containing a clock change. */
28359
28803
  function buildEventListWeek(anchor, startOfWeek, timeZone) {
@@ -28388,49 +28832,685 @@ function eventListWindowEvents(events, window) {
28388
28832
  });
28389
28833
  }
28390
28834
  //#endregion
28391
- //#region ../blocks/src/system/constants/events.ts
28392
- const eventCalendarDisplayModeValues = [
28393
- "combined",
28394
- "calendar",
28395
- "list",
28396
- "timetable",
28397
- "week-board"
28398
- ];
28399
- const eventCalendarDefaultViewValues = ["month", "week"];
28400
- const eventCalendarListLayoutValues = [
28401
- "stack",
28402
- "grid",
28403
- "compact"
28404
- ];
28405
- const arrangementOnlyListLayouts = ["ledger", "day-groups"];
28406
- const eventListArrangementValues = [...eventCalendarListLayoutValues, ...arrangementOnlyListLayouts];
28407
- const eventCalendarListLayoutOptions = [
28408
- {
28409
- id: "stack",
28410
- label: "Stack"
28411
- },
28412
- {
28413
- id: "grid",
28414
- label: "Grid"
28415
- },
28416
- {
28417
- id: "compact",
28418
- label: "Compact"
28419
- }
28420
- ];
28421
- function isEventCalendarDisplayMode(value) {
28422
- return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
28835
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
28836
+ function eventMatchesOccurrenceId(event, occurrenceId) {
28837
+ return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
28423
28838
  }
28424
- function normalizeEventCalendarDisplayMode$1(input) {
28425
- if (isEventCalendarDisplayMode(input.variant)) return input.variant;
28426
- return isEventCalendarDisplayMode(input.displayMode) ? input.displayMode : void 0;
28839
+ function findPreSelectedEvent(input) {
28840
+ const occurrenceContext = input.occurrenceContext;
28841
+ if (!occurrenceContext) return input.scopedEvents.length === 1 ? input.scopedEvents[0] ?? null : null;
28842
+ const contextDate = getBookingLocalDateKey(toScheduledBookingDateTime({
28843
+ startsAt: occurrenceContext.startsAt,
28844
+ timeZone: occurrenceContext.timeZone
28845
+ }));
28846
+ return input.scopedEvents.find((event) => eventMatchesOccurrenceId(event, occurrenceContext.id) || getBookingLocalDateKey(toScheduledBookingDateTime({
28847
+ startsAt: event.startsAt,
28848
+ timeZone: event.timeZone
28849
+ })) === contextDate) ?? null;
28427
28850
  }
28428
- /** One closed authority for the projections required by each rendered mode. */
28429
- function resolveEventCalendarReadPlan(input) {
28430
- const mode = normalizeEventCalendarDisplayMode$1(input) ?? "combined";
28431
- switch (mode) {
28432
- case "combined": return {
28433
- mode,
28851
+ function resolveEventRequiresPayment(input) {
28852
+ if (input.eligibility) return input.eligibility.requiresPayment;
28853
+ if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
28854
+ if (!input.event) return false;
28855
+ return publicEventPricingType(input.event.pricing) === "paid";
28856
+ }
28857
+ //#endregion
28858
+ //#region ../blocks/src/system/runtime/nodes/event-registration/utils.ts
28859
+ /**
28860
+ * The locale a site's booking dates are written in: the one the site's time
28861
+ * zone places it in (`eventDateLocale`, shared with event cards), or British
28862
+ * English when the site names no zone.
28863
+ */
28864
+ function bookingDateLocale(siteTimeZone) {
28865
+ return siteTimeZone ? eventDateLocale(siteTimeZone) : "en-GB";
28866
+ }
28867
+ function bookingInstant(startsAt, input) {
28868
+ return toScheduledBookingDateTime({
28869
+ startsAt,
28870
+ timeZone: toBookingTimeZone(input.timeZone),
28871
+ locale: bookingDateLocale(input.siteTimeZone)
28872
+ });
28873
+ }
28874
+ /** "7:00pm": a twelve-hour clock with a lowercase, unspaced day period. */
28875
+ function bookingClock(startsAt, input) {
28876
+ return formatBookingTime(bookingInstant(startsAt, input), {
28877
+ hour: "numeric",
28878
+ minute: "2-digit",
28879
+ hour12: true
28880
+ }).replace(/\s*([ap])\.?\s?m\.?$/i, (_match, period) => `${period.toLowerCase()}m`);
28881
+ }
28882
+ /** "7:00pm – 8:15pm", or just the start when there's no distinct end. */
28883
+ function clockRange(input) {
28884
+ const start = bookingClock(input.startsAt, input);
28885
+ const end = input.endsAt ? bookingClock(input.endsAt, input) : null;
28886
+ return end && end !== start ? `${start} – ${end}` : start;
28887
+ }
28888
+ /** A stored local clock has no date or DST offset; UTC preserves its digits. */
28889
+ function formatBookingScheduleSummary(event) {
28890
+ const summary = event.scheduleSummary;
28891
+ if (!summary) return null;
28892
+ const time = summary.startTime ? formatBookingWhen({
28893
+ startsAt: `1970-01-01T${summary.startTime}:00Z`,
28894
+ timeZone: "UTC",
28895
+ siteTimeZone: event.siteTimeZone
28896
+ }, "time") : null;
28897
+ return [summary.recurrenceDescription, time].filter(Boolean).join(" · ");
28898
+ }
28899
+ /** The one booking date formatter; every variant derives from it. */
28900
+ function formatBookingWhen(input, variant) {
28901
+ const instant = bookingInstant(input.startsAt, input);
28902
+ const day = formatBookingDate(instant, {
28903
+ weekday: "short",
28904
+ day: "numeric",
28905
+ month: "short"
28906
+ });
28907
+ switch (variant) {
28908
+ case "day": return day;
28909
+ case "dayLong": return formatBookingDate(instant, {
28910
+ weekday: "long",
28911
+ day: "numeric",
28912
+ month: "long"
28913
+ });
28914
+ case "month": return formatBookingDate(instant, { month: "long" });
28915
+ case "monthYear": return formatBookingDate(instant, {
28916
+ month: "long",
28917
+ year: "numeric"
28918
+ });
28919
+ case "time": return bookingClock(input.startsAt, input);
28920
+ case "timeRange": return clockRange(input);
28921
+ case "short": return `${day}, ${bookingClock(input.startsAt, input)}`;
28922
+ case "long": return `${day} · ${clockRange(input)}`;
28923
+ }
28924
+ }
28925
+ /** A single instant in the short form, e.g. "Mon 5 Oct, 7:00pm". */
28926
+ function formatEventDateTime(isoTimestamp, options) {
28927
+ return formatBookingWhen({
28928
+ startsAt: isoTimestamp,
28929
+ timeZone: options?.timeZone,
28930
+ siteTimeZone: options?.siteTimeZone
28931
+ }, "short");
28932
+ }
28933
+ /** The title an occurrence was given on its own, when it overrides the event's. */
28934
+ function occurrenceTitleOverride(occurrence) {
28935
+ const title = occurrence?.overrides?.title;
28936
+ return typeof title === "string" && title.trim() ? title.trim() : null;
28937
+ }
28938
+ /** The event as this page names it: an occurrence's own title wins. */
28939
+ function withOccurrenceTitle(event, occurrence) {
28940
+ const title = occurrenceTitleOverride(occurrence);
28941
+ return event && title ? {
28942
+ ...event,
28943
+ title
28944
+ } : event;
28945
+ }
28946
+ function courseSchedule(sessions, siteTimeZone) {
28947
+ const first = sessions[0];
28948
+ if (!first) return null;
28949
+ const last = sessions[sessions.length - 1] ?? first;
28950
+ const firstLabel = formatEventDateTime(first.startsAt, {
28951
+ timeZone: first.timeZone,
28952
+ siteTimeZone
28953
+ });
28954
+ if (last.startsAt === first.startsAt) return `${sessions.length} session${sessions.length === 1 ? "" : "s"} · ${firstLabel}`;
28955
+ const lastLabel = formatEventDateTime(last.startsAt, {
28956
+ timeZone: last.timeZone,
28957
+ siteTimeZone
28958
+ });
28959
+ return `${sessions.length} sessions · ${firstLabel} – ${lastLabel}`;
28960
+ }
28961
+ /**
28962
+ * When a booking happens, in one line: the occurrence's date and time, or a
28963
+ * course's session count and first and last dates. `short` by default (bar,
28964
+ * confirmation); `long` adds the end time (card, panel header).
28965
+ */
28966
+ function formatRegistrationSchedule(event, occurrence, variant = "short") {
28967
+ if (event.registrationSubject.kind === "wholeSeries") return courseSchedule(event.registrationSubject.sessions, event.siteTimeZone);
28968
+ const input = scheduleInput(event, occurrence ?? null);
28969
+ return input ? formatBookingWhen(input, variant) : null;
28970
+ }
28971
+ /** The occurrence's own times when the page names one, otherwise the event's. */
28972
+ function scheduleInput(event, occurrence) {
28973
+ const times = occurrence?.startsAt ? {
28974
+ startsAt: occurrence.startsAt,
28975
+ endsAt: occurrence.endsAt ?? null
28976
+ } : {
28977
+ startsAt: event.startsAt,
28978
+ endsAt: event.endsAt
28979
+ };
28980
+ if (!times.startsAt) return null;
28981
+ return {
28982
+ ...times,
28983
+ timeZone: occurrence?.timeZone ?? event.timeZone ?? null,
28984
+ siteTimeZone: event.siteTimeZone
28985
+ };
28986
+ }
28987
+ //#endregion
28988
+ //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
28989
+ function registrationRoute(input) {
28990
+ const base = input.chooseEvent ? [
28991
+ "select-event",
28992
+ "select-tickets",
28993
+ "enter-details"
28994
+ ] : ["select-tickets", "enter-details"];
28995
+ switch (input.payment) {
28996
+ case "none": return base;
28997
+ case "options": return [...base, "select-payment"];
28998
+ case "optionsAndPayment": return [
28999
+ ...base,
29000
+ "select-payment",
29001
+ "payment"
29002
+ ];
29003
+ }
29004
+ }
29005
+ const REGISTRATION_STEP_LABELS = [
29006
+ "Event",
29007
+ "Date",
29008
+ "Tickets",
29009
+ "Places",
29010
+ "Your details",
29011
+ "How you’ll pay",
29012
+ "Payment"
29013
+ ];
29014
+ /** Several dates of one series are a date choice; anything else is an event choice. */
29015
+ function getEventSelectionMode(events) {
29016
+ const firstSeriesId = events[0]?.seriesId;
29017
+ return events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId) ? "date" : "event";
29018
+ }
29019
+ /** The label facts for a surface: what the visitor chooses from, and what they book. */
29020
+ function registrationStepLabelContext(scopedEvents, bookedEvent) {
29021
+ return {
29022
+ eventSelection: getEventSelectionMode(scopedEvents),
29023
+ booksWholeSeries: bookedEvent?.registrationSubject.kind === "wholeSeries"
29024
+ };
29025
+ }
29026
+ /** One label owner for the live progress and the server placeholder. */
29027
+ function registrationStepLabel(step, context) {
29028
+ switch (step) {
29029
+ case "select-event": return context.eventSelection === "date" ? "Date" : "Event";
29030
+ case "select-tickets": return context.booksWholeSeries ? "Places" : "Tickets";
29031
+ case "enter-details": return "Your details";
29032
+ case "select-payment": return "How you’ll pay";
29033
+ case "payment": return "Payment";
29034
+ }
29035
+ }
29036
+ function bookingButtonText(buttonText) {
29037
+ return buttonText?.trim() || "Book your place";
29038
+ }
29039
+ function placesLabel(event, input, compact) {
29040
+ return placesLeftLabel(selectEventCapacitySource(event, input.attendanceMode), input.display, { compact });
29041
+ }
29042
+ function isFull(event, mode) {
29043
+ return formatRemainingPlaces(selectEventCapacitySource(event, mode), {
29044
+ full: "full",
29045
+ waitlist: "full"
29046
+ }) === "full";
29047
+ }
29048
+ function priceLabel(event) {
29049
+ return formatEventPriceDisplay(event.pricing).trim() || null;
29050
+ }
29051
+ /** The lowest price a display can mean; null when it names no paid amount. */
29052
+ function lowestPaidAmount(display) {
29053
+ switch (display.kind) {
29054
+ case "exact":
29055
+ case "from": return display.amount;
29056
+ case "range": return display.minAmount;
29057
+ case "mixed": return display.paidFromAmount;
29058
+ case "free":
29059
+ case "unavailable":
29060
+ case "external": return null;
29061
+ }
29062
+ }
29063
+ /**
29064
+ * One price for a set of dates: the shared label when every date costs the
29065
+ * same, otherwise "From" the lowest paid amount, through the one formatter.
29066
+ */
29067
+ function seriesPriceLabel(events) {
29068
+ const firstEvent = events[0];
29069
+ if (!firstEvent) return null;
29070
+ const displays = events.map((event) => publicEventPricingDisplay(event.pricing));
29071
+ const labels = new Set(displays.map((display) => formatBookingPriceDisplay(display, { decimals: "auto" })));
29072
+ if (labels.size === 1) return [...labels][0] || null;
29073
+ const lowest = displays.flatMap((display) => {
29074
+ const amount = lowestPaidAmount(display);
29075
+ return amount === null || !("currency" in display) ? [] : [{
29076
+ amount,
29077
+ currency: display.currency
29078
+ }];
29079
+ }).reduce((best, candidate) => best === null || candidate.amount < best.amount ? candidate : best, null);
29080
+ return lowest ? formatBookingPriceDisplay({
29081
+ kind: "from",
29082
+ amount: lowest.amount,
29083
+ currency: lowest.currency
29084
+ }, { decimals: "auto" }) : priceLabel(firstEvent);
29085
+ }
29086
+ function dateTile(event) {
29087
+ if (event.registrationSubject.kind === "wholeSeries" || !event.startsAt) return null;
29088
+ const rail = formatEventCompactDateRail(event.startsAt, { timeZone: event.timeZone });
29089
+ return {
29090
+ month: rail.month,
29091
+ day: rail.day
29092
+ };
29093
+ }
29094
+ /** "Mon 5 Oct": the compact date the chips and the bar share. */
29095
+ function formatBookingShortDate(event) {
29096
+ return formatBookingWhen(event, "day");
29097
+ }
29098
+ function byStart(left, right) {
29099
+ return Date.parse(left.startsAt) - Date.parse(right.startsAt);
29100
+ }
29101
+ function singleEntry(event, input) {
29102
+ const isCourse = event.registrationSubject.kind === "wholeSeries";
29103
+ return {
29104
+ kind: "single",
29105
+ title: event.title,
29106
+ when: formatRegistrationSchedule(event, null, "long"),
29107
+ shortWhen: formatRegistrationSchedule(event),
29108
+ venue: shownVenue(event, input.display),
29109
+ price: priceLabel(event),
29110
+ places: placesLabel(event, input, false),
29111
+ tile: dateTile(event),
29112
+ action: isCourse ? `Book the ${input.noun.label}` : bookingButtonText(input.buttonText)
29113
+ };
29114
+ }
29115
+ function chooseDateEntry(input) {
29116
+ const ordered = [...input.events].sort(byStart);
29117
+ const first = ordered[0];
29118
+ const shown = ordered.slice(0, 4);
29119
+ return {
29120
+ kind: "chooseDate",
29121
+ title: first?.title ?? "",
29122
+ schedule: (first ? formatBookingScheduleSummary(first) : null) ?? (ordered.length === 1 ? "1 date" : `${ordered.length} dates`),
29123
+ venue: first ? shownVenue(first, input.display) : null,
29124
+ price: seriesPriceLabel(ordered),
29125
+ nextDate: first ? formatBookingShortDate(first) : null,
29126
+ dateCount: ordered.length,
29127
+ dates: shown.map((event) => ({
29128
+ selectionId: event.selectionId,
29129
+ label: formatBookingShortDate(event),
29130
+ places: placesLabel(event, input, true),
29131
+ full: isFull(event, input.attendanceMode)
29132
+ })),
29133
+ moreCount: Math.max(0, ordered.length - shown.length),
29134
+ action: "Choose a date"
29135
+ };
29136
+ }
29137
+ /** The card before a booking starts; the server renders this too. */
29138
+ function idleBookingEntry(input) {
29139
+ if (input.event) return singleEntry(input.event, input);
29140
+ const only = input.events.length === 1 ? input.events[0] : void 0;
29141
+ if (only) return singleEntry(only, input);
29142
+ if (getEventSelectionMode(input.events) === "date") return chooseDateEntry(input);
29143
+ return {
29144
+ kind: "chooseEvent",
29145
+ title: input.events[0]?.title ?? "",
29146
+ count: input.events.length,
29147
+ noun: input.noun.pluralLabel,
29148
+ action: "Choose an event"
29149
+ };
29150
+ }
29151
+ //#endregion
29152
+ //#region ../blocks/src/system/blocks/offerings-preview/model.ts
29153
+ const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
29154
+ const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
29155
+ const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
29156
+ {
29157
+ preset: "class",
29158
+ sourceKey: "eventPreset:class",
29159
+ dataKey: "classEvents",
29160
+ label: "Classes"
29161
+ },
29162
+ {
29163
+ preset: "workshop",
29164
+ sourceKey: "eventPreset:workshop",
29165
+ dataKey: "workshopEvents",
29166
+ label: "Workshops"
29167
+ },
29168
+ {
29169
+ preset: "retreat",
29170
+ sourceKey: "eventPreset:retreat",
29171
+ dataKey: "retreatEvents",
29172
+ label: "Retreats"
29173
+ },
29174
+ {
29175
+ preset: "performance",
29176
+ sourceKey: "eventPreset:performance",
29177
+ dataKey: "performanceEvents",
29178
+ label: "Performances"
29179
+ }
29180
+ ];
29181
+ const OFFERINGS_PREVIEW_EVENT_PRESETS = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
29182
+ const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
29183
+ const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
29184
+ Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
29185
+ kind: "eventPreset",
29186
+ preset: definition.preset
29187
+ }]));
29188
+ function isOfferingsPreviewSourceKey(value) {
29189
+ return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
29190
+ }
29191
+ function normalizeOfferingsPreviewGroupOrder(value) {
29192
+ if (value == null) return { kind: "legacy" };
29193
+ if (value.length === 0) return { kind: "default" };
29194
+ const keys = uniqueOfferingsPreviewSourceKeys(value);
29195
+ return keys.length > 0 ? {
29196
+ kind: "custom",
29197
+ keys
29198
+ } : { kind: "default" };
29199
+ }
29200
+ function inferLegacyOfferingsPreviewGroupOrder(overrides) {
29201
+ const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
29202
+ return keys.length >= 2 ? keys : [];
29203
+ }
29204
+ function normalizeOfferingsPreviewContentForWrite(value) {
29205
+ if (!isRecord$3(value)) return null;
29206
+ const groupOrderValue = value.groupOrder;
29207
+ if (Array.isArray(groupOrderValue)) {
29208
+ const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
29209
+ return {
29210
+ ...value,
29211
+ groupOrder: normalized.kind === "custom" ? normalized.keys : []
29212
+ };
29213
+ }
29214
+ if (groupOrderValue != null) return {
29215
+ ...value,
29216
+ groupOrder: []
29217
+ };
29218
+ const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$3) : []);
29219
+ return legacyOrder.length > 0 ? {
29220
+ ...value,
29221
+ groupOrder: legacyOrder
29222
+ } : value;
29223
+ }
29224
+ /** Which offerings a site preset row names; one owner for "the course preset means whole-series runs". */
29225
+ function sourceFromPresetSummary(preset) {
29226
+ if (preset.engine === "event_series" && preset.canonicalBehaviour === "course") return { kind: "wholeSeries" };
29227
+ if (preset.engine !== "event_series") return null;
29228
+ if (isOfferingsPreviewEventPreset(preset.canonicalBehaviour)) return {
29229
+ kind: "eventPreset",
29230
+ preset: preset.canonicalBehaviour
29231
+ };
29232
+ return null;
29233
+ }
29234
+ function uniqueOfferingsPreviewSourceKeys(values) {
29235
+ const keys = [];
29236
+ for (const value of values) {
29237
+ if (!isOfferingsPreviewSourceKey(value)) continue;
29238
+ if (!keys.includes(value)) keys.push(value);
29239
+ }
29240
+ return keys;
29241
+ }
29242
+ function isRecord$3(value) {
29243
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29244
+ }
29245
+ function isOfferingsPreviewEventPreset(value) {
29246
+ return typeof value === "string" && OFFERINGS_PREVIEW_EVENT_PRESETS.includes(value);
29247
+ }
29248
+ //#endregion
29249
+ //#region ../blocks/src/system/runtime/nodes/event-registration/offeringNoun.ts
29250
+ function offeringKindForEvent(event) {
29251
+ return event.registrationSubject.kind === "wholeSeries" ? "course" : event.eventPreset ?? "event";
29252
+ }
29253
+ function presetKind(preset) {
29254
+ const source = sourceFromPresetSummary(preset);
29255
+ if (!source) return null;
29256
+ return source.kind === "wholeSeries" ? "course" : source.preset;
29257
+ }
29258
+ /**
29259
+ * What the site calls this kind of offering, in lower case for use mid-sentence
29260
+ * ("Book the course", "3 workshops"). Site preset labels win; the canonical
29261
+ * name stands in when the site has none.
29262
+ */
29263
+ function offeringNounForEvent(event, presets) {
29264
+ if (!event) return lowerCased(EVENT_PRESET_VOCABULARY.event);
29265
+ const kind = offeringKindForEvent(event);
29266
+ const preset = presets?.find((candidate) => presetKind(candidate) === kind);
29267
+ return lowerCased(preset?.label.trim() && preset.pluralLabel.trim() ? preset : EVENT_PRESET_VOCABULARY[kind]);
29268
+ }
29269
+ function lowerCased(noun) {
29270
+ return {
29271
+ label: noun.label.trim().toLocaleLowerCase(),
29272
+ pluralLabel: noun.pluralLabel.trim().toLocaleLowerCase()
29273
+ };
29274
+ }
29275
+ //#endregion
29276
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
29277
+ function toRegistrationEvent(event) {
29278
+ return {
29279
+ ...event,
29280
+ selectionId: event.id,
29281
+ registrationSubject: {
29282
+ kind: "occurrence",
29283
+ occurrenceId: event.occurrenceId
29284
+ }
29285
+ };
29286
+ }
29287
+ function toRegistrationEvents(events) {
29288
+ return events.filter((event) => event.bookingScope === "single_occurrence").map(toRegistrationEvent);
29289
+ }
29290
+ /**
29291
+ * Project one whole-series run onto the registration surface.
29292
+ *
29293
+ * Capacity, pricing, payment terms and consent come straight off the canonical
29294
+ * Event record; only the schedule is widened from the single occurrence to the
29295
+ * live run. A run without live sessions has no registration event.
29296
+ */
29297
+ function toWholeSeriesRegistrationEvent(group) {
29298
+ const { representative, run } = group;
29299
+ const sessions = orderedLiveRunSessions(run.sessions);
29300
+ const firstSession = sessions[0];
29301
+ if (!firstSession) return null;
29302
+ const lastSession = sessions[sessions.length - 1] ?? firstSession;
29303
+ return {
29304
+ ...representative,
29305
+ selectionId: run.eventSeriesId,
29306
+ registrationSubject: {
29307
+ kind: "wholeSeries",
29308
+ eventSeriesId: run.eventSeriesId,
29309
+ sessions
29310
+ },
29311
+ startsAt: firstSession.startsAt,
29312
+ endsAt: lastSession.endsAt,
29313
+ timeZone: firstSession.timeZone
29314
+ };
29315
+ }
29316
+ function toWholeSeriesRegistrationEvents(events) {
29317
+ return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
29318
+ }
29319
+ //#endregion
29320
+ //#region ../blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.ts
29321
+ /**
29322
+ * The booking card the server renders on CMS pages (issue 1581), and the
29323
+ * offering noun it resolves for the wizard. Kept beside the island contract
29324
+ * so the server and the client agree on one card.
29325
+ */
29326
+ const bookingHostSchema = z.enum(["overlay", "inPlace"]);
29327
+ const tileSchema = z.object({
29328
+ month: z.string(),
29329
+ day: z.string()
29330
+ }).nullable();
29331
+ const idleBookingEntrySchema = z.discriminatedUnion("kind", [
29332
+ z.object({
29333
+ kind: z.literal("single"),
29334
+ title: z.string(),
29335
+ when: z.string().nullable(),
29336
+ shortWhen: z.string().nullable(),
29337
+ venue: z.string().nullable(),
29338
+ price: z.string().nullable(),
29339
+ places: z.string().nullable(),
29340
+ tile: tileSchema,
29341
+ action: z.string()
29342
+ }),
29343
+ z.object({
29344
+ kind: z.literal("chooseDate"),
29345
+ title: z.string(),
29346
+ schedule: z.string(),
29347
+ venue: z.string().nullable(),
29348
+ price: z.string().nullable(),
29349
+ nextDate: z.string().nullable(),
29350
+ dateCount: z.number(),
29351
+ dates: z.array(z.object({
29352
+ selectionId: z.string(),
29353
+ label: z.string(),
29354
+ places: z.string().nullable(),
29355
+ full: z.boolean()
29356
+ })),
29357
+ moreCount: z.number(),
29358
+ action: z.string()
29359
+ }),
29360
+ z.object({
29361
+ kind: z.literal("chooseEvent"),
29362
+ title: z.string(),
29363
+ count: z.number(),
29364
+ noun: z.string(),
29365
+ action: z.string()
29366
+ })
29367
+ ]);
29368
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
29369
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
29370
+ }
29371
+ function scopedRegistrationEvents(source, scopedSourceEvents) {
29372
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
29373
+ return {
29374
+ scopedEvents,
29375
+ preSelected: findPreSelectedEvent({
29376
+ scopedEvents,
29377
+ occurrenceContext: source.occurrenceContext
29378
+ })
29379
+ };
29380
+ }
29381
+ function nounFor(scoped, presets) {
29382
+ return offeringNounForEvent(scoped.preSelected ?? scoped.scopedEvents[0] ?? null, presets);
29383
+ }
29384
+ /** What the site calls the offering this block books, from its preset rows. */
29385
+ function resolveOfferingNoun(source, scopedSourceEvents) {
29386
+ return nounFor(scopedRegistrationEvents(source, scopedSourceEvents), source.bookingOfferingPresets);
29387
+ }
29388
+ function idleEntry(source, scopedSourceEvents, display) {
29389
+ const scoped = scopedRegistrationEvents(source, scopedSourceEvents);
29390
+ if (scoped.scopedEvents.length === 0) return null;
29391
+ return idleBookingEntry({
29392
+ events: scoped.scopedEvents,
29393
+ event: withOccurrenceTitle(scoped.preSelected, source.occurrenceContext),
29394
+ display,
29395
+ buttonText: source.buttonText,
29396
+ noun: nounFor(scoped, source.bookingOfferingPresets),
29397
+ attendanceMode: null
29398
+ });
29399
+ }
29400
+ /** The server's card on CMS pages, or null where the shell still applies. */
29401
+ function bookingCardDisplay(source, scopedSourceEvents, display) {
29402
+ if ((source.host ?? "overlay") !== "overlay") return null;
29403
+ const entry = idleEntry(source, scopedSourceEvents, display);
29404
+ if (!entry) return null;
29405
+ return {
29406
+ kind: "event-registration",
29407
+ state: "booking-card",
29408
+ className: source.className ?? null,
29409
+ entry,
29410
+ buttonVariant: source.buttonVariant ?? null,
29411
+ buttonText: source.buttonText?.trim() ?? ""
29412
+ };
29413
+ }
29414
+ const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
29415
+ kind: z.literal("unsupported"),
29416
+ reason: z.enum([
29417
+ "unsupportedSubject",
29418
+ "invalidEventSeriesId",
29419
+ "invalidEventSeriesBookingScope",
29420
+ "invalidEventOfferingId",
29421
+ "invalidEventOfferingBookingScope",
29422
+ "invalidEventOfferingCatalogue"
29423
+ ])
29424
+ });
29425
+ const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
29426
+ const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
29427
+ if (unsupported.success) return unsupported.data;
29428
+ const parsed = parseEventPageSurfaceSubject(value);
29429
+ if (parsed.ok) return parsed.value;
29430
+ ctx.addIssue({
29431
+ code: z.ZodIssueCode.custom,
29432
+ message: `Invalid event page-surface subject (${parsed.error.kind})`
29433
+ });
29434
+ return z.NEVER;
29435
+ });
29436
+ function resolveEventRegistrationScope(context) {
29437
+ const occurrenceSeriesId = context.occurrenceContext?.seriesId;
29438
+ if (occurrenceSeriesId) return {
29439
+ kind: "occurrenceSeries",
29440
+ seriesId: occurrenceSeriesId
29441
+ };
29442
+ if (context.subject?.kind === "eventSeries") return {
29443
+ kind: "pageSurfaceSeries",
29444
+ seriesId: context.subject.eventSeriesId
29445
+ };
29446
+ if (context.subject?.kind === "eventOffering") return {
29447
+ kind: "pageSurfaceOffering",
29448
+ offeringId: context.subject.eventOfferingId,
29449
+ seriesIds: context.subject.catalogueEventSeriesIds
29450
+ };
29451
+ if (context.subject?.kind === "unsupported") return {
29452
+ kind: "unsupportedPageSurfaceSubject",
29453
+ reason: context.subject.reason
29454
+ };
29455
+ return { kind: "unscoped" };
29456
+ }
29457
+ function resolveEventRegistrationScopedEvents(events, context) {
29458
+ const scope = resolveEventRegistrationScope(context);
29459
+ switch (scope.kind) {
29460
+ case "occurrenceSeries":
29461
+ case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
29462
+ case "pageSurfaceOffering": {
29463
+ const seriesIds = new Set(scope.seriesIds);
29464
+ return events.filter((event) => seriesIds.has(event.seriesId));
29465
+ }
29466
+ case "unsupportedPageSurfaceSubject": return [];
29467
+ case "unscoped": return [...events];
29468
+ }
29469
+ }
29470
+ //#endregion
29471
+ //#region ../blocks/src/system/constants/events.ts
29472
+ const eventCalendarDisplayModeValues = [
29473
+ "combined",
29474
+ "calendar",
29475
+ "list",
29476
+ "timetable",
29477
+ "week-board"
29478
+ ];
29479
+ const eventCalendarDefaultViewValues = ["month", "week"];
29480
+ const eventCalendarListLayoutValues = [
29481
+ "stack",
29482
+ "grid",
29483
+ "compact"
29484
+ ];
29485
+ const arrangementOnlyListLayouts = ["ledger", "day-groups"];
29486
+ const eventListArrangementValues = [...eventCalendarListLayoutValues, ...arrangementOnlyListLayouts];
29487
+ const eventCalendarListLayoutOptions = [
29488
+ {
29489
+ id: "stack",
29490
+ label: "Stack"
29491
+ },
29492
+ {
29493
+ id: "grid",
29494
+ label: "Grid"
29495
+ },
29496
+ {
29497
+ id: "compact",
29498
+ label: "Compact"
29499
+ }
29500
+ ];
29501
+ function isEventCalendarDisplayMode(value) {
29502
+ return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
29503
+ }
29504
+ function normalizeEventCalendarDisplayMode$1(input) {
29505
+ if (isEventCalendarDisplayMode(input.variant)) return input.variant;
29506
+ return isEventCalendarDisplayMode(input.displayMode) ? input.displayMode : void 0;
29507
+ }
29508
+ /** One closed authority for the projections required by each rendered mode. */
29509
+ function resolveEventCalendarReadPlan(input) {
29510
+ const mode = normalizeEventCalendarDisplayMode$1(input) ?? "combined";
29511
+ switch (mode) {
29512
+ case "combined": return {
29513
+ mode,
28434
29514
  jobs: "both",
28435
29515
  readProfile: "listing-card"
28436
29516
  };
@@ -28510,95 +29590,6 @@ const calendarTabViewsSchema = z.enum([
28510
29590
  "week"
28511
29591
  ]);
28512
29592
  //#endregion
28513
- //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
28514
- /**
28515
- * Determine which wizard steps to show based on context and payment requirements.
28516
- *
28517
- * @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
28518
- * @param needsPaymentStep - Whether user must complete payment method selection
28519
- * @returns Array of wizard steps to display
28520
- */
28521
- function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep, paymentActive = false) {
28522
- const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
28523
- "select-event",
28524
- "select-tickets",
28525
- "enter-details"
28526
- ];
28527
- if (!needsPaymentStep) return baseSteps;
28528
- return paymentActive ? [
28529
- ...baseSteps,
28530
- "select-payment",
28531
- "payment"
28532
- ] : [...baseSteps, "select-payment"];
28533
- }
28534
- //#endregion
28535
- //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
28536
- function eventMatchesOccurrenceId(event, occurrenceId) {
28537
- return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
28538
- }
28539
- function findPreSelectedEvent(input) {
28540
- const occurrenceContext = input.occurrenceContext;
28541
- if (!occurrenceContext) return input.scopedEvents.length === 1 ? input.scopedEvents[0] ?? null : null;
28542
- const contextDate = getBookingLocalDateKey(toScheduledBookingDateTime({
28543
- startsAt: occurrenceContext.startsAt,
28544
- timeZone: occurrenceContext.timeZone
28545
- }));
28546
- return input.scopedEvents.find((event) => eventMatchesOccurrenceId(event, occurrenceContext.id) || getBookingLocalDateKey(toScheduledBookingDateTime({
28547
- startsAt: event.startsAt,
28548
- timeZone: event.timeZone
28549
- })) === contextDate) ?? null;
28550
- }
28551
- function resolveEventRequiresPayment(input) {
28552
- if (input.eligibility) return input.eligibility.requiresPayment;
28553
- if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
28554
- if (!input.event) return false;
28555
- return publicEventPricingType(input.event.pricing) === "paid";
28556
- }
28557
- //#endregion
28558
- //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
28559
- function toRegistrationEvent(event) {
28560
- return {
28561
- ...event,
28562
- selectionId: event.id,
28563
- registrationSubject: {
28564
- kind: "occurrence",
28565
- occurrenceId: event.occurrenceId
28566
- }
28567
- };
28568
- }
28569
- function toRegistrationEvents(events) {
28570
- return events.filter((event) => event.bookingScope === "single_occurrence").map(toRegistrationEvent);
28571
- }
28572
- /**
28573
- * Project one whole-series run onto the registration surface.
28574
- *
28575
- * Capacity, pricing, payment terms and consent come straight off the canonical
28576
- * Event record; only the schedule is widened from the single occurrence to the
28577
- * live run. A run without live sessions has no registration event.
28578
- */
28579
- function toWholeSeriesRegistrationEvent(group) {
28580
- const { representative, run } = group;
28581
- const sessions = orderedLiveRunSessions(run.sessions);
28582
- const firstSession = sessions[0];
28583
- if (!firstSession) return null;
28584
- const lastSession = sessions[sessions.length - 1] ?? firstSession;
28585
- return {
28586
- ...representative,
28587
- selectionId: run.eventSeriesId,
28588
- registrationSubject: {
28589
- kind: "wholeSeries",
28590
- eventSeriesId: run.eventSeriesId,
28591
- sessions
28592
- },
28593
- startsAt: firstSession.startsAt,
28594
- endsAt: lastSession.endsAt,
28595
- timeZone: firstSession.timeZone
28596
- };
28597
- }
28598
- function toWholeSeriesRegistrationEvents(events) {
28599
- return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
28600
- }
28601
- //#endregion
28602
29593
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.ts
28603
29594
  const { limit: _rawLimit, eventSurface: _rawEventSurface, scheduleScope: _rawScheduleScope, detailLevel: _rawDetailLevel, ...stringEventQueryShape } = publicEventLoaderQuerySchemaShape;
28604
29595
  const eventRecoveryRequestSchema = z.object({
@@ -28641,27 +29632,17 @@ function parseEventLoaderRecoveryRequest(value) {
28641
29632
  const EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS = [
28642
29633
  "Date",
28643
29634
  "Tickets",
28644
- "Details"
28645
- ];
28646
- const EVENT_REGISTRATION_SHELL_STEP_LABELS = [
28647
- "Event",
28648
- "Date",
28649
- "Tickets",
28650
- "Details",
28651
- "Payment options",
28652
- "Payment"
29635
+ "Your details"
28653
29636
  ];
28654
29637
  const eventRegistrationPublicEventSchema = z.preprocess(normalizeEventRegistrationEventInput, publicEventSchema);
28655
- const eventRegistrationHydrationSchemaShape = {
29638
+ const eventRegistrationCommonSchemaShape = {
28656
29639
  demoEmailSuppressed: z.boolean().optional(),
28657
29640
  occurrenceContext: occurrenceContextSchema.optional(),
28658
29641
  subject: eventRegistrationPageSurfaceSubjectSchema.nullable().optional(),
28659
29642
  maxTickets: z.string().optional(),
28660
- showVenue: z.boolean().optional(),
28661
- showCapacity: z.boolean().optional(),
28662
29643
  successMessage: z.string().optional(),
28663
29644
  buttonVariant: registrationButtonVariantSchema.optional(),
28664
- overlayEnabled: z.boolean().optional(),
29645
+ host: bookingHostSchema.optional(),
28665
29646
  spamProtectionEnabled: z.boolean().optional(),
28666
29647
  supportEmail: z.string().optional(),
28667
29648
  sitePasses: publicPassProductCollectionSchema.nullable().optional(),
@@ -28686,32 +29667,51 @@ const eventRegistrationSourceSchema = z.object({
28686
29667
  portalToken: z.string().nullable().optional(),
28687
29668
  previewToken: z.string().nullable().optional(),
28688
29669
  readAuthority: browserReadAuthoritySchema.optional(),
28689
- ...eventRegistrationHydrationSchemaShape,
29670
+ ...eventRegistrationCommonSchemaShape,
29671
+ showVenue: z.boolean().optional(),
29672
+ showCapacity: z.boolean().optional(),
28690
29673
  events: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
28691
29674
  wholeSeriesRunEvents: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
28692
29675
  eventLoaderFailures: z.record(z.string(), z.unknown()).nullable().optional(),
28693
29676
  className: z.string().nullable().optional(),
28694
29677
  buttonText: z.string().nullable().optional(),
28695
29678
  maxTickets: z.string().nullable().optional(),
28696
- stage: previewStageSchema.optional()
29679
+ stage: previewStageSchema.optional(),
29680
+ bookingOfferingPresets: z.array(z.custom((value) => typeof value === "object" && value !== null && typeof value.label === "string")).nullable().optional()
28697
29681
  });
28698
- const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [z.object({
28699
- kind: z.literal("event-registration"),
28700
- state: z.literal("generic-shell"),
28701
- className: z.string().nullable(),
28702
- titleText: z.string(),
28703
- bodyText: z.string(),
28704
- stepLabels: z.array(z.enum(EVENT_REGISTRATION_SHELL_STEP_LABELS)).min(1),
28705
- summaryButtonText: z.string()
28706
- }), z.object({
28707
- kind: z.literal("event-registration"),
28708
- state: z.literal("registration-closed"),
28709
- className: z.string().nullable(),
28710
- titleText: z.string(),
28711
- bodyText: z.string()
28712
- })]);
29682
+ const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [
29683
+ z.object({
29684
+ kind: z.literal("event-registration"),
29685
+ state: z.literal("generic-shell"),
29686
+ className: z.string().nullable(),
29687
+ titleText: z.string(),
29688
+ bodyText: z.string(),
29689
+ stepLabels: z.array(z.enum(REGISTRATION_STEP_LABELS)).min(1),
29690
+ summaryButtonText: z.string()
29691
+ }),
29692
+ z.object({
29693
+ kind: z.literal("event-registration"),
29694
+ state: z.literal("registration-closed"),
29695
+ className: z.string().nullable(),
29696
+ titleText: z.string(),
29697
+ bodyText: z.string()
29698
+ }),
29699
+ z.object({
29700
+ kind: z.literal("event-registration"),
29701
+ state: z.literal("booking-card"),
29702
+ className: z.string().nullable(),
29703
+ entry: idleBookingEntrySchema,
29704
+ buttonVariant: registrationButtonVariantSchema.nullable(),
29705
+ buttonText: z.string()
29706
+ })
29707
+ ]);
28713
29708
  const eventRegistrationHydrationSchema = z.object({
28714
- ...eventRegistrationHydrationSchemaShape,
29709
+ ...eventRegistrationCommonSchemaShape,
29710
+ display: registrationDisplaySchema,
29711
+ offeringNoun: z.object({
29712
+ label: z.string(),
29713
+ pluralLabel: z.string()
29714
+ }).optional(),
28715
29715
  eventSource: eventRegistrationEventSourceSchema
28716
29716
  });
28717
29717
  const eventRegistrationRenderSchema = z.object({
@@ -28726,7 +29726,7 @@ z.object({
28726
29726
  readAuthority: browserReadAuthoritySchema.optional(),
28727
29727
  stage: previewStageSchema.optional()
28728
29728
  });
28729
- function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
29729
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display) {
28730
29730
  const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
28731
29731
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
28732
29732
  kind: "event-registration",
@@ -28735,6 +29735,8 @@ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvent
28735
29735
  titleText: "Registration closed",
28736
29736
  bodyText: "This run is no longer open for bookings."
28737
29737
  };
29738
+ const card = bookingCardDisplay(source, scopedSourceEvents, display);
29739
+ if (card) return card;
28738
29740
  const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
28739
29741
  return {
28740
29742
  kind: "event-registration",
@@ -28751,11 +29753,14 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
28751
29753
  if (selectedRun) {
28752
29754
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
28753
29755
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
28754
- return getWizardStepsForContext(true, resolveEventRequiresPayment({
28755
- eligibility: null,
28756
- selectedTicketType: null,
28757
- event: selectedEvent
28758
- })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
29756
+ return shellStepLabels(registrationRoute({
29757
+ chooseEvent: false,
29758
+ payment: resolveEventRequiresPayment({
29759
+ eligibility: null,
29760
+ selectedTicketType: null,
29761
+ event: selectedEvent
29762
+ }) ? "optionsAndPayment" : "none"
29763
+ }), registrationStepLabelContext([selectedEvent], selectedEvent));
28759
29764
  }
28760
29765
  const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
28761
29766
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -28764,14 +29769,15 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
28764
29769
  occurrenceContext: source.occurrenceContext
28765
29770
  });
28766
29771
  const hasPreSelectedContext = Boolean(source.occurrenceContext || preSelectedEvent);
28767
- return getWizardStepsForContext(hasPreSelectedContext, hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
29772
+ const needsPaymentStep = hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
28768
29773
  eligibility: null,
28769
29774
  selectedTicketType: null,
28770
29775
  event: preSelectedEvent
28771
- }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
28772
- }
28773
- function toScopedEventRegistrationEvents(scopedSourceEvents) {
28774
- return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
29776
+ }) : false;
29777
+ return shellStepLabels(registrationRoute({
29778
+ chooseEvent: !hasPreSelectedContext,
29779
+ payment: needsPaymentStep ? "optionsAndPayment" : "none"
29780
+ }), registrationStepLabelContext(scopedEvents, preSelectedEvent));
28775
29781
  }
28776
29782
  function scopeEventRegistrationSourceEvents(source, eventSource) {
28777
29783
  return resolveEventRegistrationScopedEvents(eventSource.kind === "loaded" ? eventSource.events : [], {
@@ -28787,29 +29793,12 @@ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
28787
29793
  const runs = groupPublicEventsByRun(scopedSourceEvents);
28788
29794
  return runs.length === 1 ? runs[0] : null;
28789
29795
  }
28790
- function shellLabelForRegistrationStep(step, events) {
28791
- switch (step) {
28792
- case "select-event": return isDateSelection(events) ? "Date" : "Event";
28793
- case "select-tickets": return "Tickets";
28794
- case "enter-details": return "Details";
28795
- case "select-payment": return "Payment options";
28796
- case "payment": return "Payment";
28797
- case "error":
28798
- case "success":
28799
- case "submitting":
28800
- case "verifying-payment":
28801
- case "verifying-timeout":
28802
- case "payment-status":
28803
- case "payment-failed": return "Details";
28804
- }
28805
- }
28806
- function isDateSelection(events) {
28807
- const firstSeriesId = events[0]?.seriesId;
28808
- return Boolean(events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId));
29796
+ function shellStepLabels(route, context) {
29797
+ return route.map((step) => registrationStepLabel(step, context));
28809
29798
  }
28810
29799
  function buildEventRegistrationShellBodyText(stepLabels) {
28811
- if (stepLabels.includes("Payment options")) return "Preparing tickets, details, and payment options.";
28812
- if (stepLabels[0] === "Tickets") return "Preparing tickets and booking details.";
29800
+ if (stepLabels.includes("How you’ll pay")) return "Preparing tickets, details, and payment options.";
29801
+ if (stepLabels[0] === "Tickets" || stepLabels[0] === "Places") return "Preparing tickets and booking details.";
28813
29802
  return "Preparing dates, tickets, and booking details.";
28814
29803
  }
28815
29804
  const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
@@ -28819,6 +29808,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
28819
29808
  render: createZodCodec(eventRegistrationRenderSchema)
28820
29809
  },
28821
29810
  buildRender: (source) => {
29811
+ const display = resolveRegistrationDisplay(source);
28822
29812
  const eventSource = resolveEventRegistrationEventSource({
28823
29813
  events: source.events,
28824
29814
  wholeSeriesRunEvents: source.wholeSeriesRunEvents,
@@ -28826,7 +29816,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
28826
29816
  });
28827
29817
  const scopedSourceEvents = scopeEventRegistrationSourceEvents(source, eventSource);
28828
29818
  return {
28829
- display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
29819
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display),
28830
29820
  hydration: {
28831
29821
  occurrenceContext: source.occurrenceContext ?? null,
28832
29822
  subject: source.subject ?? null,
@@ -28836,29 +29826,29 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
28836
29826
  } : eventSource,
28837
29827
  demoEmailSuppressed: source.demoEmailSuppressed,
28838
29828
  maxTickets: source.maxTickets ?? void 0,
28839
- showVenue: source.showVenue,
28840
- showCapacity: source.showCapacity,
29829
+ display,
28841
29830
  successMessage: source.successMessage,
28842
29831
  buttonVariant: source.buttonVariant,
28843
- overlayEnabled: source.overlayEnabled,
29832
+ host: source.host ?? "overlay",
28844
29833
  spamProtectionEnabled: source.spamProtectionEnabled,
28845
29834
  supportEmail: source.supportEmail,
28846
29835
  sitePasses: source.sitePasses ?? null,
28847
29836
  siteMemberships: source.siteMemberships ?? null,
28848
29837
  mailingList: source.mailingList,
28849
- siteName: source.siteName
29838
+ siteName: source.siteName,
29839
+ offeringNoun: resolveOfferingNoun(source, scopedSourceEvents)
28850
29840
  }
28851
29841
  };
28852
29842
  }
28853
29843
  });
28854
29844
  function normalizeEventRegistrationEventInput(value) {
28855
- if (!isRecord$3(value)) return value;
29845
+ if (!isRecord$2(value)) return value;
28856
29846
  return {
28857
29847
  ...value,
28858
29848
  destination: resolveRuntimeEventDestination(value)
28859
29849
  };
28860
29850
  }
28861
- function isRecord$3(value) {
29851
+ function isRecord$2(value) {
28862
29852
  return typeof value === "object" && value !== null && !Array.isArray(value);
28863
29853
  }
28864
29854
  const eventRegistrationBlockDefinition = {
@@ -28898,7 +29888,7 @@ const eventRegistrationBlockDefinition = {
28898
29888
  description: "Main heading for the registration form",
28899
29889
  required: false,
28900
29890
  multiline: false,
28901
- defaultValue: "Register for this event"
29891
+ defaultValue: ""
28902
29892
  },
28903
29893
  {
28904
29894
  id: "description",
@@ -28970,11 +29960,11 @@ const eventRegistrationBlockDefinition = {
28970
29960
  {
28971
29961
  id: "buttonText",
28972
29962
  type: "text",
28973
- label: "Submit button text",
28974
- description: "Text for the registration submit button",
29963
+ label: "Button text",
29964
+ description: "Text on the booking button",
28975
29965
  required: false,
28976
29966
  multiline: false,
28977
- defaultValue: "Complete Registration",
29967
+ defaultValue: "Book your place",
28978
29968
  maxLength: 40
28979
29969
  },
28980
29970
  createButtonVariantField()
@@ -29006,21 +29996,21 @@ const eventRegistrationBlockDefinition = {
29006
29996
  successMessage: { $bind: { from: "content.successMessage" } },
29007
29997
  buttonText: { $bind: {
29008
29998
  from: "content.buttonText",
29009
- fallback: "Complete Registration"
29999
+ fallback: "Book your place"
29010
30000
  } },
29011
30001
  buttonVariant: { $bind: {
29012
30002
  from: "content.buttonVariant",
29013
30003
  fallback: "primary"
29014
30004
  } },
29015
- overlayEnabled: { $bind: {
29016
- from: "$root.eventRegistrationOverlayEnabled",
29017
- fallback: false
29018
- } },
29019
30005
  events: { $bind: { from: "data.events" } },
29020
30006
  wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
29021
30007
  loaderFailures: { $bind: { from: "$root.loaderFailures" } },
29022
30008
  mailingList: { $bind: { from: "$root.mailingList" } },
29023
30009
  siteName: { $bind: { from: "$root.siteName" } },
30010
+ bookingOfferingPresets: { $bind: {
30011
+ from: "$root.bookingOfferingPresets",
30012
+ fallback: []
30013
+ } },
29024
30014
  sitePasses: { $bind: { from: "data.sitePasses" } },
29025
30015
  siteMemberships: { $bind: { from: "data.siteMemberships" } },
29026
30016
  stage: { $bind: {
@@ -31656,7 +32646,21 @@ const eventCalendarBlockDefinition = {
31656
32646
  list: { label: "List" }
31657
32647
  }
31658
32648
  }, eventCalendarContentShape),
31659
- normalizeContent: normalizeEventCalendarContent,
32649
+ contentNormalizer: {
32650
+ fieldIds: [
32651
+ "variant",
32652
+ "displayMode",
32653
+ "listLayout",
32654
+ "defaultCalendarView",
32655
+ "eventSurface",
32656
+ "filterEventPresets",
32657
+ "scheduleScope",
32658
+ "filterCategoryIds",
32659
+ "filterStaffMemberId",
32660
+ "filterVenueId"
32661
+ ],
32662
+ normalize: normalizeEventCalendarContent
32663
+ },
31660
32664
  dataSchemas: {
31661
32665
  events: publicEventsArraySchema.optional(),
31662
32666
  calendarWindow: calendarWindowPageSchema.optional()
@@ -31905,90 +32909,6 @@ const recurringScheduleBlockDefinition = {
31905
32909
  } }
31906
32910
  };
31907
32911
  //#endregion
31908
- //#region ../blocks/src/system/blocks/offerings-preview/model.ts
31909
- const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
31910
- const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
31911
- const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
31912
- {
31913
- preset: "class",
31914
- sourceKey: "eventPreset:class",
31915
- dataKey: "classEvents",
31916
- label: "Classes"
31917
- },
31918
- {
31919
- preset: "workshop",
31920
- sourceKey: "eventPreset:workshop",
31921
- dataKey: "workshopEvents",
31922
- label: "Workshops"
31923
- },
31924
- {
31925
- preset: "retreat",
31926
- sourceKey: "eventPreset:retreat",
31927
- dataKey: "retreatEvents",
31928
- label: "Retreats"
31929
- },
31930
- {
31931
- preset: "performance",
31932
- sourceKey: "eventPreset:performance",
31933
- dataKey: "performanceEvents",
31934
- label: "Performances"
31935
- }
31936
- ];
31937
- OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
31938
- const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
31939
- const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
31940
- Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
31941
- kind: "eventPreset",
31942
- preset: definition.preset
31943
- }]));
31944
- function isOfferingsPreviewSourceKey(value) {
31945
- return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
31946
- }
31947
- function normalizeOfferingsPreviewGroupOrder(value) {
31948
- if (value == null) return { kind: "legacy" };
31949
- if (value.length === 0) return { kind: "default" };
31950
- const keys = uniqueOfferingsPreviewSourceKeys(value);
31951
- return keys.length > 0 ? {
31952
- kind: "custom",
31953
- keys
31954
- } : { kind: "default" };
31955
- }
31956
- function inferLegacyOfferingsPreviewGroupOrder(overrides) {
31957
- const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
31958
- return keys.length >= 2 ? keys : [];
31959
- }
31960
- function normalizeOfferingsPreviewContentForWrite(value) {
31961
- if (!isRecord$2(value)) return null;
31962
- const groupOrderValue = value.groupOrder;
31963
- if (Array.isArray(groupOrderValue)) {
31964
- const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
31965
- return {
31966
- ...value,
31967
- groupOrder: normalized.kind === "custom" ? normalized.keys : []
31968
- };
31969
- }
31970
- if (groupOrderValue != null) return {
31971
- ...value,
31972
- groupOrder: []
31973
- };
31974
- const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$2) : []);
31975
- return legacyOrder.length > 0 ? {
31976
- ...value,
31977
- groupOrder: legacyOrder
31978
- } : value;
31979
- }
31980
- function uniqueOfferingsPreviewSourceKeys(values) {
31981
- const keys = [];
31982
- for (const value of values) {
31983
- if (!isOfferingsPreviewSourceKey(value)) continue;
31984
- if (!keys.includes(value)) keys.push(value);
31985
- }
31986
- return keys;
31987
- }
31988
- function isRecord$2(value) {
31989
- return typeof value === "object" && value !== null && !Array.isArray(value);
31990
- }
31991
- //#endregion
31992
32912
  //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
31993
32913
  const groupOverrideFields = [
31994
32914
  {
@@ -32352,7 +33272,22 @@ const offeringsPreviewEventDataSchemas = Object.fromEntries(OFFERINGS_PREVIEW_EV
32352
33272
  const offeringsPreviewEventDataLoaders = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, eventPresetLoader(definition.preset)]));
32353
33273
  const offeringsPreviewBlockDefinition = {
32354
33274
  manifest: offeringsPreviewManifest,
32355
- normalizeContent: normalizeOfferingsPreviewBlockContent,
33275
+ contentNormalizer: {
33276
+ fieldIds: [
33277
+ "displayMode",
33278
+ "heading",
33279
+ "description",
33280
+ "itemLimit",
33281
+ "groupOrder",
33282
+ "groupOverrides",
33283
+ "_cardStyles",
33284
+ "buttonVariant",
33285
+ "buttonText",
33286
+ "emptyMessage",
33287
+ "_sectionStyles"
33288
+ ],
33289
+ normalize: normalizeOfferingsPreviewBlockContent
33290
+ },
32356
33291
  dataSchemas: {
32357
33292
  ...offeringsPreviewEventDataSchemas,
32358
33293
  routeCandidates: publicRouteCandidatesSchema.optional(),
@@ -35950,11 +36885,11 @@ function buildNewsletterDisplay(source) {
35950
36885
  successMessage: DEFAULT_MAILING_LIST_SUCCESS_MESSAGE,
35951
36886
  privacyNote: null
35952
36887
  };
35953
- const targeting = source.targeting ?? { mode: "default" };
35954
- const choices = resolveRuntimeChoices(context, targeting);
36888
+ const assessment = assessMailingListTargeting(context, source.targeting ?? { mode: "default" });
36889
+ const choices = resolveRuntimeChoices(assessment);
35955
36890
  return {
35956
36891
  kind: "newsletter-form",
35957
- availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
36892
+ availability: assessment.tag === "available" ? "ready" : "unavailable",
35958
36893
  className: source.className ?? null,
35959
36894
  buttonLabel: form.buttonLabel,
35960
36895
  successMessage: form.successMessage,
@@ -36011,60 +36946,12 @@ function projectNewsletterFormHydrationTarget(source) {
36011
36946
  default: return assertNever$1(source);
36012
36947
  }
36013
36948
  }
36014
- function resolveRuntimeChoices(context, targeting) {
36015
- if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
36016
- switch (targeting.provider) {
36017
- case "native": {
36018
- const offered = new Set(targeting.selection.offeredTopicIds);
36019
- return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
36020
- id: item.id,
36021
- label: item.name
36022
- })) : [];
36023
- }
36024
- case "brevo": {
36025
- const offered = new Set(targeting.selection.offeredListIds);
36026
- return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
36027
- id: String(item.id),
36028
- label: item.name
36029
- })) : [];
36030
- }
36031
- case "mailchimp": {
36032
- const offered = new Set(targeting.selection.offeredInterestIds);
36033
- 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) => ({
36034
- id: item.id,
36035
- label: item.name
36036
- })) ?? [];
36037
- }
36038
- }
36039
- }
36040
- function runtimeTargetingIsAvailable(context, targeting, choices) {
36041
- if (context?.tag !== "configured") return false;
36042
- const provider = context.provider;
36043
- if (targeting.mode === "default") switch (provider.provider) {
36044
- case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
36045
- case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
36046
- case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
36047
- }
36048
- if (targeting.provider !== provider.provider) return false;
36049
- switch (targeting.provider) {
36050
- case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
36051
- case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
36052
- case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
36053
- }
36054
- }
36055
- function nativeTargetIsAvailable(provider, targeting, choices) {
36056
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
36057
- return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
36058
- }
36059
- function brevoTargetIsAvailable(provider, targeting, choices) {
36060
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
36061
- return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
36062
- }
36063
- function mailchimpTargetIsAvailable(provider, targeting, choices) {
36064
- const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
36065
- if (!audience) return false;
36066
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
36067
- return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
36949
+ function resolveRuntimeChoices(assessment) {
36950
+ if (!("value" in assessment) || assessment.value.mode !== "custom" || !("selectedChoices" in assessment.value) || assessment.value.targeting.selection.tag !== "visitor_choice") return [];
36951
+ return assessment.value.selectedChoices.map((item) => ({
36952
+ id: String(item.id),
36953
+ label: item.name
36954
+ }));
36068
36955
  }
36069
36956
  function parseRequiredMailingListSignupBlockId(value) {
36070
36957
  const parsed = parseMailingListBlockId(value);
@@ -36168,6 +37055,10 @@ const newsletterSignupManifest = createBlockManifest({
36168
37055
  [...(newsletterSignupManifest.fields ?? []).map((field) => field.id)];
36169
37056
  const newsletterSignupBlockDefinition = {
36170
37057
  manifest: newsletterSignupManifest,
37058
+ contentNormalizer: {
37059
+ fieldIds: ["targeting"],
37060
+ normalize: normalizeMailingListBlockContent
37061
+ },
36171
37062
  runtime: newsletterFormBlockRuntime
36172
37063
  };
36173
37064
  //#endregion
@@ -36900,7 +37791,20 @@ const locationMapBlockDefinition = {
36900
37791
  })], { gap: "md" })
36901
37792
  })
36902
37793
  }),
36903
- normalizeContent: normalizeLocationMapContent
37794
+ contentNormalizer: {
37795
+ fieldIds: [
37796
+ "heading",
37797
+ "locationSource",
37798
+ "venueId",
37799
+ "location",
37800
+ "showAddressText",
37801
+ "showDirections",
37802
+ "directionsLabel",
37803
+ "zoom",
37804
+ "heightPx"
37805
+ ],
37806
+ normalize: normalizeLocationMapContent
37807
+ }
36904
37808
  };
36905
37809
  //#endregion
36906
37810
  //#region ../blocks/src/system/blocks/index.ts