@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
@@ -1986,6 +1986,29 @@ function canonicalEventPricingDisplay(pricing) {
1986
1986
  default: return assertNever$2(pricing);
1987
1987
  }
1988
1988
  }
1989
+ /**
1990
+ * The single renderer for a precomputed booking price `display` ADT. This is
1991
+ * the one place booking price variants map to user-facing strings — every call
1992
+ * site (booking entry, events calendar, event/course details, listings, portal)
1993
+ * should route through here so labels never drift. Adding a variant to the ADT
1994
+ * breaks compilation here (via `assertNever`) until it is handled.
1995
+ */
1996
+ function formatBookingPriceDisplay(display, options = {}) {
1997
+ switch (display.kind) {
1998
+ case "free": return "Free";
1999
+ case "exact": return formatCurrencyAmount(display.amount, display.currency, options);
2000
+ case "from": return `From ${formatCurrencyAmount(display.amount, display.currency, options)}`;
2001
+ case "range": return `${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)}`;
2002
+ 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)}`;
2003
+ case "unavailable": return "Pricing unavailable";
2004
+ case "external": return display.label;
2005
+ default: return assertNever$2(display);
2006
+ }
2007
+ }
2008
+ /** Render an event's price directly from its pricing value. */
2009
+ function formatEventPricing(pricing, options = {}) {
2010
+ return formatBookingPriceDisplay(publicEventPricingDisplay(pricing), options);
2011
+ }
1989
2012
  //#endregion
1990
2013
  //#region ../core/src/event-page-surface-subject.ts
1991
2014
  function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
@@ -2129,6 +2152,7 @@ function parseBookingTimeZone(timeZone) {
2129
2152
  }
2130
2153
  //#endregion
2131
2154
  //#region ../core/src/booking-date-time.ts
2155
+ const DEFAULT_LOCALE = "en-US";
2132
2156
  const FALLBACK_TIMEZONE = "UTC";
2133
2157
  const START_OF_DAY = {
2134
2158
  hour: 0,
@@ -2251,6 +2275,34 @@ function toScheduledBookingDateTime(input) {
2251
2275
  if (result.ok) return result.value;
2252
2276
  throw new Error(formatParseError(result));
2253
2277
  }
2278
+ function formatBookingInstant(isoString, options) {
2279
+ const date = new Date(isoString);
2280
+ if (Number.isNaN(date.getTime())) return isoString;
2281
+ return getCachedDateTimeFormatter(options.locale ?? DEFAULT_LOCALE, getSafeTimeZone(options.timeZone), options.format).format(date);
2282
+ }
2283
+ function formatBookingDate(input, format = {
2284
+ weekday: "long",
2285
+ year: "numeric",
2286
+ month: "long",
2287
+ day: "numeric"
2288
+ }) {
2289
+ return formatBookingInstant(input.startsAt, {
2290
+ locale: input.locale,
2291
+ timeZone: input.timeZone,
2292
+ format
2293
+ });
2294
+ }
2295
+ function formatBookingTime(input, format = {
2296
+ hour: "numeric",
2297
+ minute: "2-digit",
2298
+ hour12: true
2299
+ }) {
2300
+ return formatBookingInstant(input.startsAt, {
2301
+ locale: input.locale,
2302
+ timeZone: input.timeZone,
2303
+ format
2304
+ });
2305
+ }
2254
2306
  function getBookingLocalDateKey(input) {
2255
2307
  return formatDateKey$1(getLocalScheduleDate(new Date(input.startsAt), input.timeZone));
2256
2308
  }
@@ -2524,6 +2576,14 @@ function formError(field) {
2524
2576
  }
2525
2577
  //#endregion
2526
2578
  //#region ../core/src/mailing-list-targeting.ts
2579
+ const DEFAULT_MAILING_LIST_TARGETING = { mode: "default" };
2580
+ function mailingListTargetingChoiceIds(targeting) {
2581
+ switch (targeting.provider) {
2582
+ case "native": return targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
2583
+ case "brevo": return targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
2584
+ case "mailchimp": return targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
2585
+ }
2586
+ }
2527
2587
  function planMailingListChoicePresentation(targeting) {
2528
2588
  if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
2529
2589
  switch (targeting.provider) {
@@ -2563,6 +2623,103 @@ function planMailingListChoicePresentation(targeting) {
2563
2623
  }
2564
2624
  }
2565
2625
  //#endregion
2626
+ //#region ../core/src/mailing-list-targeting-availability.ts
2627
+ function resolveMailingListDefaultDestination(provider) {
2628
+ switch (provider.provider) {
2629
+ case "native": return provider.topics.find((item) => item.id === provider.defaultTopicId) ?? null;
2630
+ case "brevo": return provider.lists.find((item) => item.id === provider.defaultListId) ?? null;
2631
+ case "mailchimp": return provider.audiences.find((item) => item.id === provider.defaultAudienceId) ?? null;
2632
+ }
2633
+ }
2634
+ function assessMailingListTargeting(context, targeting) {
2635
+ if (context?.tag !== "configured") return {
2636
+ tag: "unavailable",
2637
+ reason: "setup_missing",
2638
+ targeting
2639
+ };
2640
+ const catalog = context.provider;
2641
+ if (targeting.mode === "custom") return assessCustomTargeting(catalog, targeting);
2642
+ const destination = resolveMailingListDefaultDestination(catalog);
2643
+ return destination === null ? {
2644
+ tag: "unavailable",
2645
+ reason: "default_missing",
2646
+ targeting
2647
+ } : {
2648
+ tag: "available",
2649
+ value: {
2650
+ mode: "default",
2651
+ targeting,
2652
+ provider: catalog.provider,
2653
+ destination
2654
+ }
2655
+ };
2656
+ }
2657
+ function assessCustomTargeting(catalog, targeting) {
2658
+ switch (targeting.provider) {
2659
+ case "native": return catalog.provider === "native" ? assessMatchedChoices({
2660
+ provider: "native",
2661
+ catalog,
2662
+ targeting
2663
+ }, catalog.topics) : {
2664
+ tag: "unavailable",
2665
+ reason: "provider_mismatch",
2666
+ targeting
2667
+ };
2668
+ case "brevo": return catalog.provider === "brevo" ? assessMatchedChoices({
2669
+ provider: "brevo",
2670
+ catalog,
2671
+ targeting
2672
+ }, catalog.lists) : {
2673
+ tag: "unavailable",
2674
+ reason: "provider_mismatch",
2675
+ targeting
2676
+ };
2677
+ case "mailchimp": {
2678
+ if (catalog.provider !== "mailchimp") return {
2679
+ tag: "unavailable",
2680
+ reason: "provider_mismatch",
2681
+ targeting
2682
+ };
2683
+ const audience = catalog.audiences.find((item) => item.id === targeting.audienceId);
2684
+ return audience === void 0 ? {
2685
+ tag: "unavailable",
2686
+ reason: "audience_missing",
2687
+ value: {
2688
+ mode: "custom",
2689
+ provider: "mailchimp",
2690
+ catalog,
2691
+ targeting
2692
+ }
2693
+ } : assessMatchedChoices({
2694
+ provider: "mailchimp",
2695
+ catalog,
2696
+ targeting,
2697
+ audience
2698
+ }, audience.interests);
2699
+ }
2700
+ }
2701
+ }
2702
+ function assessMatchedChoices(matched, choices) {
2703
+ const selected = new Set(mailingListTargetingChoiceIds(matched.targeting));
2704
+ const available = new Set(choices.map((item) => item.id));
2705
+ const missingIds = [...selected].filter((id) => !available.has(id));
2706
+ const value = {
2707
+ mode: "custom",
2708
+ ...matched,
2709
+ choices,
2710
+ selectedChoices: choices.filter((item) => selected.has(item.id))
2711
+ };
2712
+ return missingIds.length > 0 ? {
2713
+ tag: "unavailable",
2714
+ reason: "choices_missing",
2715
+ missingIds,
2716
+ value
2717
+ } : {
2718
+ tag: "available",
2719
+ value
2720
+ };
2721
+ }
2722
+ //#endregion
2566
2723
  //#region ../core/src/mailing-list-runtime.ts
2567
2724
  function parseMailingListRuntimeContext(value) {
2568
2725
  if (!isRecord$8(value)) return invalidRuntimeContext();
@@ -2627,57 +2784,47 @@ function parseBrevoRuntimeProvider(value) {
2627
2784
  function parseMailchimpRuntimeProvider(value) {
2628
2785
  if (!Array.isArray(value.audiences)) return null;
2629
2786
  const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
2630
- if (defaultAudienceId === null) return null;
2631
- const audiences = [];
2632
- for (const audience of value.audiences) {
2633
- if (!isRecord$8(audience)) return null;
2634
- const id = parseMailchimpAudienceId(audience.id);
2635
- const name = parseName(audience.name);
2636
- if (id === null || name === null || !Array.isArray(audience.interests)) return null;
2637
- const interests = parseMailchimpInterests(audience.interests);
2638
- if (interests === null) return null;
2639
- audiences.push({
2640
- id,
2641
- name,
2642
- interests
2643
- });
2644
- }
2645
- return {
2787
+ const audiences = Array.from(value.audiences, parseMailchimpAudience);
2788
+ return defaultAudienceId !== null && audiences.every((audience) => audience !== null) ? {
2646
2789
  provider: "mailchimp",
2647
2790
  defaultAudienceId,
2648
2791
  audiences
2649
- };
2792
+ } : null;
2650
2793
  }
2651
- function parseMailchimpInterests(value) {
2652
- const interests = [];
2653
- for (const interest of value) {
2654
- if (!isRecord$8(interest)) return null;
2655
- const id = parseMailchimpInterestId(interest.id);
2656
- const name = parseName(interest.name);
2657
- const categoryName = parseName(interest.categoryName);
2658
- if (id === null || name === null || categoryName === null) return null;
2659
- interests.push({
2660
- id,
2661
- name,
2662
- categoryName
2663
- });
2664
- }
2665
- return interests;
2794
+ function parseMailchimpAudience(value) {
2795
+ if (!isRecord$8(value) || !Array.isArray(value.interests)) return null;
2796
+ const id = parseMailchimpAudienceId(value.id);
2797
+ const name = parseName(value.name);
2798
+ const interests = Array.from(value.interests, parseMailchimpInterest);
2799
+ return id !== null && name !== null && interests.every((interest) => interest !== null) ? {
2800
+ id,
2801
+ name,
2802
+ interests
2803
+ } : null;
2804
+ }
2805
+ function parseMailchimpInterest(value) {
2806
+ if (!isRecord$8(value)) return null;
2807
+ const id = parseMailchimpInterestId(value.id);
2808
+ const name = parseName(value.name);
2809
+ const categoryName = parseName(value.categoryName);
2810
+ return id !== null && name !== null && categoryName !== null ? {
2811
+ id,
2812
+ name,
2813
+ categoryName
2814
+ } : null;
2666
2815
  }
2667
2816
  function parseNamedItems(value, parseId) {
2668
2817
  if (!Array.isArray(value)) return null;
2669
- const items = [];
2670
- for (const item of value) {
2818
+ const items = Array.from(value, (item) => {
2671
2819
  if (!isRecord$8(item)) return null;
2672
2820
  const id = parseId(item.id);
2673
2821
  const name = parseName(item.name);
2674
- if (id === null || name === null) return null;
2675
- items.push({
2822
+ return id !== null && name !== null ? {
2676
2823
  id,
2677
2824
  name
2678
- });
2679
- }
2680
- return items;
2825
+ } : null;
2826
+ });
2827
+ return items.every((item) => item !== null) ? items : null;
2681
2828
  }
2682
2829
  function parseName(value) {
2683
2830
  if (typeof value !== "string") return null;
@@ -2693,6 +2840,7 @@ function invalidRuntimeContext() {
2693
2840
  //#endregion
2694
2841
  //#region ../core/src/mailing-list-targeting-parser.ts
2695
2842
  function parseMailingListPlacementTargeting(value) {
2843
+ if (value == null) return ok(DEFAULT_MAILING_LIST_TARGETING);
2696
2844
  if (!isRecord$7(value)) return targetingError("shape");
2697
2845
  if (value.mode === "default") return ok({ mode: "default" });
2698
2846
  if (value.mode !== "custom") return targetingError("mode");
@@ -2791,14 +2939,11 @@ function parseNonEmptyUniqueIds(value, parseId) {
2791
2939
  }
2792
2940
  function parseUniqueIds(value, parseId) {
2793
2941
  if (!Array.isArray(value)) return targetingError("targets");
2794
- const parsed = [];
2795
- for (const item of value) {
2942
+ return Array.from(value).reduce((parsed, item) => {
2943
+ if (!parsed.ok) return parsed;
2796
2944
  const id = parseId(item);
2797
- if (id === null) return targetingError("targets");
2798
- if (parsed.includes(id)) return targetingError("duplicate_target");
2799
- parsed.push(id);
2800
- }
2801
- return ok(parsed);
2945
+ return id === null ? targetingError("targets") : parsed.value.includes(id) ? targetingError("duplicate_target") : ok([...parsed.value, id]);
2946
+ }, ok([]));
2802
2947
  }
2803
2948
  function isRecord$7(value) {
2804
2949
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3227,6 +3372,45 @@ function resolveSiteVerticalBookingConfig(rawConfig) {
3227
3372
  };
3228
3373
  }
3229
3374
  //#endregion
3375
+ //#region ../core/src/event-preset-vocabulary.ts
3376
+ const EVENT_PRESET_VOCABULARY = {
3377
+ event: {
3378
+ label: "Event",
3379
+ pluralLabel: "Events",
3380
+ description: "Any event, repeating or manually dated, that does not fit the other categories."
3381
+ },
3382
+ class: {
3383
+ label: "Class",
3384
+ pluralLabel: "Classes",
3385
+ description: "Regular scheduled classes and group sessions."
3386
+ },
3387
+ course: {
3388
+ label: "Course",
3389
+ pluralLabel: "Courses",
3390
+ description: "A fixed set of dates that people book together."
3391
+ },
3392
+ term: {
3393
+ label: "Term",
3394
+ pluralLabel: "Terms",
3395
+ description: "A fixed term of sessions that people book together."
3396
+ },
3397
+ workshop: {
3398
+ label: "Workshop",
3399
+ pluralLabel: "Workshops",
3400
+ description: "One-off workshops, day sessions, or special events."
3401
+ },
3402
+ retreat: {
3403
+ label: "Retreat",
3404
+ pluralLabel: "Retreats",
3405
+ description: "Multi-day or residential immersive experiences."
3406
+ },
3407
+ performance: {
3408
+ label: "Performance",
3409
+ pluralLabel: "Performances",
3410
+ description: "Audience-facing shows, concerts, recitals, or showcases."
3411
+ }
3412
+ };
3413
+ //#endregion
3230
3414
  //#region ../api/src/publicEventEndpointDefinitions.ts
3231
3415
  const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3232
3416
  checkPublicEventRegistrationNextStep: {
@@ -24262,6 +24446,14 @@ z.object({
24262
24446
  icon: z.string().optional()
24263
24447
  });
24264
24448
  //#endregion
24449
+ //#region ../blocks/src/lib/typeGuards.ts
24450
+ /**
24451
+ * Strict record guard: excludes null, primitives, and arrays.
24452
+ */
24453
+ function isRecord$5(value) {
24454
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24455
+ }
24456
+ //#endregion
24265
24457
  //#region ../blocks/src/system/designCapabilities.ts
24266
24458
  function declareBlockDesignCapability(capability) {
24267
24459
  return {
@@ -31115,6 +31307,15 @@ const formBlockDefinition = {
31115
31307
  runtime: formBlockRuntime
31116
31308
  };
31117
31309
  //#endregion
31310
+ //#region ../blocks/src/system/helpers/normalizeMailingListBlockContent.ts
31311
+ /** Fill the declared default without admitting or rewriting invalid custom settings. */
31312
+ function normalizeMailingListBlockContent(record) {
31313
+ return record.targeting == null ? {
31314
+ ...record,
31315
+ targeting: DEFAULT_MAILING_LIST_TARGETING
31316
+ } : record;
31317
+ }
31318
+ //#endregion
31118
31319
  //#region ../blocks/src/system/blocks/contact-newsletter.ts
31119
31320
  const contactNewsletterLayout = styledSection({
31120
31321
  children: sectionContainer([el("div", { className: "contact-newsletter-grid" }, [el("div", { className: "contact-newsletter-column contact-newsletter-contact" }, [el("div", { className: "contact-newsletter-copy" }, [text({
@@ -31278,6 +31479,10 @@ const contactNewsletterBlockDefinition = {
31278
31479
  ],
31279
31480
  icon: "MailPlus"
31280
31481
  }),
31482
+ contentNormalizer: {
31483
+ fieldIds: ["targeting"],
31484
+ normalize: normalizeMailingListBlockContent
31485
+ },
31281
31486
  dataSchemas: { form: formDataSchema.optional() },
31282
31487
  dataLoaders: { form: {
31283
31488
  endpoint: "getPublicFormById",
@@ -32695,14 +32900,6 @@ const mediaTransforms = [{
32695
32900
  }
32696
32901
  }];
32697
32902
  //#endregion
32698
- //#region ../blocks/src/lib/typeGuards.ts
32699
- /**
32700
- * Strict record guard: excludes null, primitives, and arrays.
32701
- */
32702
- function isRecord$5(value) {
32703
- return typeof value === "object" && value !== null && !Array.isArray(value);
32704
- }
32705
- //#endregion
32706
32903
  //#region ../blocks/src/system/transforms/registry/content.ts
32707
32904
  const supportedRichTextTokenSchema = z.enum(["year", "site_name"]);
32708
32905
  const interpolateRichTextTokensOptionsSchema = z.object({ tokens: z.array(supportedRichTextTokenSchema).default(["year", "site_name"]) });
@@ -35678,6 +35875,14 @@ bookingPaymentOptionSchemas.flexibleBalanceConfig;
35678
35875
  bookingPaymentOptionSchemas.paymentCollectionOption;
35679
35876
  const bookingPaymentTermsSchema = bookingPaymentOptionSchemas.paymentTerms;
35680
35877
  bookingPaymentOptionSchemas.paymentOptionsChoice;
35878
+ z.strictObject({
35879
+ currency: z.string().trim().length(3),
35880
+ totalCents: nonNegativeIntegerSchema,
35881
+ dueNowCents: nonNegativeIntegerSchema,
35882
+ laterPaymentAmountsCents: z.array(nonNegativeIntegerSchema),
35883
+ paymentOption: z.union([createBookingPaymentOptionSchemas("strict").paymentCollectionOption, z.strictObject({ kind: z.literal("not_required") })]),
35884
+ promisedDiscountId: z.string().min(1).nullable().optional()
35885
+ });
35681
35886
  //#endregion
35682
35887
  //#region ../blocks/src/system/blocks/shared/bookingPaymentTerms.ts
35683
35888
  const blockBookingPaymentTermsSchema = bookingPaymentTermsSchema;
@@ -35918,6 +36123,10 @@ const publicEventBaseSchema = z.object({
35918
36123
  eventDestinationMode: eventDestinationModeSchema.optional(),
35919
36124
  scheduleScope: eventScheduleScopeSchema$2.optional(),
35920
36125
  recurrence: publicEventRecurrenceSchema.optional(),
36126
+ scheduleSummary: z.object({
36127
+ recurrenceDescription: z.string(),
36128
+ startTime: z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/).nullable()
36129
+ }).nullable().optional(),
35921
36130
  id: z.string(),
35922
36131
  occurrenceId: z.string(),
35923
36132
  seriesId: z.string(),
@@ -36328,68 +36537,267 @@ const occurrenceContextSchema = z.object({
36328
36537
  overrides: z.record(z.string(), z.unknown()).nullable().optional()
36329
36538
  }).nullable();
36330
36539
  //#endregion
36331
- //#region ../blocks/src/system/runtime/api/readAuthority.ts
36332
- const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
36333
- kind: z.literal("preview"),
36334
- token: z.string().min(1)
36335
- }).strict()]);
36336
- const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
36337
- kind: z.literal("unsupported"),
36338
- reason: z.enum([
36339
- "unsupportedSubject",
36340
- "invalidEventSeriesId",
36341
- "invalidEventSeriesBookingScope",
36342
- "invalidEventOfferingId",
36343
- "invalidEventOfferingBookingScope",
36344
- "invalidEventOfferingCatalogue"
36345
- ])
36346
- });
36347
- const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
36348
- const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
36349
- if (unsupported.success) return unsupported.data;
36350
- const parsed = parseEventPageSurfaceSubject(value);
36351
- if (parsed.ok) return parsed.value;
36352
- ctx.addIssue({
36353
- code: z.ZodIssueCode.custom,
36354
- message: `Invalid event page-surface subject (${parsed.error.kind})`
36355
- });
36356
- return z.NEVER;
36357
- });
36358
- function resolveEventRegistrationScope(context) {
36359
- const occurrenceSeriesId = context.occurrenceContext?.seriesId;
36360
- if (occurrenceSeriesId) return {
36361
- kind: "occurrenceSeries",
36362
- seriesId: occurrenceSeriesId
36540
+ //#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
36541
+ /**
36542
+ * Compute remaining spots for an event from `availableSpots` with a fallback
36543
+ * to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
36544
+ * for uncapped events so call sites can render no capacity indicator at all.
36545
+ *
36546
+ * The single source of truth for capacity math across event cards, agenda
36547
+ * rows, and timetable cells — keep wording variations in the call sites.
36548
+ */
36549
+ function getEventAvailability(event) {
36550
+ if (event.capacity == null) return {
36551
+ available: null,
36552
+ isFull: false
36363
36553
  };
36364
- if (context.subject?.kind === "eventSeries") return {
36365
- kind: "pageSurfaceSeries",
36366
- seriesId: context.subject.eventSeriesId
36554
+ const available = event.availableSpots ?? event.capacity - event.registeredCount;
36555
+ if (available <= 0) return {
36556
+ available: 0,
36557
+ isFull: true
36367
36558
  };
36368
- if (context.subject?.kind === "eventOffering") return {
36369
- kind: "pageSurfaceOffering",
36370
- offeringId: context.subject.eventOfferingId,
36371
- seriesIds: context.subject.catalogueEventSeriesIds
36559
+ return {
36560
+ available,
36561
+ isFull: false
36372
36562
  };
36373
- if (context.subject?.kind === "unsupported") return {
36374
- kind: "unsupportedPageSurfaceSubject",
36375
- reason: context.subject.reason
36563
+ }
36564
+ /**
36565
+ * Pick the capacity row a booking decision should read: the selected
36566
+ * attendance mode's availability when the visitor has chosen one and the
36567
+ * event publishes it, otherwise the event-level aggregate.
36568
+ *
36569
+ * The mode row is used wholesale. A mode with `capacity: null` on a capped
36570
+ * event is uncapped *by design*: the registration write path applies the
36571
+ * same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
36572
+ * back to the event's `availableSpots` here would block bookings the server
36573
+ * accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
36574
+ */
36575
+ function selectEventCapacitySource(event, attendanceMode) {
36576
+ if (!attendanceMode) return event;
36577
+ return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
36578
+ }
36579
+ /**
36580
+ * Whether the visitor's next step is the waitlist: the source is full *and*
36581
+ * a waitlist is open. The one place this is decided — cards, rows, the
36582
+ * wizard's steps and its action bar all route through here so they agree,
36583
+ * including for producers that omit `availableSpots` and rely on the
36584
+ * `capacity - registeredCount` fallback.
36585
+ */
36586
+ function canJoinEventWaitlist(event, options) {
36587
+ const source = selectEventCapacitySource(event, options?.attendanceMode);
36588
+ return getEventAvailability(source).isFull && source.waitlistEnabled === true;
36589
+ }
36590
+ /**
36591
+ * Remaining places at or below which an event reads as scarce and the exact
36592
+ * count is worth showing. Above it, no number is shown at all: "147 places
36593
+ * left" is not something a visitor acts on, and it is the figure most likely
36594
+ * to be stale on a cached page.
36595
+ *
36596
+ * max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
36597
+ *
36598
+ * The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
36599
+ * 5% floor stops large events staying silent until three remain.
36600
+ * capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
36601
+ */
36602
+ function lowCapacityThreshold(capacity) {
36603
+ return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
36604
+ }
36605
+ function getRemainingPlaces(source) {
36606
+ const { available, isFull } = getEventAvailability(source);
36607
+ if (available === null || source.capacity === null) return { kind: "unlimited" };
36608
+ if (isFull) return {
36609
+ kind: "full",
36610
+ waitlist: canJoinEventWaitlist(source)
36376
36611
  };
36377
- return { kind: "unscoped" };
36612
+ if (available <= lowCapacityThreshold(source.capacity)) return {
36613
+ kind: "low",
36614
+ remaining: available
36615
+ };
36616
+ return { kind: "open" };
36378
36617
  }
36379
- function resolveEventRegistrationScopedEvents(events, context) {
36380
- const scope = resolveEventRegistrationScope(context);
36381
- switch (scope.kind) {
36382
- case "occurrenceSeries":
36383
- case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
36384
- case "pageSurfaceOffering": {
36385
- const seriesIds = new Set(scope.seriesIds);
36386
- return events.filter((event) => seriesIds.has(event.seriesId));
36387
- }
36388
- case "unsupportedPageSurfaceSubject": return [];
36389
- case "unscoped": return [...events];
36618
+ /**
36619
+ * The one place a remaining-places count becomes text. Returns `null` when
36620
+ * nothing should be shown: uncapped events, and capped events above the
36621
+ * scarcity threshold. Surfaces that need their own full-state wording pass
36622
+ * it in rather than re-deriving the count.
36623
+ */
36624
+ function formatRemainingPlaces(source, wording = {}) {
36625
+ const remaining = getRemainingPlaces(source);
36626
+ switch (remaining.kind) {
36627
+ case "unlimited":
36628
+ case "open": return null;
36629
+ case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
36630
+ case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
36631
+ }
36632
+ }
36633
+ function formatPlacesLeft(count, compact) {
36634
+ if (compact) return `${count} left`;
36635
+ return count === 1 ? "1 place left" : `${count} places left`;
36636
+ }
36637
+ function formatTicketTypeRemaining(remainingCapacity) {
36638
+ if (remainingCapacity === null || remainingCapacity > 3) return null;
36639
+ if (remainingCapacity <= 0) return "None left for this ticket type";
36640
+ return `${formatPlacesLeft(remainingCapacity, false)} for this ticket type`;
36641
+ }
36642
+ /**
36643
+ * Which capacity figure wins when they disagree.
36644
+ *
36645
+ * For a hybrid event that publishes both attendance-mode rows, **the mode
36646
+ * rows are authoritative and the event-level aggregate is only a summary**.
36647
+ * The registration write path admits or refuses per mode
36648
+ * (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
36649
+ * aggregate can read full while a mode still has room, and can read open
36650
+ * while the mode the visitor wants is full. Believing the aggregate on a
36651
+ * card is how a "places available" event turns into a dead end at step two.
36652
+ *
36653
+ * Everything else — single-mode events, events that publish no rows, and the
36654
+ * aggregate-only helpers {@link getEventAvailability} /
36655
+ * {@link getRemainingPlaces} that existing callers rely on — keeps reading
36656
+ * the aggregate, unchanged.
36657
+ */
36658
+ function findHybridModeRows(event) {
36659
+ if (event.deliveryMode !== "hybrid") return null;
36660
+ const rows = event.attendanceModes ?? [];
36661
+ const inPerson = rows.find((row) => row.mode === "in_person");
36662
+ const online = rows.find((row) => row.mode === "online");
36663
+ return inPerson && online ? {
36664
+ inPerson,
36665
+ online
36666
+ } : null;
36667
+ }
36668
+ /** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
36669
+ function remainingPlacesRank(remaining) {
36670
+ switch (remaining.kind) {
36671
+ case "unlimited": return 4;
36672
+ case "open": return 3;
36673
+ case "low": return 2;
36674
+ case "full": return remaining.waitlist ? 1 : 0;
36675
+ }
36676
+ }
36677
+ function isMoreOpen(candidate, than) {
36678
+ const rank = remainingPlacesRank(candidate);
36679
+ const otherRank = remainingPlacesRank(than);
36680
+ if (rank !== otherRank) return rank > otherRank;
36681
+ return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
36682
+ }
36683
+ /** True when `candidate` has less room left than `than`. */
36684
+ function isScarcer(candidate, than) {
36685
+ return isMoreOpen(than, candidate);
36686
+ }
36687
+ const ATTENDANCE_MODE_LABEL = {
36688
+ in_person: "In person",
36689
+ online: "Online"
36690
+ };
36691
+ /** Wording for a mode with room to spare, where the count is deliberately withheld. */
36692
+ const PLACES_AVAILABLE = "Places available";
36693
+ /**
36694
+ * The browse-surface label for an event's remaining places. Single-mode
36695
+ * events get exactly the aggregate label {@link formatRemainingPlaces}
36696
+ * produces, and so does a hybrid event whose two modes tell the same story.
36697
+ * When the modes differ, both sides are named: "available" on a card is a
36698
+ * promise the registration wizard would otherwise have to break.
36699
+ *
36700
+ * Hybrid examples, in person full with 3 online places left:
36701
+ * default -> "Online: 3 places left · In person: Full"
36702
+ * { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
36703
+ * { compact: true } -> "Online: 3 left"
36704
+ */
36705
+ function formatEventRemainingPlaces(event, wording = {}) {
36706
+ const rows = findHybridModeRows(event);
36707
+ if (!rows) return formatRemainingPlaces(event, wording);
36708
+ const inPerson = formatRemainingPlaces(rows.inPerson, wording);
36709
+ const online = formatRemainingPlaces(rows.online, wording);
36710
+ if (inPerson === online) return inPerson;
36711
+ if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
36712
+ return formatBothHybridSides(rows, {
36713
+ inPerson,
36714
+ online
36715
+ });
36716
+ }
36717
+ /**
36718
+ * Both sides, the one with room first: that is the half a visitor can act on,
36719
+ * and it should not arrive after a full stop's worth of bad news.
36720
+ */
36721
+ function formatBothHybridSides(rows, labels) {
36722
+ 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(" · ");
36723
+ }
36724
+ function namedModePlaces(mode, label) {
36725
+ return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
36726
+ }
36727
+ /**
36728
+ * Pills and timetable cells have room for one short phrase, so they name the
36729
+ * single mode that carries news rather than listing both: the scarce side
36730
+ * when there is a count to show, and otherwise the side that has closed —
36731
+ * "In person: Full", never "Online only", which reads as a statement about
36732
+ * how the event is delivered rather than about its remaining places.
36733
+ */
36734
+ function formatCompactHybridPlaces(rows, wording) {
36735
+ const inPerson = getRemainingPlaces(rows.inPerson);
36736
+ const online = getRemainingPlaces(rows.online);
36737
+ if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
36738
+ if (inPerson.kind === "full" || online.kind === "full") {
36739
+ const fullSide = inPerson.kind === "full" ? "in_person" : "online";
36740
+ const openSide = inPerson.kind === "full" ? "online" : "in_person";
36741
+ const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
36742
+ return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
36390
36743
  }
36744
+ const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
36745
+ return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
36746
+ }
36747
+ function rowFor(rows, mode) {
36748
+ return mode === "online" ? rows.online : rows.inPerson;
36391
36749
  }
36392
36750
  //#endregion
36751
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDisplay.ts
36752
+ const registrationDisplaySchema = z.object({
36753
+ venue: z.enum(["shown", "hidden"]),
36754
+ placesLeft: z.enum(["shown", "hidden"])
36755
+ });
36756
+ function resolveRegistrationDisplay(source) {
36757
+ return {
36758
+ venue: source.showVenue === false ? "hidden" : "shown",
36759
+ placesLeft: source.showCapacity === false ? "hidden" : "shown"
36760
+ };
36761
+ }
36762
+ /** Venue stays in domain data; views receive only the permitted presentation. */
36763
+ function shownVenue(event, display) {
36764
+ return display.venue === "shown" ? event.venue?.name ?? null : null;
36765
+ }
36766
+ /** Full/waitlist explanations remain visible even when optional counts are hidden. */
36767
+ function placesLeftLabel(source, display, wording = {}) {
36768
+ if ("kind" in source) return ticketPlacesLabel(source.remaining, display);
36769
+ if (source.deliveryMode === "hybrid" && source.attendanceModes?.length) return hybridPlacesLabel(source, display, wording);
36770
+ return capacityPlacesLabel(source, display, wording);
36771
+ }
36772
+ function capacityPlacesLabel(source, display, wording) {
36773
+ const remaining = getRemainingPlaces(source);
36774
+ if (display.placesLeft === "hidden" && remaining.kind !== "full") return null;
36775
+ switch (remaining.kind) {
36776
+ case "full": return formatRemainingPlaces(source, wording);
36777
+ case "low": return formatRemainingPlaces(source, wording);
36778
+ case "unlimited": return wording.unlimited ?? null;
36779
+ case "open": return wording.open ?? null;
36780
+ default: return assertNever$2(remaining);
36781
+ }
36782
+ }
36783
+ function hybridPlacesLabel(event, display, wording) {
36784
+ if (display.placesLeft === "shown") return formatEventRemainingPlaces(event, wording);
36785
+ return (event.attendanceModes ?? []).flatMap((mode) => {
36786
+ const label = placesLeftLabel(mode, display, wording);
36787
+ return label ? [`${mode.mode === "online" ? "Online" : "In person"}: ${label}`] : [];
36788
+ }).join(" · ") || null;
36789
+ }
36790
+ function ticketPlacesLabel(remaining, display) {
36791
+ if (remaining !== null && remaining > 0 && display.placesLeft === "hidden") return null;
36792
+ return formatTicketTypeRemaining(remaining);
36793
+ }
36794
+ //#endregion
36795
+ //#region ../blocks/src/system/runtime/api/readAuthority.ts
36796
+ const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
36797
+ kind: z.literal("preview"),
36798
+ token: z.string().min(1)
36799
+ }).strict()]);
36800
+ //#endregion
36393
36801
  //#region ../calendar/src/grid.ts
36394
36802
  const MONTH_NAMES = [
36395
36803
  "January",
@@ -36476,6 +36884,11 @@ function buildCalendarViewport(input) {
36476
36884
  //#endregion
36477
36885
  //#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
36478
36886
  /**
36887
+ * Date and time presentation for events: the single place an event's
36888
+ * timestamps become strings or date buckets. Calendar-grid primitives are
36889
+ * re-exported from @riverbankcms/calendar so block code has one import.
36890
+ */
36891
+ /**
36479
36892
  * The English locale a site's public dates are written in, read from the
36480
36893
  * site's own time zone (Will, 2026-09-26: "date style from site time zone").
36481
36894
  *
@@ -36495,6 +36908,28 @@ function eventDateLocale(siteTimeZone) {
36495
36908
  if (siteTimeZone === "Pacific/Auckland" || siteTimeZone === "Pacific/Chatham") return "en-NZ";
36496
36909
  return "en-US";
36497
36910
  }
36911
+ function formatEventCompactDateRail(isoTimestamp, options, letterCase = "capitals") {
36912
+ const locale = options?.locale ?? "en-US";
36913
+ const dateTime = toScheduledBookingDateTime({
36914
+ startsAt: isoTimestamp,
36915
+ timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
36916
+ locale
36917
+ });
36918
+ const cased = (value) => letterCase === "capitals" ? value.toLocaleUpperCase(locale) : value;
36919
+ const weekday = cased(formatBookingDate(dateTime, { weekday: "short" }));
36920
+ const month = cased(formatBookingDate(dateTime, { month: "short" }).slice(0, 3));
36921
+ return {
36922
+ weekday,
36923
+ day: formatBookingDate(dateTime, { day: "numeric" }),
36924
+ month,
36925
+ label: formatBookingDate(dateTime, {
36926
+ weekday: "long",
36927
+ year: "numeric",
36928
+ month: "long",
36929
+ day: "numeric"
36930
+ })
36931
+ };
36932
+ }
36498
36933
  //#endregion
36499
36934
  //#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
36500
36935
  function isRecord$4(value) {
@@ -36514,6 +36949,15 @@ function resolveRuntimeEventDestination(event) {
36514
36949
  };
36515
36950
  }
36516
36951
  //#endregion
36952
+ //#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
36953
+ /**
36954
+ * Format an event's price directly from its pricing value (without the full
36955
+ * event). Same output as {@link formatEventPrice}.
36956
+ */
36957
+ function formatEventPriceDisplay(pricing) {
36958
+ return formatEventPricing(pricing, { decimals: "auto" });
36959
+ }
36960
+ //#endregion
36517
36961
  //#region ../blocks/src/system/runtime/nodes/events/domain/listWeek.ts
36518
36962
  /** Civil weeks use the site zone, including weeks containing a clock change. */
36519
36963
  function buildEventListWeek(anchor, startOfWeek, timeZone) {
@@ -36548,150 +36992,6 @@ function eventListWindowEvents(events, window) {
36548
36992
  });
36549
36993
  }
36550
36994
  //#endregion
36551
- //#region ../blocks/src/system/constants/events.ts
36552
- const eventCalendarDisplayModeValues = [
36553
- "combined",
36554
- "calendar",
36555
- "list",
36556
- "timetable",
36557
- "week-board"
36558
- ];
36559
- const eventCalendarDefaultViewValues = ["month", "week"];
36560
- const eventCalendarListLayoutValues = [
36561
- "stack",
36562
- "grid",
36563
- "compact"
36564
- ];
36565
- const arrangementOnlyListLayouts = ["ledger", "day-groups"];
36566
- const eventListArrangementValues = [...eventCalendarListLayoutValues, ...arrangementOnlyListLayouts];
36567
- const eventCalendarListLayoutOptions = [
36568
- {
36569
- id: "stack",
36570
- label: "Stack"
36571
- },
36572
- {
36573
- id: "grid",
36574
- label: "Grid"
36575
- },
36576
- {
36577
- id: "compact",
36578
- label: "Compact"
36579
- }
36580
- ];
36581
- function isEventCalendarDisplayMode(value) {
36582
- return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
36583
- }
36584
- function normalizeEventCalendarDisplayMode$1(input) {
36585
- if (isEventCalendarDisplayMode(input.variant)) return input.variant;
36586
- return isEventCalendarDisplayMode(input.displayMode) ? input.displayMode : void 0;
36587
- }
36588
- /** One closed authority for the projections required by each rendered mode. */
36589
- function resolveEventCalendarReadPlan(input) {
36590
- const mode = normalizeEventCalendarDisplayMode$1(input) ?? "combined";
36591
- switch (mode) {
36592
- case "combined": return {
36593
- mode,
36594
- jobs: "both",
36595
- readProfile: "listing-card"
36596
- };
36597
- case "list": return {
36598
- mode,
36599
- jobs: "list",
36600
- readProfile: "listing-card"
36601
- };
36602
- case "calendar": return {
36603
- mode,
36604
- jobs: "calendar",
36605
- readProfile: "listing-card"
36606
- };
36607
- case "timetable":
36608
- case "week-board": return {
36609
- mode,
36610
- jobs: "calendar",
36611
- readProfile: "timetable"
36612
- };
36613
- }
36614
- }
36615
- function isEventCalendarDefaultView(value) {
36616
- return typeof value === "string" && eventCalendarDefaultViewValues.includes(value);
36617
- }
36618
- function isEventCalendarListLayout(value) {
36619
- return typeof value === "string" && eventCalendarListLayoutValues.includes(value);
36620
- }
36621
- /** The calendar manifest owns its secondary arrangement axis. */
36622
- const eventCalendarVariantMeaning = {
36623
- kind: "mode",
36624
- arrangementField: "listLayout",
36625
- keys: eventCalendarListLayoutValues,
36626
- lookOnlyKeys: [
36627
- ...arrangementOnlyListLayouts,
36628
- "month-grid",
36629
- "month-day"
36630
- ],
36631
- defaultKey: "stack",
36632
- modeLabels: {
36633
- list: "List",
36634
- calendar: "Month",
36635
- combined: "List and month",
36636
- timetable: "Timetable",
36637
- "week-board": "Week board"
36638
- }
36639
- };
36640
- /** Weekly lists share the arrangement-Look admission used by calendar arrangements. */
36641
- function eventCalendarListGroupingDecision(input) {
36642
- const available = input.arrangementLooks && resolveEventCalendarReadPlan(input).mode === "list";
36643
- return {
36644
- available,
36645
- grouping: available && input.listBy === "week" ? "week" : "upcoming"
36646
- };
36647
- }
36648
- //#endregion
36649
- //#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
36650
- const previewStageSchema = z.enum(["preview", "published"]);
36651
- const registrationButtonVariantSchema = blockButtonRoleSchema;
36652
- const eventButtonVariantSchema = blockButtonRoleSchema;
36653
- const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
36654
- const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
36655
- const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
36656
- const columnOptionSchema = z.enum([
36657
- "2",
36658
- "3",
36659
- "4"
36660
- ]);
36661
- const weekStartSchema = z.enum(["sunday", "monday"]);
36662
- const filterAutoShowSchema = z.enum([
36663
- "auto",
36664
- "always",
36665
- "never"
36666
- ]);
36667
- const calendarTabViewsSchema = z.enum([
36668
- "both",
36669
- "month",
36670
- "week"
36671
- ]);
36672
- //#endregion
36673
- //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
36674
- /**
36675
- * Determine which wizard steps to show based on context and payment requirements.
36676
- *
36677
- * @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
36678
- * @param needsPaymentStep - Whether user must complete payment method selection
36679
- * @returns Array of wizard steps to display
36680
- */
36681
- function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep, paymentActive = false) {
36682
- const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
36683
- "select-event",
36684
- "select-tickets",
36685
- "enter-details"
36686
- ];
36687
- if (!needsPaymentStep) return baseSteps;
36688
- return paymentActive ? [
36689
- ...baseSteps,
36690
- "select-payment",
36691
- "payment"
36692
- ] : [...baseSteps, "select-payment"];
36693
- }
36694
- //#endregion
36695
36995
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
36696
36996
  function eventMatchesOccurrenceId(event, occurrenceId) {
36697
36997
  return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
@@ -36715,6 +37015,424 @@ function resolveEventRequiresPayment(input) {
36715
37015
  return publicEventPricingType(input.event.pricing) === "paid";
36716
37016
  }
36717
37017
  //#endregion
37018
+ //#region ../blocks/src/system/runtime/nodes/event-registration/utils.ts
37019
+ /**
37020
+ * The locale a site's booking dates are written in: the one the site's time
37021
+ * zone places it in (`eventDateLocale`, shared with event cards), or British
37022
+ * English when the site names no zone.
37023
+ */
37024
+ function bookingDateLocale(siteTimeZone) {
37025
+ return siteTimeZone ? eventDateLocale(siteTimeZone) : "en-GB";
37026
+ }
37027
+ function bookingInstant(startsAt, input) {
37028
+ return toScheduledBookingDateTime({
37029
+ startsAt,
37030
+ timeZone: toBookingTimeZone(input.timeZone),
37031
+ locale: bookingDateLocale(input.siteTimeZone)
37032
+ });
37033
+ }
37034
+ /** "7:00pm": a twelve-hour clock with a lowercase, unspaced day period. */
37035
+ function bookingClock(startsAt, input) {
37036
+ return formatBookingTime(bookingInstant(startsAt, input), {
37037
+ hour: "numeric",
37038
+ minute: "2-digit",
37039
+ hour12: true
37040
+ }).replace(/\s*([ap])\.?\s?m\.?$/i, (_match, period) => `${period.toLowerCase()}m`);
37041
+ }
37042
+ /** "7:00pm – 8:15pm", or just the start when there's no distinct end. */
37043
+ function clockRange(input) {
37044
+ const start = bookingClock(input.startsAt, input);
37045
+ const end = input.endsAt ? bookingClock(input.endsAt, input) : null;
37046
+ return end && end !== start ? `${start} – ${end}` : start;
37047
+ }
37048
+ /** A stored local clock has no date or DST offset; UTC preserves its digits. */
37049
+ function formatBookingScheduleSummary(event) {
37050
+ const summary = event.scheduleSummary;
37051
+ if (!summary) return null;
37052
+ const time = summary.startTime ? formatBookingWhen({
37053
+ startsAt: `1970-01-01T${summary.startTime}:00Z`,
37054
+ timeZone: "UTC",
37055
+ siteTimeZone: event.siteTimeZone
37056
+ }, "time") : null;
37057
+ return [summary.recurrenceDescription, time].filter(Boolean).join(" · ");
37058
+ }
37059
+ /** The one booking date formatter; every variant derives from it. */
37060
+ function formatBookingWhen(input, variant) {
37061
+ const instant = bookingInstant(input.startsAt, input);
37062
+ const day = formatBookingDate(instant, {
37063
+ weekday: "short",
37064
+ day: "numeric",
37065
+ month: "short"
37066
+ });
37067
+ switch (variant) {
37068
+ case "day": return day;
37069
+ case "dayLong": return formatBookingDate(instant, {
37070
+ weekday: "long",
37071
+ day: "numeric",
37072
+ month: "long"
37073
+ });
37074
+ case "month": return formatBookingDate(instant, { month: "long" });
37075
+ case "monthYear": return formatBookingDate(instant, {
37076
+ month: "long",
37077
+ year: "numeric"
37078
+ });
37079
+ case "time": return bookingClock(input.startsAt, input);
37080
+ case "timeRange": return clockRange(input);
37081
+ case "short": return `${day}, ${bookingClock(input.startsAt, input)}`;
37082
+ case "long": return `${day} · ${clockRange(input)}`;
37083
+ }
37084
+ }
37085
+ /** A single instant in the short form, e.g. "Mon 5 Oct, 7:00pm". */
37086
+ function formatEventDateTime(isoTimestamp, options) {
37087
+ return formatBookingWhen({
37088
+ startsAt: isoTimestamp,
37089
+ timeZone: options?.timeZone,
37090
+ siteTimeZone: options?.siteTimeZone
37091
+ }, "short");
37092
+ }
37093
+ /** The title an occurrence was given on its own, when it overrides the event's. */
37094
+ function occurrenceTitleOverride(occurrence) {
37095
+ const title = occurrence?.overrides?.title;
37096
+ return typeof title === "string" && title.trim() ? title.trim() : null;
37097
+ }
37098
+ /** The event as this page names it: an occurrence's own title wins. */
37099
+ function withOccurrenceTitle(event, occurrence) {
37100
+ const title = occurrenceTitleOverride(occurrence);
37101
+ return event && title ? {
37102
+ ...event,
37103
+ title
37104
+ } : event;
37105
+ }
37106
+ function courseSchedule(sessions, siteTimeZone) {
37107
+ const first = sessions[0];
37108
+ if (!first) return null;
37109
+ const last = sessions[sessions.length - 1] ?? first;
37110
+ const firstLabel = formatEventDateTime(first.startsAt, {
37111
+ timeZone: first.timeZone,
37112
+ siteTimeZone
37113
+ });
37114
+ if (last.startsAt === first.startsAt) return `${sessions.length} session${sessions.length === 1 ? "" : "s"} · ${firstLabel}`;
37115
+ const lastLabel = formatEventDateTime(last.startsAt, {
37116
+ timeZone: last.timeZone,
37117
+ siteTimeZone
37118
+ });
37119
+ return `${sessions.length} sessions · ${firstLabel} – ${lastLabel}`;
37120
+ }
37121
+ /**
37122
+ * When a booking happens, in one line: the occurrence's date and time, or a
37123
+ * course's session count and first and last dates. `short` by default (bar,
37124
+ * confirmation); `long` adds the end time (card, panel header).
37125
+ */
37126
+ function formatRegistrationSchedule(event, occurrence, variant = "short") {
37127
+ if (event.registrationSubject.kind === "wholeSeries") return courseSchedule(event.registrationSubject.sessions, event.siteTimeZone);
37128
+ const input = scheduleInput(event, occurrence ?? null);
37129
+ return input ? formatBookingWhen(input, variant) : null;
37130
+ }
37131
+ /** The occurrence's own times when the page names one, otherwise the event's. */
37132
+ function scheduleInput(event, occurrence) {
37133
+ const times = occurrence?.startsAt ? {
37134
+ startsAt: occurrence.startsAt,
37135
+ endsAt: occurrence.endsAt ?? null
37136
+ } : {
37137
+ startsAt: event.startsAt,
37138
+ endsAt: event.endsAt
37139
+ };
37140
+ if (!times.startsAt) return null;
37141
+ return {
37142
+ ...times,
37143
+ timeZone: occurrence?.timeZone ?? event.timeZone ?? null,
37144
+ siteTimeZone: event.siteTimeZone
37145
+ };
37146
+ }
37147
+ //#endregion
37148
+ //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
37149
+ function registrationRoute(input) {
37150
+ const base = input.chooseEvent ? [
37151
+ "select-event",
37152
+ "select-tickets",
37153
+ "enter-details"
37154
+ ] : ["select-tickets", "enter-details"];
37155
+ switch (input.payment) {
37156
+ case "none": return base;
37157
+ case "options": return [...base, "select-payment"];
37158
+ case "optionsAndPayment": return [
37159
+ ...base,
37160
+ "select-payment",
37161
+ "payment"
37162
+ ];
37163
+ }
37164
+ }
37165
+ const REGISTRATION_STEP_LABELS = [
37166
+ "Event",
37167
+ "Date",
37168
+ "Tickets",
37169
+ "Places",
37170
+ "Your details",
37171
+ "How you’ll pay",
37172
+ "Payment"
37173
+ ];
37174
+ /** Several dates of one series are a date choice; anything else is an event choice. */
37175
+ function getEventSelectionMode(events) {
37176
+ const firstSeriesId = events[0]?.seriesId;
37177
+ return events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId) ? "date" : "event";
37178
+ }
37179
+ /** The label facts for a surface: what the visitor chooses from, and what they book. */
37180
+ function registrationStepLabelContext(scopedEvents, bookedEvent) {
37181
+ return {
37182
+ eventSelection: getEventSelectionMode(scopedEvents),
37183
+ booksWholeSeries: bookedEvent?.registrationSubject.kind === "wholeSeries"
37184
+ };
37185
+ }
37186
+ /** One label owner for the live progress and the server placeholder. */
37187
+ function registrationStepLabel(step, context) {
37188
+ switch (step) {
37189
+ case "select-event": return context.eventSelection === "date" ? "Date" : "Event";
37190
+ case "select-tickets": return context.booksWholeSeries ? "Places" : "Tickets";
37191
+ case "enter-details": return "Your details";
37192
+ case "select-payment": return "How you’ll pay";
37193
+ case "payment": return "Payment";
37194
+ }
37195
+ }
37196
+ function bookingButtonText(buttonText) {
37197
+ return buttonText?.trim() || "Book your place";
37198
+ }
37199
+ function placesLabel(event, input, compact) {
37200
+ return placesLeftLabel(selectEventCapacitySource(event, input.attendanceMode), input.display, { compact });
37201
+ }
37202
+ function isFull(event, mode) {
37203
+ return formatRemainingPlaces(selectEventCapacitySource(event, mode), {
37204
+ full: "full",
37205
+ waitlist: "full"
37206
+ }) === "full";
37207
+ }
37208
+ function priceLabel(event) {
37209
+ return formatEventPriceDisplay(event.pricing).trim() || null;
37210
+ }
37211
+ /** The lowest price a display can mean; null when it names no paid amount. */
37212
+ function lowestPaidAmount(display) {
37213
+ switch (display.kind) {
37214
+ case "exact":
37215
+ case "from": return display.amount;
37216
+ case "range": return display.minAmount;
37217
+ case "mixed": return display.paidFromAmount;
37218
+ case "free":
37219
+ case "unavailable":
37220
+ case "external": return null;
37221
+ }
37222
+ }
37223
+ /**
37224
+ * One price for a set of dates: the shared label when every date costs the
37225
+ * same, otherwise "From" the lowest paid amount, through the one formatter.
37226
+ */
37227
+ function seriesPriceLabel(events) {
37228
+ const firstEvent = events[0];
37229
+ if (!firstEvent) return null;
37230
+ const displays = events.map((event) => publicEventPricingDisplay(event.pricing));
37231
+ const labels = new Set(displays.map((display) => formatBookingPriceDisplay(display, { decimals: "auto" })));
37232
+ if (labels.size === 1) return [...labels][0] || null;
37233
+ const lowest = displays.flatMap((display) => {
37234
+ const amount = lowestPaidAmount(display);
37235
+ return amount === null || !("currency" in display) ? [] : [{
37236
+ amount,
37237
+ currency: display.currency
37238
+ }];
37239
+ }).reduce((best, candidate) => best === null || candidate.amount < best.amount ? candidate : best, null);
37240
+ return lowest ? formatBookingPriceDisplay({
37241
+ kind: "from",
37242
+ amount: lowest.amount,
37243
+ currency: lowest.currency
37244
+ }, { decimals: "auto" }) : priceLabel(firstEvent);
37245
+ }
37246
+ function dateTile(event) {
37247
+ if (event.registrationSubject.kind === "wholeSeries" || !event.startsAt) return null;
37248
+ const rail = formatEventCompactDateRail(event.startsAt, { timeZone: event.timeZone });
37249
+ return {
37250
+ month: rail.month,
37251
+ day: rail.day
37252
+ };
37253
+ }
37254
+ /** "Mon 5 Oct": the compact date the chips and the bar share. */
37255
+ function formatBookingShortDate(event) {
37256
+ return formatBookingWhen(event, "day");
37257
+ }
37258
+ function byStart(left, right) {
37259
+ return Date.parse(left.startsAt) - Date.parse(right.startsAt);
37260
+ }
37261
+ function singleEntry(event, input) {
37262
+ const isCourse = event.registrationSubject.kind === "wholeSeries";
37263
+ return {
37264
+ kind: "single",
37265
+ title: event.title,
37266
+ when: formatRegistrationSchedule(event, null, "long"),
37267
+ shortWhen: formatRegistrationSchedule(event),
37268
+ venue: shownVenue(event, input.display),
37269
+ price: priceLabel(event),
37270
+ places: placesLabel(event, input, false),
37271
+ tile: dateTile(event),
37272
+ action: isCourse ? `Book the ${input.noun.label}` : bookingButtonText(input.buttonText)
37273
+ };
37274
+ }
37275
+ function chooseDateEntry(input) {
37276
+ const ordered = [...input.events].sort(byStart);
37277
+ const first = ordered[0];
37278
+ const shown = ordered.slice(0, 4);
37279
+ return {
37280
+ kind: "chooseDate",
37281
+ title: first?.title ?? "",
37282
+ schedule: (first ? formatBookingScheduleSummary(first) : null) ?? (ordered.length === 1 ? "1 date" : `${ordered.length} dates`),
37283
+ venue: first ? shownVenue(first, input.display) : null,
37284
+ price: seriesPriceLabel(ordered),
37285
+ nextDate: first ? formatBookingShortDate(first) : null,
37286
+ dateCount: ordered.length,
37287
+ dates: shown.map((event) => ({
37288
+ selectionId: event.selectionId,
37289
+ label: formatBookingShortDate(event),
37290
+ places: placesLabel(event, input, true),
37291
+ full: isFull(event, input.attendanceMode)
37292
+ })),
37293
+ moreCount: Math.max(0, ordered.length - shown.length),
37294
+ action: "Choose a date"
37295
+ };
37296
+ }
37297
+ /** The card before a booking starts; the server renders this too. */
37298
+ function idleBookingEntry(input) {
37299
+ if (input.event) return singleEntry(input.event, input);
37300
+ const only = input.events.length === 1 ? input.events[0] : void 0;
37301
+ if (only) return singleEntry(only, input);
37302
+ if (getEventSelectionMode(input.events) === "date") return chooseDateEntry(input);
37303
+ return {
37304
+ kind: "chooseEvent",
37305
+ title: input.events[0]?.title ?? "",
37306
+ count: input.events.length,
37307
+ noun: input.noun.pluralLabel,
37308
+ action: "Choose an event"
37309
+ };
37310
+ }
37311
+ //#endregion
37312
+ //#region ../blocks/src/system/blocks/offerings-preview/model.ts
37313
+ const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
37314
+ const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
37315
+ const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
37316
+ {
37317
+ preset: "class",
37318
+ sourceKey: "eventPreset:class",
37319
+ dataKey: "classEvents",
37320
+ label: "Classes"
37321
+ },
37322
+ {
37323
+ preset: "workshop",
37324
+ sourceKey: "eventPreset:workshop",
37325
+ dataKey: "workshopEvents",
37326
+ label: "Workshops"
37327
+ },
37328
+ {
37329
+ preset: "retreat",
37330
+ sourceKey: "eventPreset:retreat",
37331
+ dataKey: "retreatEvents",
37332
+ label: "Retreats"
37333
+ },
37334
+ {
37335
+ preset: "performance",
37336
+ sourceKey: "eventPreset:performance",
37337
+ dataKey: "performanceEvents",
37338
+ label: "Performances"
37339
+ }
37340
+ ];
37341
+ const OFFERINGS_PREVIEW_EVENT_PRESETS = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
37342
+ const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
37343
+ const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
37344
+ Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
37345
+ kind: "eventPreset",
37346
+ preset: definition.preset
37347
+ }]));
37348
+ function isOfferingsPreviewSourceKey(value) {
37349
+ return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
37350
+ }
37351
+ function normalizeOfferingsPreviewGroupOrder(value) {
37352
+ if (value == null) return { kind: "legacy" };
37353
+ if (value.length === 0) return { kind: "default" };
37354
+ const keys = uniqueOfferingsPreviewSourceKeys(value);
37355
+ return keys.length > 0 ? {
37356
+ kind: "custom",
37357
+ keys
37358
+ } : { kind: "default" };
37359
+ }
37360
+ function inferLegacyOfferingsPreviewGroupOrder(overrides) {
37361
+ const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
37362
+ return keys.length >= 2 ? keys : [];
37363
+ }
37364
+ function normalizeOfferingsPreviewContentForWrite(value) {
37365
+ if (!isRecord$3(value)) return null;
37366
+ const groupOrderValue = value.groupOrder;
37367
+ if (Array.isArray(groupOrderValue)) {
37368
+ const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
37369
+ return {
37370
+ ...value,
37371
+ groupOrder: normalized.kind === "custom" ? normalized.keys : []
37372
+ };
37373
+ }
37374
+ if (groupOrderValue != null) return {
37375
+ ...value,
37376
+ groupOrder: []
37377
+ };
37378
+ const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$3) : []);
37379
+ return legacyOrder.length > 0 ? {
37380
+ ...value,
37381
+ groupOrder: legacyOrder
37382
+ } : value;
37383
+ }
37384
+ /** Which offerings a site preset row names; one owner for "the course preset means whole-series runs". */
37385
+ function sourceFromPresetSummary(preset) {
37386
+ if (preset.engine === "event_series" && preset.canonicalBehaviour === "course") return { kind: "wholeSeries" };
37387
+ if (preset.engine !== "event_series") return null;
37388
+ if (isOfferingsPreviewEventPreset(preset.canonicalBehaviour)) return {
37389
+ kind: "eventPreset",
37390
+ preset: preset.canonicalBehaviour
37391
+ };
37392
+ return null;
37393
+ }
37394
+ function uniqueOfferingsPreviewSourceKeys(values) {
37395
+ const keys = [];
37396
+ for (const value of values) {
37397
+ if (!isOfferingsPreviewSourceKey(value)) continue;
37398
+ if (!keys.includes(value)) keys.push(value);
37399
+ }
37400
+ return keys;
37401
+ }
37402
+ function isRecord$3(value) {
37403
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37404
+ }
37405
+ function isOfferingsPreviewEventPreset(value) {
37406
+ return typeof value === "string" && OFFERINGS_PREVIEW_EVENT_PRESETS.includes(value);
37407
+ }
37408
+ //#endregion
37409
+ //#region ../blocks/src/system/runtime/nodes/event-registration/offeringNoun.ts
37410
+ function offeringKindForEvent(event) {
37411
+ return event.registrationSubject.kind === "wholeSeries" ? "course" : event.eventPreset ?? "event";
37412
+ }
37413
+ function presetKind(preset) {
37414
+ const source = sourceFromPresetSummary(preset);
37415
+ if (!source) return null;
37416
+ return source.kind === "wholeSeries" ? "course" : source.preset;
37417
+ }
37418
+ /**
37419
+ * What the site calls this kind of offering, in lower case for use mid-sentence
37420
+ * ("Book the course", "3 workshops"). Site preset labels win; the canonical
37421
+ * name stands in when the site has none.
37422
+ */
37423
+ function offeringNounForEvent(event, presets) {
37424
+ if (!event) return lowerCased(EVENT_PRESET_VOCABULARY.event);
37425
+ const kind = offeringKindForEvent(event);
37426
+ const preset = presets?.find((candidate) => presetKind(candidate) === kind);
37427
+ return lowerCased(preset?.label.trim() && preset.pluralLabel.trim() ? preset : EVENT_PRESET_VOCABULARY[kind]);
37428
+ }
37429
+ function lowerCased(noun) {
37430
+ return {
37431
+ label: noun.label.trim().toLocaleLowerCase(),
37432
+ pluralLabel: noun.pluralLabel.trim().toLocaleLowerCase()
37433
+ };
37434
+ }
37435
+ //#endregion
36718
37436
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
36719
37437
  function toRegistrationEvent(event) {
36720
37438
  return {
@@ -36759,6 +37477,279 @@ function toWholeSeriesRegistrationEvents(events) {
36759
37477
  return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
36760
37478
  }
36761
37479
  //#endregion
37480
+ //#region ../blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.ts
37481
+ /**
37482
+ * The booking card the server renders on CMS pages (issue 1581), and the
37483
+ * offering noun it resolves for the wizard. Kept beside the island contract
37484
+ * so the server and the client agree on one card.
37485
+ */
37486
+ const bookingHostSchema = z.enum(["overlay", "inPlace"]);
37487
+ const tileSchema = z.object({
37488
+ month: z.string(),
37489
+ day: z.string()
37490
+ }).nullable();
37491
+ const idleBookingEntrySchema = z.discriminatedUnion("kind", [
37492
+ z.object({
37493
+ kind: z.literal("single"),
37494
+ title: z.string(),
37495
+ when: z.string().nullable(),
37496
+ shortWhen: z.string().nullable(),
37497
+ venue: z.string().nullable(),
37498
+ price: z.string().nullable(),
37499
+ places: z.string().nullable(),
37500
+ tile: tileSchema,
37501
+ action: z.string()
37502
+ }),
37503
+ z.object({
37504
+ kind: z.literal("chooseDate"),
37505
+ title: z.string(),
37506
+ schedule: z.string(),
37507
+ venue: z.string().nullable(),
37508
+ price: z.string().nullable(),
37509
+ nextDate: z.string().nullable(),
37510
+ dateCount: z.number(),
37511
+ dates: z.array(z.object({
37512
+ selectionId: z.string(),
37513
+ label: z.string(),
37514
+ places: z.string().nullable(),
37515
+ full: z.boolean()
37516
+ })),
37517
+ moreCount: z.number(),
37518
+ action: z.string()
37519
+ }),
37520
+ z.object({
37521
+ kind: z.literal("chooseEvent"),
37522
+ title: z.string(),
37523
+ count: z.number(),
37524
+ noun: z.string(),
37525
+ action: z.string()
37526
+ })
37527
+ ]);
37528
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
37529
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
37530
+ }
37531
+ function scopedRegistrationEvents(source, scopedSourceEvents) {
37532
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
37533
+ return {
37534
+ scopedEvents,
37535
+ preSelected: findPreSelectedEvent({
37536
+ scopedEvents,
37537
+ occurrenceContext: source.occurrenceContext
37538
+ })
37539
+ };
37540
+ }
37541
+ function nounFor(scoped, presets) {
37542
+ return offeringNounForEvent(scoped.preSelected ?? scoped.scopedEvents[0] ?? null, presets);
37543
+ }
37544
+ /** What the site calls the offering this block books, from its preset rows. */
37545
+ function resolveOfferingNoun(source, scopedSourceEvents) {
37546
+ return nounFor(scopedRegistrationEvents(source, scopedSourceEvents), source.bookingOfferingPresets);
37547
+ }
37548
+ function idleEntry(source, scopedSourceEvents, display) {
37549
+ const scoped = scopedRegistrationEvents(source, scopedSourceEvents);
37550
+ if (scoped.scopedEvents.length === 0) return null;
37551
+ return idleBookingEntry({
37552
+ events: scoped.scopedEvents,
37553
+ event: withOccurrenceTitle(scoped.preSelected, source.occurrenceContext),
37554
+ display,
37555
+ buttonText: source.buttonText,
37556
+ noun: nounFor(scoped, source.bookingOfferingPresets),
37557
+ attendanceMode: null
37558
+ });
37559
+ }
37560
+ /** The server's card on CMS pages, or null where the shell still applies. */
37561
+ function bookingCardDisplay(source, scopedSourceEvents, display) {
37562
+ if ((source.host ?? "overlay") !== "overlay") return null;
37563
+ const entry = idleEntry(source, scopedSourceEvents, display);
37564
+ if (!entry) return null;
37565
+ return {
37566
+ kind: "event-registration",
37567
+ state: "booking-card",
37568
+ className: source.className ?? null,
37569
+ entry,
37570
+ buttonVariant: source.buttonVariant ?? null,
37571
+ buttonText: source.buttonText?.trim() ?? ""
37572
+ };
37573
+ }
37574
+ const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
37575
+ kind: z.literal("unsupported"),
37576
+ reason: z.enum([
37577
+ "unsupportedSubject",
37578
+ "invalidEventSeriesId",
37579
+ "invalidEventSeriesBookingScope",
37580
+ "invalidEventOfferingId",
37581
+ "invalidEventOfferingBookingScope",
37582
+ "invalidEventOfferingCatalogue"
37583
+ ])
37584
+ });
37585
+ const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
37586
+ const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
37587
+ if (unsupported.success) return unsupported.data;
37588
+ const parsed = parseEventPageSurfaceSubject(value);
37589
+ if (parsed.ok) return parsed.value;
37590
+ ctx.addIssue({
37591
+ code: z.ZodIssueCode.custom,
37592
+ message: `Invalid event page-surface subject (${parsed.error.kind})`
37593
+ });
37594
+ return z.NEVER;
37595
+ });
37596
+ function resolveEventRegistrationScope(context) {
37597
+ const occurrenceSeriesId = context.occurrenceContext?.seriesId;
37598
+ if (occurrenceSeriesId) return {
37599
+ kind: "occurrenceSeries",
37600
+ seriesId: occurrenceSeriesId
37601
+ };
37602
+ if (context.subject?.kind === "eventSeries") return {
37603
+ kind: "pageSurfaceSeries",
37604
+ seriesId: context.subject.eventSeriesId
37605
+ };
37606
+ if (context.subject?.kind === "eventOffering") return {
37607
+ kind: "pageSurfaceOffering",
37608
+ offeringId: context.subject.eventOfferingId,
37609
+ seriesIds: context.subject.catalogueEventSeriesIds
37610
+ };
37611
+ if (context.subject?.kind === "unsupported") return {
37612
+ kind: "unsupportedPageSurfaceSubject",
37613
+ reason: context.subject.reason
37614
+ };
37615
+ return { kind: "unscoped" };
37616
+ }
37617
+ function resolveEventRegistrationScopedEvents(events, context) {
37618
+ const scope = resolveEventRegistrationScope(context);
37619
+ switch (scope.kind) {
37620
+ case "occurrenceSeries":
37621
+ case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
37622
+ case "pageSurfaceOffering": {
37623
+ const seriesIds = new Set(scope.seriesIds);
37624
+ return events.filter((event) => seriesIds.has(event.seriesId));
37625
+ }
37626
+ case "unsupportedPageSurfaceSubject": return [];
37627
+ case "unscoped": return [...events];
37628
+ }
37629
+ }
37630
+ //#endregion
37631
+ //#region ../blocks/src/system/constants/events.ts
37632
+ const eventCalendarDisplayModeValues = [
37633
+ "combined",
37634
+ "calendar",
37635
+ "list",
37636
+ "timetable",
37637
+ "week-board"
37638
+ ];
37639
+ const eventCalendarDefaultViewValues = ["month", "week"];
37640
+ const eventCalendarListLayoutValues = [
37641
+ "stack",
37642
+ "grid",
37643
+ "compact"
37644
+ ];
37645
+ const arrangementOnlyListLayouts = ["ledger", "day-groups"];
37646
+ const eventListArrangementValues = [...eventCalendarListLayoutValues, ...arrangementOnlyListLayouts];
37647
+ const eventCalendarListLayoutOptions = [
37648
+ {
37649
+ id: "stack",
37650
+ label: "Stack"
37651
+ },
37652
+ {
37653
+ id: "grid",
37654
+ label: "Grid"
37655
+ },
37656
+ {
37657
+ id: "compact",
37658
+ label: "Compact"
37659
+ }
37660
+ ];
37661
+ function isEventCalendarDisplayMode(value) {
37662
+ return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
37663
+ }
37664
+ function normalizeEventCalendarDisplayMode$1(input) {
37665
+ if (isEventCalendarDisplayMode(input.variant)) return input.variant;
37666
+ return isEventCalendarDisplayMode(input.displayMode) ? input.displayMode : void 0;
37667
+ }
37668
+ /** One closed authority for the projections required by each rendered mode. */
37669
+ function resolveEventCalendarReadPlan(input) {
37670
+ const mode = normalizeEventCalendarDisplayMode$1(input) ?? "combined";
37671
+ switch (mode) {
37672
+ case "combined": return {
37673
+ mode,
37674
+ jobs: "both",
37675
+ readProfile: "listing-card"
37676
+ };
37677
+ case "list": return {
37678
+ mode,
37679
+ jobs: "list",
37680
+ readProfile: "listing-card"
37681
+ };
37682
+ case "calendar": return {
37683
+ mode,
37684
+ jobs: "calendar",
37685
+ readProfile: "listing-card"
37686
+ };
37687
+ case "timetable":
37688
+ case "week-board": return {
37689
+ mode,
37690
+ jobs: "calendar",
37691
+ readProfile: "timetable"
37692
+ };
37693
+ }
37694
+ }
37695
+ function isEventCalendarDefaultView(value) {
37696
+ return typeof value === "string" && eventCalendarDefaultViewValues.includes(value);
37697
+ }
37698
+ function isEventCalendarListLayout(value) {
37699
+ return typeof value === "string" && eventCalendarListLayoutValues.includes(value);
37700
+ }
37701
+ /** The calendar manifest owns its secondary arrangement axis. */
37702
+ const eventCalendarVariantMeaning = {
37703
+ kind: "mode",
37704
+ arrangementField: "listLayout",
37705
+ keys: eventCalendarListLayoutValues,
37706
+ lookOnlyKeys: [
37707
+ ...arrangementOnlyListLayouts,
37708
+ "month-grid",
37709
+ "month-day"
37710
+ ],
37711
+ defaultKey: "stack",
37712
+ modeLabels: {
37713
+ list: "List",
37714
+ calendar: "Month",
37715
+ combined: "List and month",
37716
+ timetable: "Timetable",
37717
+ "week-board": "Week board"
37718
+ }
37719
+ };
37720
+ /** Weekly lists share the arrangement-Look admission used by calendar arrangements. */
37721
+ function eventCalendarListGroupingDecision(input) {
37722
+ const available = input.arrangementLooks && resolveEventCalendarReadPlan(input).mode === "list";
37723
+ return {
37724
+ available,
37725
+ grouping: available && input.listBy === "week" ? "week" : "upcoming"
37726
+ };
37727
+ }
37728
+ //#endregion
37729
+ //#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
37730
+ const previewStageSchema = z.enum(["preview", "published"]);
37731
+ const registrationButtonVariantSchema = blockButtonRoleSchema;
37732
+ const eventButtonVariantSchema = blockButtonRoleSchema;
37733
+ const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
37734
+ const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
37735
+ const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
37736
+ const columnOptionSchema = z.enum([
37737
+ "2",
37738
+ "3",
37739
+ "4"
37740
+ ]);
37741
+ const weekStartSchema = z.enum(["sunday", "monday"]);
37742
+ const filterAutoShowSchema = z.enum([
37743
+ "auto",
37744
+ "always",
37745
+ "never"
37746
+ ]);
37747
+ const calendarTabViewsSchema = z.enum([
37748
+ "both",
37749
+ "month",
37750
+ "week"
37751
+ ]);
37752
+ //#endregion
36762
37753
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.ts
36763
37754
  const { limit: _rawLimit, eventSurface: _rawEventSurface, scheduleScope: _rawScheduleScope, detailLevel: _rawDetailLevel, ...stringEventQueryShape } = publicEventLoaderQuerySchemaShape;
36764
37755
  const eventRecoveryRequestSchema = z.object({
@@ -36801,27 +37792,17 @@ function parseEventLoaderRecoveryRequest(value) {
36801
37792
  const EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS = [
36802
37793
  "Date",
36803
37794
  "Tickets",
36804
- "Details"
36805
- ];
36806
- const EVENT_REGISTRATION_SHELL_STEP_LABELS = [
36807
- "Event",
36808
- "Date",
36809
- "Tickets",
36810
- "Details",
36811
- "Payment options",
36812
- "Payment"
37795
+ "Your details"
36813
37796
  ];
36814
37797
  const eventRegistrationPublicEventSchema = z.preprocess(normalizeEventRegistrationEventInput, publicEventSchema);
36815
- const eventRegistrationHydrationSchemaShape = {
37798
+ const eventRegistrationCommonSchemaShape = {
36816
37799
  demoEmailSuppressed: z.boolean().optional(),
36817
37800
  occurrenceContext: occurrenceContextSchema.optional(),
36818
37801
  subject: eventRegistrationPageSurfaceSubjectSchema.nullable().optional(),
36819
37802
  maxTickets: z.string().optional(),
36820
- showVenue: z.boolean().optional(),
36821
- showCapacity: z.boolean().optional(),
36822
37803
  successMessage: z.string().optional(),
36823
37804
  buttonVariant: registrationButtonVariantSchema.optional(),
36824
- overlayEnabled: z.boolean().optional(),
37805
+ host: bookingHostSchema.optional(),
36825
37806
  spamProtectionEnabled: z.boolean().optional(),
36826
37807
  supportEmail: z.string().optional(),
36827
37808
  sitePasses: publicPassProductCollectionSchema.nullable().optional(),
@@ -36846,32 +37827,51 @@ const eventRegistrationSourceSchema = z.object({
36846
37827
  portalToken: z.string().nullable().optional(),
36847
37828
  previewToken: z.string().nullable().optional(),
36848
37829
  readAuthority: browserReadAuthoritySchema.optional(),
36849
- ...eventRegistrationHydrationSchemaShape,
37830
+ ...eventRegistrationCommonSchemaShape,
37831
+ showVenue: z.boolean().optional(),
37832
+ showCapacity: z.boolean().optional(),
36850
37833
  events: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
36851
37834
  wholeSeriesRunEvents: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
36852
37835
  eventLoaderFailures: z.record(z.string(), z.unknown()).nullable().optional(),
36853
37836
  className: z.string().nullable().optional(),
36854
37837
  buttonText: z.string().nullable().optional(),
36855
37838
  maxTickets: z.string().nullable().optional(),
36856
- stage: previewStageSchema.optional()
37839
+ stage: previewStageSchema.optional(),
37840
+ bookingOfferingPresets: z.array(z.custom((value) => typeof value === "object" && value !== null && typeof value.label === "string")).nullable().optional()
36857
37841
  });
36858
- const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [z.object({
36859
- kind: z.literal("event-registration"),
36860
- state: z.literal("generic-shell"),
36861
- className: z.string().nullable(),
36862
- titleText: z.string(),
36863
- bodyText: z.string(),
36864
- stepLabels: z.array(z.enum(EVENT_REGISTRATION_SHELL_STEP_LABELS)).min(1),
36865
- summaryButtonText: z.string()
36866
- }), z.object({
36867
- kind: z.literal("event-registration"),
36868
- state: z.literal("registration-closed"),
36869
- className: z.string().nullable(),
36870
- titleText: z.string(),
36871
- bodyText: z.string()
36872
- })]);
37842
+ const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [
37843
+ z.object({
37844
+ kind: z.literal("event-registration"),
37845
+ state: z.literal("generic-shell"),
37846
+ className: z.string().nullable(),
37847
+ titleText: z.string(),
37848
+ bodyText: z.string(),
37849
+ stepLabels: z.array(z.enum(REGISTRATION_STEP_LABELS)).min(1),
37850
+ summaryButtonText: z.string()
37851
+ }),
37852
+ z.object({
37853
+ kind: z.literal("event-registration"),
37854
+ state: z.literal("registration-closed"),
37855
+ className: z.string().nullable(),
37856
+ titleText: z.string(),
37857
+ bodyText: z.string()
37858
+ }),
37859
+ z.object({
37860
+ kind: z.literal("event-registration"),
37861
+ state: z.literal("booking-card"),
37862
+ className: z.string().nullable(),
37863
+ entry: idleBookingEntrySchema,
37864
+ buttonVariant: registrationButtonVariantSchema.nullable(),
37865
+ buttonText: z.string()
37866
+ })
37867
+ ]);
36873
37868
  const eventRegistrationHydrationSchema = z.object({
36874
- ...eventRegistrationHydrationSchemaShape,
37869
+ ...eventRegistrationCommonSchemaShape,
37870
+ display: registrationDisplaySchema,
37871
+ offeringNoun: z.object({
37872
+ label: z.string(),
37873
+ pluralLabel: z.string()
37874
+ }).optional(),
36875
37875
  eventSource: eventRegistrationEventSourceSchema
36876
37876
  });
36877
37877
  const eventRegistrationRenderSchema = z.object({
@@ -36886,7 +37886,7 @@ z.object({
36886
37886
  readAuthority: browserReadAuthoritySchema.optional(),
36887
37887
  stage: previewStageSchema.optional()
36888
37888
  });
36889
- function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
37889
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display) {
36890
37890
  const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
36891
37891
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
36892
37892
  kind: "event-registration",
@@ -36895,6 +37895,8 @@ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvent
36895
37895
  titleText: "Registration closed",
36896
37896
  bodyText: "This run is no longer open for bookings."
36897
37897
  };
37898
+ const card = bookingCardDisplay(source, scopedSourceEvents, display);
37899
+ if (card) return card;
36898
37900
  const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
36899
37901
  return {
36900
37902
  kind: "event-registration",
@@ -36911,11 +37913,14 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
36911
37913
  if (selectedRun) {
36912
37914
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
36913
37915
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
36914
- return getWizardStepsForContext(true, resolveEventRequiresPayment({
36915
- eligibility: null,
36916
- selectedTicketType: null,
36917
- event: selectedEvent
36918
- })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
37916
+ return shellStepLabels(registrationRoute({
37917
+ chooseEvent: false,
37918
+ payment: resolveEventRequiresPayment({
37919
+ eligibility: null,
37920
+ selectedTicketType: null,
37921
+ event: selectedEvent
37922
+ }) ? "optionsAndPayment" : "none"
37923
+ }), registrationStepLabelContext([selectedEvent], selectedEvent));
36919
37924
  }
36920
37925
  const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
36921
37926
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -36924,14 +37929,15 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
36924
37929
  occurrenceContext: source.occurrenceContext
36925
37930
  });
36926
37931
  const hasPreSelectedContext = Boolean(source.occurrenceContext || preSelectedEvent);
36927
- return getWizardStepsForContext(hasPreSelectedContext, hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
37932
+ const needsPaymentStep = hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
36928
37933
  eligibility: null,
36929
37934
  selectedTicketType: null,
36930
37935
  event: preSelectedEvent
36931
- }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
36932
- }
36933
- function toScopedEventRegistrationEvents(scopedSourceEvents) {
36934
- return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
37936
+ }) : false;
37937
+ return shellStepLabels(registrationRoute({
37938
+ chooseEvent: !hasPreSelectedContext,
37939
+ payment: needsPaymentStep ? "optionsAndPayment" : "none"
37940
+ }), registrationStepLabelContext(scopedEvents, preSelectedEvent));
36935
37941
  }
36936
37942
  function scopeEventRegistrationSourceEvents(source, eventSource) {
36937
37943
  return resolveEventRegistrationScopedEvents(eventSource.kind === "loaded" ? eventSource.events : [], {
@@ -36947,29 +37953,12 @@ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
36947
37953
  const runs = groupPublicEventsByRun(scopedSourceEvents);
36948
37954
  return runs.length === 1 ? runs[0] : null;
36949
37955
  }
36950
- function shellLabelForRegistrationStep(step, events) {
36951
- switch (step) {
36952
- case "select-event": return isDateSelection(events) ? "Date" : "Event";
36953
- case "select-tickets": return "Tickets";
36954
- case "enter-details": return "Details";
36955
- case "select-payment": return "Payment options";
36956
- case "payment": return "Payment";
36957
- case "error":
36958
- case "success":
36959
- case "submitting":
36960
- case "verifying-payment":
36961
- case "verifying-timeout":
36962
- case "payment-status":
36963
- case "payment-failed": return "Details";
36964
- }
36965
- }
36966
- function isDateSelection(events) {
36967
- const firstSeriesId = events[0]?.seriesId;
36968
- return Boolean(events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId));
37956
+ function shellStepLabels(route, context) {
37957
+ return route.map((step) => registrationStepLabel(step, context));
36969
37958
  }
36970
37959
  function buildEventRegistrationShellBodyText(stepLabels) {
36971
- if (stepLabels.includes("Payment options")) return "Preparing tickets, details, and payment options.";
36972
- if (stepLabels[0] === "Tickets") return "Preparing tickets and booking details.";
37960
+ if (stepLabels.includes("How you’ll pay")) return "Preparing tickets, details, and payment options.";
37961
+ if (stepLabels[0] === "Tickets" || stepLabels[0] === "Places") return "Preparing tickets and booking details.";
36973
37962
  return "Preparing dates, tickets, and booking details.";
36974
37963
  }
36975
37964
  const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
@@ -36979,6 +37968,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36979
37968
  render: createZodCodec(eventRegistrationRenderSchema)
36980
37969
  },
36981
37970
  buildRender: (source) => {
37971
+ const display = resolveRegistrationDisplay(source);
36982
37972
  const eventSource = resolveEventRegistrationEventSource({
36983
37973
  events: source.events,
36984
37974
  wholeSeriesRunEvents: source.wholeSeriesRunEvents,
@@ -36986,7 +37976,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36986
37976
  });
36987
37977
  const scopedSourceEvents = scopeEventRegistrationSourceEvents(source, eventSource);
36988
37978
  return {
36989
- display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
37979
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display),
36990
37980
  hydration: {
36991
37981
  occurrenceContext: source.occurrenceContext ?? null,
36992
37982
  subject: source.subject ?? null,
@@ -36996,29 +37986,29 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36996
37986
  } : eventSource,
36997
37987
  demoEmailSuppressed: source.demoEmailSuppressed,
36998
37988
  maxTickets: source.maxTickets ?? void 0,
36999
- showVenue: source.showVenue,
37000
- showCapacity: source.showCapacity,
37989
+ display,
37001
37990
  successMessage: source.successMessage,
37002
37991
  buttonVariant: source.buttonVariant,
37003
- overlayEnabled: source.overlayEnabled,
37992
+ host: source.host ?? "overlay",
37004
37993
  spamProtectionEnabled: source.spamProtectionEnabled,
37005
37994
  supportEmail: source.supportEmail,
37006
37995
  sitePasses: source.sitePasses ?? null,
37007
37996
  siteMemberships: source.siteMemberships ?? null,
37008
37997
  mailingList: source.mailingList,
37009
- siteName: source.siteName
37998
+ siteName: source.siteName,
37999
+ offeringNoun: resolveOfferingNoun(source, scopedSourceEvents)
37010
38000
  }
37011
38001
  };
37012
38002
  }
37013
38003
  });
37014
38004
  function normalizeEventRegistrationEventInput(value) {
37015
- if (!isRecord$3(value)) return value;
38005
+ if (!isRecord$2(value)) return value;
37016
38006
  return {
37017
38007
  ...value,
37018
38008
  destination: resolveRuntimeEventDestination(value)
37019
38009
  };
37020
38010
  }
37021
- function isRecord$3(value) {
38011
+ function isRecord$2(value) {
37022
38012
  return typeof value === "object" && value !== null && !Array.isArray(value);
37023
38013
  }
37024
38014
  const eventRegistrationBlockDefinition = {
@@ -37058,7 +38048,7 @@ const eventRegistrationBlockDefinition = {
37058
38048
  description: "Main heading for the registration form",
37059
38049
  required: false,
37060
38050
  multiline: false,
37061
- defaultValue: "Register for this event"
38051
+ defaultValue: ""
37062
38052
  },
37063
38053
  {
37064
38054
  id: "description",
@@ -37130,11 +38120,11 @@ const eventRegistrationBlockDefinition = {
37130
38120
  {
37131
38121
  id: "buttonText",
37132
38122
  type: "text",
37133
- label: "Submit button text",
37134
- description: "Text for the registration submit button",
38123
+ label: "Button text",
38124
+ description: "Text on the booking button",
37135
38125
  required: false,
37136
38126
  multiline: false,
37137
- defaultValue: "Complete Registration",
38127
+ defaultValue: "Book your place",
37138
38128
  maxLength: 40
37139
38129
  },
37140
38130
  createButtonVariantField()
@@ -37166,21 +38156,21 @@ const eventRegistrationBlockDefinition = {
37166
38156
  successMessage: { $bind: { from: "content.successMessage" } },
37167
38157
  buttonText: { $bind: {
37168
38158
  from: "content.buttonText",
37169
- fallback: "Complete Registration"
38159
+ fallback: "Book your place"
37170
38160
  } },
37171
38161
  buttonVariant: { $bind: {
37172
38162
  from: "content.buttonVariant",
37173
38163
  fallback: "primary"
37174
38164
  } },
37175
- overlayEnabled: { $bind: {
37176
- from: "$root.eventRegistrationOverlayEnabled",
37177
- fallback: false
37178
- } },
37179
38165
  events: { $bind: { from: "data.events" } },
37180
38166
  wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
37181
38167
  loaderFailures: { $bind: { from: "$root.loaderFailures" } },
37182
38168
  mailingList: { $bind: { from: "$root.mailingList" } },
37183
38169
  siteName: { $bind: { from: "$root.siteName" } },
38170
+ bookingOfferingPresets: { $bind: {
38171
+ from: "$root.bookingOfferingPresets",
38172
+ fallback: []
38173
+ } },
37184
38174
  sitePasses: { $bind: { from: "data.sitePasses" } },
37185
38175
  siteMemberships: { $bind: { from: "data.siteMemberships" } },
37186
38176
  stage: { $bind: {
@@ -39841,7 +40831,21 @@ const eventCalendarBlockDefinition = {
39841
40831
  list: { label: "List" }
39842
40832
  }
39843
40833
  }, eventCalendarContentShape),
39844
- normalizeContent: normalizeEventCalendarContent,
40834
+ contentNormalizer: {
40835
+ fieldIds: [
40836
+ "variant",
40837
+ "displayMode",
40838
+ "listLayout",
40839
+ "defaultCalendarView",
40840
+ "eventSurface",
40841
+ "filterEventPresets",
40842
+ "scheduleScope",
40843
+ "filterCategoryIds",
40844
+ "filterStaffMemberId",
40845
+ "filterVenueId"
40846
+ ],
40847
+ normalize: normalizeEventCalendarContent
40848
+ },
39845
40849
  dataSchemas: {
39846
40850
  events: publicEventsArraySchema.optional(),
39847
40851
  calendarWindow: calendarWindowPageSchema.optional()
@@ -40090,90 +41094,6 @@ const recurringScheduleBlockDefinition = {
40090
41094
  } }
40091
41095
  };
40092
41096
  //#endregion
40093
- //#region ../blocks/src/system/blocks/offerings-preview/model.ts
40094
- const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
40095
- const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
40096
- const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
40097
- {
40098
- preset: "class",
40099
- sourceKey: "eventPreset:class",
40100
- dataKey: "classEvents",
40101
- label: "Classes"
40102
- },
40103
- {
40104
- preset: "workshop",
40105
- sourceKey: "eventPreset:workshop",
40106
- dataKey: "workshopEvents",
40107
- label: "Workshops"
40108
- },
40109
- {
40110
- preset: "retreat",
40111
- sourceKey: "eventPreset:retreat",
40112
- dataKey: "retreatEvents",
40113
- label: "Retreats"
40114
- },
40115
- {
40116
- preset: "performance",
40117
- sourceKey: "eventPreset:performance",
40118
- dataKey: "performanceEvents",
40119
- label: "Performances"
40120
- }
40121
- ];
40122
- OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
40123
- const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
40124
- const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
40125
- Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
40126
- kind: "eventPreset",
40127
- preset: definition.preset
40128
- }]));
40129
- function isOfferingsPreviewSourceKey(value) {
40130
- return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
40131
- }
40132
- function normalizeOfferingsPreviewGroupOrder(value) {
40133
- if (value == null) return { kind: "legacy" };
40134
- if (value.length === 0) return { kind: "default" };
40135
- const keys = uniqueOfferingsPreviewSourceKeys(value);
40136
- return keys.length > 0 ? {
40137
- kind: "custom",
40138
- keys
40139
- } : { kind: "default" };
40140
- }
40141
- function inferLegacyOfferingsPreviewGroupOrder(overrides) {
40142
- const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
40143
- return keys.length >= 2 ? keys : [];
40144
- }
40145
- function normalizeOfferingsPreviewContentForWrite(value) {
40146
- if (!isRecord$2(value)) return null;
40147
- const groupOrderValue = value.groupOrder;
40148
- if (Array.isArray(groupOrderValue)) {
40149
- const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
40150
- return {
40151
- ...value,
40152
- groupOrder: normalized.kind === "custom" ? normalized.keys : []
40153
- };
40154
- }
40155
- if (groupOrderValue != null) return {
40156
- ...value,
40157
- groupOrder: []
40158
- };
40159
- const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$2) : []);
40160
- return legacyOrder.length > 0 ? {
40161
- ...value,
40162
- groupOrder: legacyOrder
40163
- } : value;
40164
- }
40165
- function uniqueOfferingsPreviewSourceKeys(values) {
40166
- const keys = [];
40167
- for (const value of values) {
40168
- if (!isOfferingsPreviewSourceKey(value)) continue;
40169
- if (!keys.includes(value)) keys.push(value);
40170
- }
40171
- return keys;
40172
- }
40173
- function isRecord$2(value) {
40174
- return typeof value === "object" && value !== null && !Array.isArray(value);
40175
- }
40176
- //#endregion
40177
41097
  //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
40178
41098
  const groupOverrideFields = [
40179
41099
  {
@@ -40537,7 +41457,22 @@ const offeringsPreviewEventDataSchemas = Object.fromEntries(OFFERINGS_PREVIEW_EV
40537
41457
  const offeringsPreviewEventDataLoaders = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, eventPresetLoader(definition.preset)]));
40538
41458
  const offeringsPreviewBlockDefinition = {
40539
41459
  manifest: offeringsPreviewManifest,
40540
- normalizeContent: normalizeOfferingsPreviewBlockContent,
41460
+ contentNormalizer: {
41461
+ fieldIds: [
41462
+ "displayMode",
41463
+ "heading",
41464
+ "description",
41465
+ "itemLimit",
41466
+ "groupOrder",
41467
+ "groupOverrides",
41468
+ "_cardStyles",
41469
+ "buttonVariant",
41470
+ "buttonText",
41471
+ "emptyMessage",
41472
+ "_sectionStyles"
41473
+ ],
41474
+ normalize: normalizeOfferingsPreviewBlockContent
41475
+ },
40541
41476
  dataSchemas: {
40542
41477
  ...offeringsPreviewEventDataSchemas,
40543
41478
  routeCandidates: publicRouteCandidatesSchema.optional(),
@@ -44135,11 +45070,11 @@ function buildNewsletterDisplay(source) {
44135
45070
  successMessage: DEFAULT_MAILING_LIST_SUCCESS_MESSAGE,
44136
45071
  privacyNote: null
44137
45072
  };
44138
- const targeting = source.targeting ?? { mode: "default" };
44139
- const choices = resolveRuntimeChoices(context, targeting);
45073
+ const assessment = assessMailingListTargeting(context, source.targeting ?? { mode: "default" });
45074
+ const choices = resolveRuntimeChoices(assessment);
44140
45075
  return {
44141
45076
  kind: "newsletter-form",
44142
- availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
45077
+ availability: assessment.tag === "available" ? "ready" : "unavailable",
44143
45078
  className: source.className ?? null,
44144
45079
  buttonLabel: form.buttonLabel,
44145
45080
  successMessage: form.successMessage,
@@ -44196,60 +45131,12 @@ function projectNewsletterFormHydrationTarget(source) {
44196
45131
  default: return assertNever$2(source);
44197
45132
  }
44198
45133
  }
44199
- function resolveRuntimeChoices(context, targeting) {
44200
- if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
44201
- switch (targeting.provider) {
44202
- case "native": {
44203
- const offered = new Set(targeting.selection.offeredTopicIds);
44204
- return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
44205
- id: item.id,
44206
- label: item.name
44207
- })) : [];
44208
- }
44209
- case "brevo": {
44210
- const offered = new Set(targeting.selection.offeredListIds);
44211
- return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
44212
- id: String(item.id),
44213
- label: item.name
44214
- })) : [];
44215
- }
44216
- case "mailchimp": {
44217
- const offered = new Set(targeting.selection.offeredInterestIds);
44218
- 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) => ({
44219
- id: item.id,
44220
- label: item.name
44221
- })) ?? [];
44222
- }
44223
- }
44224
- }
44225
- function runtimeTargetingIsAvailable(context, targeting, choices) {
44226
- if (context?.tag !== "configured") return false;
44227
- const provider = context.provider;
44228
- if (targeting.mode === "default") switch (provider.provider) {
44229
- case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
44230
- case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
44231
- case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
44232
- }
44233
- if (targeting.provider !== provider.provider) return false;
44234
- switch (targeting.provider) {
44235
- case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
44236
- case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
44237
- case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
44238
- }
44239
- }
44240
- function nativeTargetIsAvailable(provider, targeting, choices) {
44241
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
44242
- return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
44243
- }
44244
- function brevoTargetIsAvailable(provider, targeting, choices) {
44245
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
44246
- return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
44247
- }
44248
- function mailchimpTargetIsAvailable(provider, targeting, choices) {
44249
- const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
44250
- if (!audience) return false;
44251
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
44252
- return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
45134
+ function resolveRuntimeChoices(assessment) {
45135
+ if (!("value" in assessment) || assessment.value.mode !== "custom" || !("selectedChoices" in assessment.value) || assessment.value.targeting.selection.tag !== "visitor_choice") return [];
45136
+ return assessment.value.selectedChoices.map((item) => ({
45137
+ id: String(item.id),
45138
+ label: item.name
45139
+ }));
44253
45140
  }
44254
45141
  function parseRequiredMailingListSignupBlockId(value) {
44255
45142
  const parsed = parseMailingListBlockId(value);
@@ -44353,6 +45240,10 @@ const newsletterSignupManifest = createBlockManifest({
44353
45240
  [...(newsletterSignupManifest.fields ?? []).map((field) => field.id)];
44354
45241
  const newsletterSignupBlockDefinition = {
44355
45242
  manifest: newsletterSignupManifest,
45243
+ contentNormalizer: {
45244
+ fieldIds: ["targeting"],
45245
+ normalize: normalizeMailingListBlockContent
45246
+ },
44356
45247
  runtime: newsletterFormBlockRuntime
44357
45248
  };
44358
45249
  //#endregion
@@ -45085,7 +45976,20 @@ const locationMapBlockDefinition = {
45085
45976
  })], { gap: "md" })
45086
45977
  })
45087
45978
  }),
45088
- normalizeContent: normalizeLocationMapContent
45979
+ contentNormalizer: {
45980
+ fieldIds: [
45981
+ "heading",
45982
+ "locationSource",
45983
+ "venueId",
45984
+ "location",
45985
+ "showAddressText",
45986
+ "showDirections",
45987
+ "directionsLabel",
45988
+ "zoom",
45989
+ "heightPx"
45990
+ ],
45991
+ normalize: normalizeLocationMapContent
45992
+ }
45089
45993
  };
45090
45994
  //#endregion
45091
45995
  //#region ../blocks/src/system/blocks/index.ts
@@ -46290,17 +47194,7 @@ const linksTransforms = [{
46290
47194
  id: "links.resolve",
46291
47195
  kind: "formatter",
46292
47196
  summary: "Resolve LinkValue objects to href strings using the route map",
46293
- run: (value, _options, context) => {
46294
- if (!value || typeof value !== "object") return null;
46295
- const link = value;
46296
- const kind = typeof link.kind === "string" ? link.kind : null;
46297
- if (kind === "internal") return resolveInternalLink(link, context);
46298
- if (kind === "page") return resolvePageLink(link, context);
46299
- if (kind === "entry") return resolveEntryLink(link, context);
46300
- if (kind === "external" || kind === "url") return resolveExternalLink(link);
46301
- if (!kind && typeof link.href === "string") return resolveExternalLink(link);
46302
- return null;
46303
- }
47197
+ run: (value, _options, context) => resolveAuthoredLinkHref(value, context)
46304
47198
  }, {
46305
47199
  id: "links.label",
46306
47200
  kind: "formatter",
@@ -46327,6 +47221,21 @@ const linksTransforms = [{
46327
47221
  return null;
46328
47222
  }
46329
47223
  }];
47224
+ function resolveAuthoredLinkHref(value, context = {}) {
47225
+ if (!value || typeof value !== "object") return null;
47226
+ const link = value;
47227
+ switch (link.kind) {
47228
+ case "internal": return resolveInternalLink(link, context);
47229
+ case "page": return resolvePageLink(link, context);
47230
+ case "entry": return resolveEntryLink(link, context);
47231
+ case "external":
47232
+ case "url": return resolveExternalLink(link);
47233
+ default: return resolvePreResolvedLink(link);
47234
+ }
47235
+ }
47236
+ function resolvePreResolvedLink(link) {
47237
+ return !(typeof link.kind === "string" ? link.kind : null) && typeof link.href === "string" ? resolveExternalLink(link) : null;
47238
+ }
46330
47239
  const eventCalendarSelectionParam = {
46331
47240
  id: "events.calendarSelectionParam",
46332
47241
  kind: "formatter",