@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
@@ -1941,6 +1941,29 @@ function canonicalEventPricingDisplay(pricing) {
1941
1941
  default: return assertNever$2(pricing);
1942
1942
  }
1943
1943
  }
1944
+ /**
1945
+ * The single renderer for a precomputed booking price `display` ADT. This is
1946
+ * the one place booking price variants map to user-facing strings — every call
1947
+ * site (booking entry, events calendar, event/course details, listings, portal)
1948
+ * should route through here so labels never drift. Adding a variant to the ADT
1949
+ * breaks compilation here (via `assertNever`) until it is handled.
1950
+ */
1951
+ function formatBookingPriceDisplay(display, options = {}) {
1952
+ switch (display.kind) {
1953
+ case "free": return "Free";
1954
+ case "exact": return formatCurrencyAmount(display.amount, display.currency, options);
1955
+ case "from": return `From ${formatCurrencyAmount(display.amount, display.currency, options)}`;
1956
+ case "range": return `${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)}`;
1957
+ 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)}`;
1958
+ case "unavailable": return "Pricing unavailable";
1959
+ case "external": return display.label;
1960
+ default: return assertNever$2(display);
1961
+ }
1962
+ }
1963
+ /** Render an event's price directly from its pricing value. */
1964
+ function formatEventPricing(pricing, options = {}) {
1965
+ return formatBookingPriceDisplay(publicEventPricingDisplay(pricing), options);
1966
+ }
1944
1967
  //#endregion
1945
1968
  //#region ../core/src/event-page-surface-subject.ts
1946
1969
  function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
@@ -2084,6 +2107,7 @@ function parseBookingTimeZone(timeZone) {
2084
2107
  }
2085
2108
  //#endregion
2086
2109
  //#region ../core/src/booking-date-time.ts
2110
+ const DEFAULT_LOCALE = "en-US";
2087
2111
  const FALLBACK_TIMEZONE = "UTC";
2088
2112
  const START_OF_DAY = {
2089
2113
  hour: 0,
@@ -2206,6 +2230,34 @@ function toScheduledBookingDateTime(input) {
2206
2230
  if (result.ok) return result.value;
2207
2231
  throw new Error(formatParseError(result));
2208
2232
  }
2233
+ function formatBookingInstant(isoString, options) {
2234
+ const date = new Date(isoString);
2235
+ if (Number.isNaN(date.getTime())) return isoString;
2236
+ return getCachedDateTimeFormatter(options.locale ?? DEFAULT_LOCALE, getSafeTimeZone(options.timeZone), options.format).format(date);
2237
+ }
2238
+ function formatBookingDate(input, format = {
2239
+ weekday: "long",
2240
+ year: "numeric",
2241
+ month: "long",
2242
+ day: "numeric"
2243
+ }) {
2244
+ return formatBookingInstant(input.startsAt, {
2245
+ locale: input.locale,
2246
+ timeZone: input.timeZone,
2247
+ format
2248
+ });
2249
+ }
2250
+ function formatBookingTime(input, format = {
2251
+ hour: "numeric",
2252
+ minute: "2-digit",
2253
+ hour12: true
2254
+ }) {
2255
+ return formatBookingInstant(input.startsAt, {
2256
+ locale: input.locale,
2257
+ timeZone: input.timeZone,
2258
+ format
2259
+ });
2260
+ }
2209
2261
  function getBookingLocalDateKey(input) {
2210
2262
  return formatDateKey$1(getLocalScheduleDate(new Date(input.startsAt), input.timeZone));
2211
2263
  }
@@ -2447,6 +2499,14 @@ function formError(field) {
2447
2499
  }
2448
2500
  //#endregion
2449
2501
  //#region ../core/src/mailing-list-targeting.ts
2502
+ const DEFAULT_MAILING_LIST_TARGETING = { mode: "default" };
2503
+ function mailingListTargetingChoiceIds(targeting) {
2504
+ switch (targeting.provider) {
2505
+ case "native": return targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
2506
+ case "brevo": return targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
2507
+ case "mailchimp": return targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
2508
+ }
2509
+ }
2450
2510
  function planMailingListChoicePresentation(targeting) {
2451
2511
  if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
2452
2512
  switch (targeting.provider) {
@@ -2486,6 +2546,103 @@ function planMailingListChoicePresentation(targeting) {
2486
2546
  }
2487
2547
  }
2488
2548
  //#endregion
2549
+ //#region ../core/src/mailing-list-targeting-availability.ts
2550
+ function resolveMailingListDefaultDestination(provider) {
2551
+ switch (provider.provider) {
2552
+ case "native": return provider.topics.find((item) => item.id === provider.defaultTopicId) ?? null;
2553
+ case "brevo": return provider.lists.find((item) => item.id === provider.defaultListId) ?? null;
2554
+ case "mailchimp": return provider.audiences.find((item) => item.id === provider.defaultAudienceId) ?? null;
2555
+ }
2556
+ }
2557
+ function assessMailingListTargeting(context, targeting) {
2558
+ if (context?.tag !== "configured") return {
2559
+ tag: "unavailable",
2560
+ reason: "setup_missing",
2561
+ targeting
2562
+ };
2563
+ const catalog = context.provider;
2564
+ if (targeting.mode === "custom") return assessCustomTargeting(catalog, targeting);
2565
+ const destination = resolveMailingListDefaultDestination(catalog);
2566
+ return destination === null ? {
2567
+ tag: "unavailable",
2568
+ reason: "default_missing",
2569
+ targeting
2570
+ } : {
2571
+ tag: "available",
2572
+ value: {
2573
+ mode: "default",
2574
+ targeting,
2575
+ provider: catalog.provider,
2576
+ destination
2577
+ }
2578
+ };
2579
+ }
2580
+ function assessCustomTargeting(catalog, targeting) {
2581
+ switch (targeting.provider) {
2582
+ case "native": return catalog.provider === "native" ? assessMatchedChoices({
2583
+ provider: "native",
2584
+ catalog,
2585
+ targeting
2586
+ }, catalog.topics) : {
2587
+ tag: "unavailable",
2588
+ reason: "provider_mismatch",
2589
+ targeting
2590
+ };
2591
+ case "brevo": return catalog.provider === "brevo" ? assessMatchedChoices({
2592
+ provider: "brevo",
2593
+ catalog,
2594
+ targeting
2595
+ }, catalog.lists) : {
2596
+ tag: "unavailable",
2597
+ reason: "provider_mismatch",
2598
+ targeting
2599
+ };
2600
+ case "mailchimp": {
2601
+ if (catalog.provider !== "mailchimp") return {
2602
+ tag: "unavailable",
2603
+ reason: "provider_mismatch",
2604
+ targeting
2605
+ };
2606
+ const audience = catalog.audiences.find((item) => item.id === targeting.audienceId);
2607
+ return audience === void 0 ? {
2608
+ tag: "unavailable",
2609
+ reason: "audience_missing",
2610
+ value: {
2611
+ mode: "custom",
2612
+ provider: "mailchimp",
2613
+ catalog,
2614
+ targeting
2615
+ }
2616
+ } : assessMatchedChoices({
2617
+ provider: "mailchimp",
2618
+ catalog,
2619
+ targeting,
2620
+ audience
2621
+ }, audience.interests);
2622
+ }
2623
+ }
2624
+ }
2625
+ function assessMatchedChoices(matched, choices) {
2626
+ const selected = new Set(mailingListTargetingChoiceIds(matched.targeting));
2627
+ const available = new Set(choices.map((item) => item.id));
2628
+ const missingIds = [...selected].filter((id) => !available.has(id));
2629
+ const value = {
2630
+ mode: "custom",
2631
+ ...matched,
2632
+ choices,
2633
+ selectedChoices: choices.filter((item) => selected.has(item.id))
2634
+ };
2635
+ return missingIds.length > 0 ? {
2636
+ tag: "unavailable",
2637
+ reason: "choices_missing",
2638
+ missingIds,
2639
+ value
2640
+ } : {
2641
+ tag: "available",
2642
+ value
2643
+ };
2644
+ }
2645
+ //#endregion
2489
2646
  //#region ../core/src/mailing-list-runtime.ts
2490
2647
  function parseMailingListRuntimeContext(value) {
2491
2648
  if (!isRecord$8(value)) return invalidRuntimeContext();
@@ -2550,57 +2707,47 @@ function parseBrevoRuntimeProvider(value) {
2550
2707
  function parseMailchimpRuntimeProvider(value) {
2551
2708
  if (!Array.isArray(value.audiences)) return null;
2552
2709
  const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
2553
- if (defaultAudienceId === null) return null;
2554
- const audiences = [];
2555
- for (const audience of value.audiences) {
2556
- if (!isRecord$8(audience)) return null;
2557
- const id = parseMailchimpAudienceId(audience.id);
2558
- const name = parseName(audience.name);
2559
- if (id === null || name === null || !Array.isArray(audience.interests)) return null;
2560
- const interests = parseMailchimpInterests(audience.interests);
2561
- if (interests === null) return null;
2562
- audiences.push({
2563
- id,
2564
- name,
2565
- interests
2566
- });
2567
- }
2568
- return {
2710
+ const audiences = Array.from(value.audiences, parseMailchimpAudience);
2711
+ return defaultAudienceId !== null && audiences.every((audience) => audience !== null) ? {
2569
2712
  provider: "mailchimp",
2570
2713
  defaultAudienceId,
2571
2714
  audiences
2572
- };
2715
+ } : null;
2573
2716
  }
2574
- function parseMailchimpInterests(value) {
2575
- const interests = [];
2576
- for (const interest of value) {
2577
- if (!isRecord$8(interest)) return null;
2578
- const id = parseMailchimpInterestId(interest.id);
2579
- const name = parseName(interest.name);
2580
- const categoryName = parseName(interest.categoryName);
2581
- if (id === null || name === null || categoryName === null) return null;
2582
- interests.push({
2583
- id,
2584
- name,
2585
- categoryName
2586
- });
2587
- }
2588
- return interests;
2717
+ function parseMailchimpAudience(value) {
2718
+ if (!isRecord$8(value) || !Array.isArray(value.interests)) return null;
2719
+ const id = parseMailchimpAudienceId(value.id);
2720
+ const name = parseName(value.name);
2721
+ const interests = Array.from(value.interests, parseMailchimpInterest);
2722
+ return id !== null && name !== null && interests.every((interest) => interest !== null) ? {
2723
+ id,
2724
+ name,
2725
+ interests
2726
+ } : null;
2727
+ }
2728
+ function parseMailchimpInterest(value) {
2729
+ if (!isRecord$8(value)) return null;
2730
+ const id = parseMailchimpInterestId(value.id);
2731
+ const name = parseName(value.name);
2732
+ const categoryName = parseName(value.categoryName);
2733
+ return id !== null && name !== null && categoryName !== null ? {
2734
+ id,
2735
+ name,
2736
+ categoryName
2737
+ } : null;
2589
2738
  }
2590
2739
  function parseNamedItems(value, parseId) {
2591
2740
  if (!Array.isArray(value)) return null;
2592
- const items = [];
2593
- for (const item of value) {
2741
+ const items = Array.from(value, (item) => {
2594
2742
  if (!isRecord$8(item)) return null;
2595
2743
  const id = parseId(item.id);
2596
2744
  const name = parseName(item.name);
2597
- if (id === null || name === null) return null;
2598
- items.push({
2745
+ return id !== null && name !== null ? {
2599
2746
  id,
2600
2747
  name
2601
- });
2602
- }
2603
- return items;
2748
+ } : null;
2749
+ });
2750
+ return items.every((item) => item !== null) ? items : null;
2604
2751
  }
2605
2752
  function parseName(value) {
2606
2753
  if (typeof value !== "string") return null;
@@ -2616,6 +2763,7 @@ function invalidRuntimeContext() {
2616
2763
  //#endregion
2617
2764
  //#region ../core/src/mailing-list-targeting-parser.ts
2618
2765
  function parseMailingListPlacementTargeting(value) {
2766
+ if (value == null) return ok(DEFAULT_MAILING_LIST_TARGETING);
2619
2767
  if (!isRecord$7(value)) return targetingError("shape");
2620
2768
  if (value.mode === "default") return ok({ mode: "default" });
2621
2769
  if (value.mode !== "custom") return targetingError("mode");
@@ -2714,14 +2862,11 @@ function parseNonEmptyUniqueIds(value, parseId) {
2714
2862
  }
2715
2863
  function parseUniqueIds(value, parseId) {
2716
2864
  if (!Array.isArray(value)) return targetingError("targets");
2717
- const parsed = [];
2718
- for (const item of value) {
2865
+ return Array.from(value).reduce((parsed, item) => {
2866
+ if (!parsed.ok) return parsed;
2719
2867
  const id = parseId(item);
2720
- if (id === null) return targetingError("targets");
2721
- if (parsed.includes(id)) return targetingError("duplicate_target");
2722
- parsed.push(id);
2723
- }
2724
- return ok(parsed);
2868
+ return id === null ? targetingError("targets") : parsed.value.includes(id) ? targetingError("duplicate_target") : ok([...parsed.value, id]);
2869
+ }, ok([]));
2725
2870
  }
2726
2871
  function isRecord$7(value) {
2727
2872
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3150,6 +3295,45 @@ function resolveSiteVerticalBookingConfig(rawConfig) {
3150
3295
  };
3151
3296
  }
3152
3297
  //#endregion
3298
+ //#region ../core/src/event-preset-vocabulary.ts
3299
+ const EVENT_PRESET_VOCABULARY = {
3300
+ event: {
3301
+ label: "Event",
3302
+ pluralLabel: "Events",
3303
+ description: "Any event, repeating or manually dated, that does not fit the other categories."
3304
+ },
3305
+ class: {
3306
+ label: "Class",
3307
+ pluralLabel: "Classes",
3308
+ description: "Regular scheduled classes and group sessions."
3309
+ },
3310
+ course: {
3311
+ label: "Course",
3312
+ pluralLabel: "Courses",
3313
+ description: "A fixed set of dates that people book together."
3314
+ },
3315
+ term: {
3316
+ label: "Term",
3317
+ pluralLabel: "Terms",
3318
+ description: "A fixed term of sessions that people book together."
3319
+ },
3320
+ workshop: {
3321
+ label: "Workshop",
3322
+ pluralLabel: "Workshops",
3323
+ description: "One-off workshops, day sessions, or special events."
3324
+ },
3325
+ retreat: {
3326
+ label: "Retreat",
3327
+ pluralLabel: "Retreats",
3328
+ description: "Multi-day or residential immersive experiences."
3329
+ },
3330
+ performance: {
3331
+ label: "Performance",
3332
+ pluralLabel: "Performances",
3333
+ description: "Audience-facing shows, concerts, recitals, or showcases."
3334
+ }
3335
+ };
3336
+ //#endregion
3153
3337
  //#region ../api/src/publicEventEndpointDefinitions.ts
3154
3338
  const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3155
3339
  checkPublicEventRegistrationNextStep: {
@@ -24185,6 +24369,14 @@ z.object({
24185
24369
  icon: z.string().optional()
24186
24370
  });
24187
24371
  //#endregion
24372
+ //#region ../blocks/src/lib/typeGuards.ts
24373
+ /**
24374
+ * Strict record guard: excludes null, primitives, and arrays.
24375
+ */
24376
+ function isRecord$5(value) {
24377
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24378
+ }
24379
+ //#endregion
24188
24380
  //#region ../blocks/src/system/designCapabilities.ts
24189
24381
  function declareBlockDesignCapability(capability) {
24190
24382
  return {
@@ -31038,6 +31230,15 @@ const formBlockDefinition = {
31038
31230
  runtime: formBlockRuntime
31039
31231
  };
31040
31232
  //#endregion
31233
+ //#region ../blocks/src/system/helpers/normalizeMailingListBlockContent.ts
31234
+ /** Fill the declared default without admitting or rewriting invalid custom settings. */
31235
+ function normalizeMailingListBlockContent(record) {
31236
+ return record.targeting == null ? {
31237
+ ...record,
31238
+ targeting: DEFAULT_MAILING_LIST_TARGETING
31239
+ } : record;
31240
+ }
31241
+ //#endregion
31041
31242
  //#region ../blocks/src/system/blocks/contact-newsletter.ts
31042
31243
  const contactNewsletterLayout = styledSection({
31043
31244
  children: sectionContainer([el("div", { className: "contact-newsletter-grid" }, [el("div", { className: "contact-newsletter-column contact-newsletter-contact" }, [el("div", { className: "contact-newsletter-copy" }, [text({
@@ -31201,6 +31402,10 @@ const contactNewsletterBlockDefinition = {
31201
31402
  ],
31202
31403
  icon: "MailPlus"
31203
31404
  }),
31405
+ contentNormalizer: {
31406
+ fieldIds: ["targeting"],
31407
+ normalize: normalizeMailingListBlockContent
31408
+ },
31204
31409
  dataSchemas: { form: formDataSchema.optional() },
31205
31410
  dataLoaders: { form: {
31206
31411
  endpoint: "getPublicFormById",
@@ -32618,14 +32823,6 @@ const mediaTransforms = [{
32618
32823
  }
32619
32824
  }];
32620
32825
  //#endregion
32621
- //#region ../blocks/src/lib/typeGuards.ts
32622
- /**
32623
- * Strict record guard: excludes null, primitives, and arrays.
32624
- */
32625
- function isRecord$5(value) {
32626
- return typeof value === "object" && value !== null && !Array.isArray(value);
32627
- }
32628
- //#endregion
32629
32826
  //#region ../blocks/src/system/transforms/registry/content.ts
32630
32827
  const supportedRichTextTokenSchema = z.enum(["year", "site_name"]);
32631
32828
  const interpolateRichTextTokensOptionsSchema = z.object({ tokens: z.array(supportedRichTextTokenSchema).default(["year", "site_name"]) });
@@ -35601,6 +35798,14 @@ bookingPaymentOptionSchemas.flexibleBalanceConfig;
35601
35798
  bookingPaymentOptionSchemas.paymentCollectionOption;
35602
35799
  const bookingPaymentTermsSchema = bookingPaymentOptionSchemas.paymentTerms;
35603
35800
  bookingPaymentOptionSchemas.paymentOptionsChoice;
35801
+ z.strictObject({
35802
+ currency: z.string().trim().length(3),
35803
+ totalCents: nonNegativeIntegerSchema,
35804
+ dueNowCents: nonNegativeIntegerSchema,
35805
+ laterPaymentAmountsCents: z.array(nonNegativeIntegerSchema),
35806
+ paymentOption: z.union([createBookingPaymentOptionSchemas("strict").paymentCollectionOption, z.strictObject({ kind: z.literal("not_required") })]),
35807
+ promisedDiscountId: z.string().min(1).nullable().optional()
35808
+ });
35604
35809
  //#endregion
35605
35810
  //#region ../blocks/src/system/blocks/shared/bookingPaymentTerms.ts
35606
35811
  const blockBookingPaymentTermsSchema = bookingPaymentTermsSchema;
@@ -35841,6 +36046,10 @@ const publicEventBaseSchema = z.object({
35841
36046
  eventDestinationMode: eventDestinationModeSchema.optional(),
35842
36047
  scheduleScope: eventScheduleScopeSchema$2.optional(),
35843
36048
  recurrence: publicEventRecurrenceSchema.optional(),
36049
+ scheduleSummary: z.object({
36050
+ recurrenceDescription: z.string(),
36051
+ startTime: z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/).nullable()
36052
+ }).nullable().optional(),
35844
36053
  id: z.string(),
35845
36054
  occurrenceId: z.string(),
35846
36055
  seriesId: z.string(),
@@ -36251,68 +36460,267 @@ const occurrenceContextSchema = z.object({
36251
36460
  overrides: z.record(z.string(), z.unknown()).nullable().optional()
36252
36461
  }).nullable();
36253
36462
  //#endregion
36254
- //#region ../blocks/src/system/runtime/api/readAuthority.ts
36255
- const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
36256
- kind: z.literal("preview"),
36257
- token: z.string().min(1)
36258
- }).strict()]);
36259
- const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
36260
- kind: z.literal("unsupported"),
36261
- reason: z.enum([
36262
- "unsupportedSubject",
36263
- "invalidEventSeriesId",
36264
- "invalidEventSeriesBookingScope",
36265
- "invalidEventOfferingId",
36266
- "invalidEventOfferingBookingScope",
36267
- "invalidEventOfferingCatalogue"
36268
- ])
36269
- });
36270
- const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
36271
- const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
36272
- if (unsupported.success) return unsupported.data;
36273
- const parsed = parseEventPageSurfaceSubject(value);
36274
- if (parsed.ok) return parsed.value;
36275
- ctx.addIssue({
36276
- code: z.ZodIssueCode.custom,
36277
- message: `Invalid event page-surface subject (${parsed.error.kind})`
36278
- });
36279
- return z.NEVER;
36280
- });
36281
- function resolveEventRegistrationScope(context) {
36282
- const occurrenceSeriesId = context.occurrenceContext?.seriesId;
36283
- if (occurrenceSeriesId) return {
36284
- kind: "occurrenceSeries",
36285
- seriesId: occurrenceSeriesId
36463
+ //#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
36464
+ /**
36465
+ * Compute remaining spots for an event from `availableSpots` with a fallback
36466
+ * to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
36467
+ * for uncapped events so call sites can render no capacity indicator at all.
36468
+ *
36469
+ * The single source of truth for capacity math across event cards, agenda
36470
+ * rows, and timetable cells — keep wording variations in the call sites.
36471
+ */
36472
+ function getEventAvailability(event) {
36473
+ if (event.capacity == null) return {
36474
+ available: null,
36475
+ isFull: false
36286
36476
  };
36287
- if (context.subject?.kind === "eventSeries") return {
36288
- kind: "pageSurfaceSeries",
36289
- seriesId: context.subject.eventSeriesId
36477
+ const available = event.availableSpots ?? event.capacity - event.registeredCount;
36478
+ if (available <= 0) return {
36479
+ available: 0,
36480
+ isFull: true
36290
36481
  };
36291
- if (context.subject?.kind === "eventOffering") return {
36292
- kind: "pageSurfaceOffering",
36293
- offeringId: context.subject.eventOfferingId,
36294
- seriesIds: context.subject.catalogueEventSeriesIds
36482
+ return {
36483
+ available,
36484
+ isFull: false
36295
36485
  };
36296
- if (context.subject?.kind === "unsupported") return {
36297
- kind: "unsupportedPageSurfaceSubject",
36298
- reason: context.subject.reason
36486
+ }
36487
+ /**
36488
+ * Pick the capacity row a booking decision should read: the selected
36489
+ * attendance mode's availability when the visitor has chosen one and the
36490
+ * event publishes it, otherwise the event-level aggregate.
36491
+ *
36492
+ * The mode row is used wholesale. A mode with `capacity: null` on a capped
36493
+ * event is uncapped *by design*: the registration write path applies the
36494
+ * same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
36495
+ * back to the event's `availableSpots` here would block bookings the server
36496
+ * accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
36497
+ */
36498
+ function selectEventCapacitySource(event, attendanceMode) {
36499
+ if (!attendanceMode) return event;
36500
+ return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
36501
+ }
36502
+ /**
36503
+ * Whether the visitor's next step is the waitlist: the source is full *and*
36504
+ * a waitlist is open. The one place this is decided — cards, rows, the
36505
+ * wizard's steps and its action bar all route through here so they agree,
36506
+ * including for producers that omit `availableSpots` and rely on the
36507
+ * `capacity - registeredCount` fallback.
36508
+ */
36509
+ function canJoinEventWaitlist(event, options) {
36510
+ const source = selectEventCapacitySource(event, options?.attendanceMode);
36511
+ return getEventAvailability(source).isFull && source.waitlistEnabled === true;
36512
+ }
36513
+ /**
36514
+ * Remaining places at or below which an event reads as scarce and the exact
36515
+ * count is worth showing. Above it, no number is shown at all: "147 places
36516
+ * left" is not something a visitor acts on, and it is the figure most likely
36517
+ * to be stale on a cached page.
36518
+ *
36519
+ * max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
36520
+ *
36521
+ * The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
36522
+ * 5% floor stops large events staying silent until three remain.
36523
+ * capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
36524
+ */
36525
+ function lowCapacityThreshold(capacity) {
36526
+ return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
36527
+ }
36528
+ function getRemainingPlaces(source) {
36529
+ const { available, isFull } = getEventAvailability(source);
36530
+ if (available === null || source.capacity === null) return { kind: "unlimited" };
36531
+ if (isFull) return {
36532
+ kind: "full",
36533
+ waitlist: canJoinEventWaitlist(source)
36299
36534
  };
36300
- return { kind: "unscoped" };
36535
+ if (available <= lowCapacityThreshold(source.capacity)) return {
36536
+ kind: "low",
36537
+ remaining: available
36538
+ };
36539
+ return { kind: "open" };
36301
36540
  }
36302
- function resolveEventRegistrationScopedEvents(events, context) {
36303
- const scope = resolveEventRegistrationScope(context);
36304
- switch (scope.kind) {
36305
- case "occurrenceSeries":
36306
- case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
36307
- case "pageSurfaceOffering": {
36308
- const seriesIds = new Set(scope.seriesIds);
36309
- return events.filter((event) => seriesIds.has(event.seriesId));
36310
- }
36311
- case "unsupportedPageSurfaceSubject": return [];
36312
- case "unscoped": return [...events];
36541
+ /**
36542
+ * The one place a remaining-places count becomes text. Returns `null` when
36543
+ * nothing should be shown: uncapped events, and capped events above the
36544
+ * scarcity threshold. Surfaces that need their own full-state wording pass
36545
+ * it in rather than re-deriving the count.
36546
+ */
36547
+ function formatRemainingPlaces(source, wording = {}) {
36548
+ const remaining = getRemainingPlaces(source);
36549
+ switch (remaining.kind) {
36550
+ case "unlimited":
36551
+ case "open": return null;
36552
+ case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
36553
+ case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
36554
+ }
36555
+ }
36556
+ function formatPlacesLeft(count, compact) {
36557
+ if (compact) return `${count} left`;
36558
+ return count === 1 ? "1 place left" : `${count} places left`;
36559
+ }
36560
+ function formatTicketTypeRemaining(remainingCapacity) {
36561
+ if (remainingCapacity === null || remainingCapacity > 3) return null;
36562
+ if (remainingCapacity <= 0) return "None left for this ticket type";
36563
+ return `${formatPlacesLeft(remainingCapacity, false)} for this ticket type`;
36564
+ }
36565
+ /**
36566
+ * Which capacity figure wins when they disagree.
36567
+ *
36568
+ * For a hybrid event that publishes both attendance-mode rows, **the mode
36569
+ * rows are authoritative and the event-level aggregate is only a summary**.
36570
+ * The registration write path admits or refuses per mode
36571
+ * (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
36572
+ * aggregate can read full while a mode still has room, and can read open
36573
+ * while the mode the visitor wants is full. Believing the aggregate on a
36574
+ * card is how a "places available" event turns into a dead end at step two.
36575
+ *
36576
+ * Everything else — single-mode events, events that publish no rows, and the
36577
+ * aggregate-only helpers {@link getEventAvailability} /
36578
+ * {@link getRemainingPlaces} that existing callers rely on — keeps reading
36579
+ * the aggregate, unchanged.
36580
+ */
36581
+ function findHybridModeRows(event) {
36582
+ if (event.deliveryMode !== "hybrid") return null;
36583
+ const rows = event.attendanceModes ?? [];
36584
+ const inPerson = rows.find((row) => row.mode === "in_person");
36585
+ const online = rows.find((row) => row.mode === "online");
36586
+ return inPerson && online ? {
36587
+ inPerson,
36588
+ online
36589
+ } : null;
36590
+ }
36591
+ /** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
36592
+ function remainingPlacesRank(remaining) {
36593
+ switch (remaining.kind) {
36594
+ case "unlimited": return 4;
36595
+ case "open": return 3;
36596
+ case "low": return 2;
36597
+ case "full": return remaining.waitlist ? 1 : 0;
36598
+ }
36599
+ }
36600
+ function isMoreOpen(candidate, than) {
36601
+ const rank = remainingPlacesRank(candidate);
36602
+ const otherRank = remainingPlacesRank(than);
36603
+ if (rank !== otherRank) return rank > otherRank;
36604
+ return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
36605
+ }
36606
+ /** True when `candidate` has less room left than `than`. */
36607
+ function isScarcer(candidate, than) {
36608
+ return isMoreOpen(than, candidate);
36609
+ }
36610
+ const ATTENDANCE_MODE_LABEL = {
36611
+ in_person: "In person",
36612
+ online: "Online"
36613
+ };
36614
+ /** Wording for a mode with room to spare, where the count is deliberately withheld. */
36615
+ const PLACES_AVAILABLE = "Places available";
36616
+ /**
36617
+ * The browse-surface label for an event's remaining places. Single-mode
36618
+ * events get exactly the aggregate label {@link formatRemainingPlaces}
36619
+ * produces, and so does a hybrid event whose two modes tell the same story.
36620
+ * When the modes differ, both sides are named: "available" on a card is a
36621
+ * promise the registration wizard would otherwise have to break.
36622
+ *
36623
+ * Hybrid examples, in person full with 3 online places left:
36624
+ * default -> "Online: 3 places left · In person: Full"
36625
+ * { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
36626
+ * { compact: true } -> "Online: 3 left"
36627
+ */
36628
+ function formatEventRemainingPlaces(event, wording = {}) {
36629
+ const rows = findHybridModeRows(event);
36630
+ if (!rows) return formatRemainingPlaces(event, wording);
36631
+ const inPerson = formatRemainingPlaces(rows.inPerson, wording);
36632
+ const online = formatRemainingPlaces(rows.online, wording);
36633
+ if (inPerson === online) return inPerson;
36634
+ if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
36635
+ return formatBothHybridSides(rows, {
36636
+ inPerson,
36637
+ online
36638
+ });
36639
+ }
36640
+ /**
36641
+ * Both sides, the one with room first: that is the half a visitor can act on,
36642
+ * and it should not arrive after a full stop's worth of bad news.
36643
+ */
36644
+ function formatBothHybridSides(rows, labels) {
36645
+ 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(" · ");
36646
+ }
36647
+ function namedModePlaces(mode, label) {
36648
+ return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
36649
+ }
36650
+ /**
36651
+ * Pills and timetable cells have room for one short phrase, so they name the
36652
+ * single mode that carries news rather than listing both: the scarce side
36653
+ * when there is a count to show, and otherwise the side that has closed —
36654
+ * "In person: Full", never "Online only", which reads as a statement about
36655
+ * how the event is delivered rather than about its remaining places.
36656
+ */
36657
+ function formatCompactHybridPlaces(rows, wording) {
36658
+ const inPerson = getRemainingPlaces(rows.inPerson);
36659
+ const online = getRemainingPlaces(rows.online);
36660
+ if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
36661
+ if (inPerson.kind === "full" || online.kind === "full") {
36662
+ const fullSide = inPerson.kind === "full" ? "in_person" : "online";
36663
+ const openSide = inPerson.kind === "full" ? "online" : "in_person";
36664
+ const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
36665
+ return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
36313
36666
  }
36667
+ const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
36668
+ return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
36669
+ }
36670
+ function rowFor(rows, mode) {
36671
+ return mode === "online" ? rows.online : rows.inPerson;
36314
36672
  }
36315
36673
  //#endregion
36674
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDisplay.ts
36675
+ const registrationDisplaySchema = z.object({
36676
+ venue: z.enum(["shown", "hidden"]),
36677
+ placesLeft: z.enum(["shown", "hidden"])
36678
+ });
36679
+ function resolveRegistrationDisplay(source) {
36680
+ return {
36681
+ venue: source.showVenue === false ? "hidden" : "shown",
36682
+ placesLeft: source.showCapacity === false ? "hidden" : "shown"
36683
+ };
36684
+ }
36685
+ /** Venue stays in domain data; views receive only the permitted presentation. */
36686
+ function shownVenue(event, display) {
36687
+ return display.venue === "shown" ? event.venue?.name ?? null : null;
36688
+ }
36689
+ /** Full/waitlist explanations remain visible even when optional counts are hidden. */
36690
+ function placesLeftLabel(source, display, wording = {}) {
36691
+ if ("kind" in source) return ticketPlacesLabel(source.remaining, display);
36692
+ if (source.deliveryMode === "hybrid" && source.attendanceModes?.length) return hybridPlacesLabel(source, display, wording);
36693
+ return capacityPlacesLabel(source, display, wording);
36694
+ }
36695
+ function capacityPlacesLabel(source, display, wording) {
36696
+ const remaining = getRemainingPlaces(source);
36697
+ if (display.placesLeft === "hidden" && remaining.kind !== "full") return null;
36698
+ switch (remaining.kind) {
36699
+ case "full": return formatRemainingPlaces(source, wording);
36700
+ case "low": return formatRemainingPlaces(source, wording);
36701
+ case "unlimited": return wording.unlimited ?? null;
36702
+ case "open": return wording.open ?? null;
36703
+ default: return assertNever$2(remaining);
36704
+ }
36705
+ }
36706
+ function hybridPlacesLabel(event, display, wording) {
36707
+ if (display.placesLeft === "shown") return formatEventRemainingPlaces(event, wording);
36708
+ return (event.attendanceModes ?? []).flatMap((mode) => {
36709
+ const label = placesLeftLabel(mode, display, wording);
36710
+ return label ? [`${mode.mode === "online" ? "Online" : "In person"}: ${label}`] : [];
36711
+ }).join(" · ") || null;
36712
+ }
36713
+ function ticketPlacesLabel(remaining, display) {
36714
+ if (remaining !== null && remaining > 0 && display.placesLeft === "hidden") return null;
36715
+ return formatTicketTypeRemaining(remaining);
36716
+ }
36717
+ //#endregion
36718
+ //#region ../blocks/src/system/runtime/api/readAuthority.ts
36719
+ const browserReadAuthoritySchema = z.discriminatedUnion("kind", [z.object({ kind: z.literal("published") }).strict(), z.object({
36720
+ kind: z.literal("preview"),
36721
+ token: z.string().min(1)
36722
+ }).strict()]);
36723
+ //#endregion
36316
36724
  //#region ../calendar/src/grid.ts
36317
36725
  const MONTH_NAMES = [
36318
36726
  "January",
@@ -36399,6 +36807,11 @@ function buildCalendarViewport(input) {
36399
36807
  //#endregion
36400
36808
  //#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
36401
36809
  /**
36810
+ * Date and time presentation for events: the single place an event's
36811
+ * timestamps become strings or date buckets. Calendar-grid primitives are
36812
+ * re-exported from @riverbankcms/calendar so block code has one import.
36813
+ */
36814
+ /**
36402
36815
  * The English locale a site's public dates are written in, read from the
36403
36816
  * site's own time zone (Will, 2026-09-26: "date style from site time zone").
36404
36817
  *
@@ -36418,6 +36831,28 @@ function eventDateLocale(siteTimeZone) {
36418
36831
  if (siteTimeZone === "Pacific/Auckland" || siteTimeZone === "Pacific/Chatham") return "en-NZ";
36419
36832
  return "en-US";
36420
36833
  }
36834
+ function formatEventCompactDateRail(isoTimestamp, options, letterCase = "capitals") {
36835
+ const locale = options?.locale ?? "en-US";
36836
+ const dateTime = toScheduledBookingDateTime({
36837
+ startsAt: isoTimestamp,
36838
+ timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
36839
+ locale
36840
+ });
36841
+ const cased = (value) => letterCase === "capitals" ? value.toLocaleUpperCase(locale) : value;
36842
+ const weekday = cased(formatBookingDate(dateTime, { weekday: "short" }));
36843
+ const month = cased(formatBookingDate(dateTime, { month: "short" }).slice(0, 3));
36844
+ return {
36845
+ weekday,
36846
+ day: formatBookingDate(dateTime, { day: "numeric" }),
36847
+ month,
36848
+ label: formatBookingDate(dateTime, {
36849
+ weekday: "long",
36850
+ year: "numeric",
36851
+ month: "long",
36852
+ day: "numeric"
36853
+ })
36854
+ };
36855
+ }
36421
36856
  //#endregion
36422
36857
  //#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
36423
36858
  function isRecord$4(value) {
@@ -36437,6 +36872,15 @@ function resolveRuntimeEventDestination(event) {
36437
36872
  };
36438
36873
  }
36439
36874
  //#endregion
36875
+ //#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
36876
+ /**
36877
+ * Format an event's price directly from its pricing value (without the full
36878
+ * event). Same output as {@link formatEventPrice}.
36879
+ */
36880
+ function formatEventPriceDisplay(pricing) {
36881
+ return formatEventPricing(pricing, { decimals: "auto" });
36882
+ }
36883
+ //#endregion
36440
36884
  //#region ../blocks/src/system/runtime/nodes/events/domain/listWeek.ts
36441
36885
  /** Civil weeks use the site zone, including weeks containing a clock change. */
36442
36886
  function buildEventListWeek(anchor, startOfWeek, timeZone) {
@@ -36471,150 +36915,6 @@ function eventListWindowEvents(events, window) {
36471
36915
  });
36472
36916
  }
36473
36917
  //#endregion
36474
- //#region ../blocks/src/system/constants/events.ts
36475
- const eventCalendarDisplayModeValues = [
36476
- "combined",
36477
- "calendar",
36478
- "list",
36479
- "timetable",
36480
- "week-board"
36481
- ];
36482
- const eventCalendarDefaultViewValues = ["month", "week"];
36483
- const eventCalendarListLayoutValues = [
36484
- "stack",
36485
- "grid",
36486
- "compact"
36487
- ];
36488
- const arrangementOnlyListLayouts = ["ledger", "day-groups"];
36489
- const eventListArrangementValues = [...eventCalendarListLayoutValues, ...arrangementOnlyListLayouts];
36490
- const eventCalendarListLayoutOptions = [
36491
- {
36492
- id: "stack",
36493
- label: "Stack"
36494
- },
36495
- {
36496
- id: "grid",
36497
- label: "Grid"
36498
- },
36499
- {
36500
- id: "compact",
36501
- label: "Compact"
36502
- }
36503
- ];
36504
- function isEventCalendarDisplayMode(value) {
36505
- return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
36506
- }
36507
- function normalizeEventCalendarDisplayMode$1(input) {
36508
- if (isEventCalendarDisplayMode(input.variant)) return input.variant;
36509
- return isEventCalendarDisplayMode(input.displayMode) ? input.displayMode : void 0;
36510
- }
36511
- /** One closed authority for the projections required by each rendered mode. */
36512
- function resolveEventCalendarReadPlan(input) {
36513
- const mode = normalizeEventCalendarDisplayMode$1(input) ?? "combined";
36514
- switch (mode) {
36515
- case "combined": return {
36516
- mode,
36517
- jobs: "both",
36518
- readProfile: "listing-card"
36519
- };
36520
- case "list": return {
36521
- mode,
36522
- jobs: "list",
36523
- readProfile: "listing-card"
36524
- };
36525
- case "calendar": return {
36526
- mode,
36527
- jobs: "calendar",
36528
- readProfile: "listing-card"
36529
- };
36530
- case "timetable":
36531
- case "week-board": return {
36532
- mode,
36533
- jobs: "calendar",
36534
- readProfile: "timetable"
36535
- };
36536
- }
36537
- }
36538
- function isEventCalendarDefaultView(value) {
36539
- return typeof value === "string" && eventCalendarDefaultViewValues.includes(value);
36540
- }
36541
- function isEventCalendarListLayout(value) {
36542
- return typeof value === "string" && eventCalendarListLayoutValues.includes(value);
36543
- }
36544
- /** The calendar manifest owns its secondary arrangement axis. */
36545
- const eventCalendarVariantMeaning = {
36546
- kind: "mode",
36547
- arrangementField: "listLayout",
36548
- keys: eventCalendarListLayoutValues,
36549
- lookOnlyKeys: [
36550
- ...arrangementOnlyListLayouts,
36551
- "month-grid",
36552
- "month-day"
36553
- ],
36554
- defaultKey: "stack",
36555
- modeLabels: {
36556
- list: "List",
36557
- calendar: "Month",
36558
- combined: "List and month",
36559
- timetable: "Timetable",
36560
- "week-board": "Week board"
36561
- }
36562
- };
36563
- /** Weekly lists share the arrangement-Look admission used by calendar arrangements. */
36564
- function eventCalendarListGroupingDecision(input) {
36565
- const available = input.arrangementLooks && resolveEventCalendarReadPlan(input).mode === "list";
36566
- return {
36567
- available,
36568
- grouping: available && input.listBy === "week" ? "week" : "upcoming"
36569
- };
36570
- }
36571
- //#endregion
36572
- //#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
36573
- const previewStageSchema = z.enum(["preview", "published"]);
36574
- const registrationButtonVariantSchema = blockButtonRoleSchema;
36575
- const eventButtonVariantSchema = blockButtonRoleSchema;
36576
- const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
36577
- const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
36578
- const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
36579
- const columnOptionSchema = z.enum([
36580
- "2",
36581
- "3",
36582
- "4"
36583
- ]);
36584
- const weekStartSchema = z.enum(["sunday", "monday"]);
36585
- const filterAutoShowSchema = z.enum([
36586
- "auto",
36587
- "always",
36588
- "never"
36589
- ]);
36590
- const calendarTabViewsSchema = z.enum([
36591
- "both",
36592
- "month",
36593
- "week"
36594
- ]);
36595
- //#endregion
36596
- //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
36597
- /**
36598
- * Determine which wizard steps to show based on context and payment requirements.
36599
- *
36600
- * @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
36601
- * @param needsPaymentStep - Whether user must complete payment method selection
36602
- * @returns Array of wizard steps to display
36603
- */
36604
- function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep, paymentActive = false) {
36605
- const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
36606
- "select-event",
36607
- "select-tickets",
36608
- "enter-details"
36609
- ];
36610
- if (!needsPaymentStep) return baseSteps;
36611
- return paymentActive ? [
36612
- ...baseSteps,
36613
- "select-payment",
36614
- "payment"
36615
- ] : [...baseSteps, "select-payment"];
36616
- }
36617
- //#endregion
36618
36918
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
36619
36919
  function eventMatchesOccurrenceId(event, occurrenceId) {
36620
36920
  return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
@@ -36638,6 +36938,424 @@ function resolveEventRequiresPayment(input) {
36638
36938
  return publicEventPricingType(input.event.pricing) === "paid";
36639
36939
  }
36640
36940
  //#endregion
36941
+ //#region ../blocks/src/system/runtime/nodes/event-registration/utils.ts
36942
+ /**
36943
+ * The locale a site's booking dates are written in: the one the site's time
36944
+ * zone places it in (`eventDateLocale`, shared with event cards), or British
36945
+ * English when the site names no zone.
36946
+ */
36947
+ function bookingDateLocale(siteTimeZone) {
36948
+ return siteTimeZone ? eventDateLocale(siteTimeZone) : "en-GB";
36949
+ }
36950
+ function bookingInstant(startsAt, input) {
36951
+ return toScheduledBookingDateTime({
36952
+ startsAt,
36953
+ timeZone: toBookingTimeZone(input.timeZone),
36954
+ locale: bookingDateLocale(input.siteTimeZone)
36955
+ });
36956
+ }
36957
+ /** "7:00pm": a twelve-hour clock with a lowercase, unspaced day period. */
36958
+ function bookingClock(startsAt, input) {
36959
+ return formatBookingTime(bookingInstant(startsAt, input), {
36960
+ hour: "numeric",
36961
+ minute: "2-digit",
36962
+ hour12: true
36963
+ }).replace(/\s*([ap])\.?\s?m\.?$/i, (_match, period) => `${period.toLowerCase()}m`);
36964
+ }
36965
+ /** "7:00pm – 8:15pm", or just the start when there's no distinct end. */
36966
+ function clockRange(input) {
36967
+ const start = bookingClock(input.startsAt, input);
36968
+ const end = input.endsAt ? bookingClock(input.endsAt, input) : null;
36969
+ return end && end !== start ? `${start} – ${end}` : start;
36970
+ }
36971
+ /** A stored local clock has no date or DST offset; UTC preserves its digits. */
36972
+ function formatBookingScheduleSummary(event) {
36973
+ const summary = event.scheduleSummary;
36974
+ if (!summary) return null;
36975
+ const time = summary.startTime ? formatBookingWhen({
36976
+ startsAt: `1970-01-01T${summary.startTime}:00Z`,
36977
+ timeZone: "UTC",
36978
+ siteTimeZone: event.siteTimeZone
36979
+ }, "time") : null;
36980
+ return [summary.recurrenceDescription, time].filter(Boolean).join(" · ");
36981
+ }
36982
+ /** The one booking date formatter; every variant derives from it. */
36983
+ function formatBookingWhen(input, variant) {
36984
+ const instant = bookingInstant(input.startsAt, input);
36985
+ const day = formatBookingDate(instant, {
36986
+ weekday: "short",
36987
+ day: "numeric",
36988
+ month: "short"
36989
+ });
36990
+ switch (variant) {
36991
+ case "day": return day;
36992
+ case "dayLong": return formatBookingDate(instant, {
36993
+ weekday: "long",
36994
+ day: "numeric",
36995
+ month: "long"
36996
+ });
36997
+ case "month": return formatBookingDate(instant, { month: "long" });
36998
+ case "monthYear": return formatBookingDate(instant, {
36999
+ month: "long",
37000
+ year: "numeric"
37001
+ });
37002
+ case "time": return bookingClock(input.startsAt, input);
37003
+ case "timeRange": return clockRange(input);
37004
+ case "short": return `${day}, ${bookingClock(input.startsAt, input)}`;
37005
+ case "long": return `${day} · ${clockRange(input)}`;
37006
+ }
37007
+ }
37008
+ /** A single instant in the short form, e.g. "Mon 5 Oct, 7:00pm". */
37009
+ function formatEventDateTime(isoTimestamp, options) {
37010
+ return formatBookingWhen({
37011
+ startsAt: isoTimestamp,
37012
+ timeZone: options?.timeZone,
37013
+ siteTimeZone: options?.siteTimeZone
37014
+ }, "short");
37015
+ }
37016
+ /** The title an occurrence was given on its own, when it overrides the event's. */
37017
+ function occurrenceTitleOverride(occurrence) {
37018
+ const title = occurrence?.overrides?.title;
37019
+ return typeof title === "string" && title.trim() ? title.trim() : null;
37020
+ }
37021
+ /** The event as this page names it: an occurrence's own title wins. */
37022
+ function withOccurrenceTitle(event, occurrence) {
37023
+ const title = occurrenceTitleOverride(occurrence);
37024
+ return event && title ? {
37025
+ ...event,
37026
+ title
37027
+ } : event;
37028
+ }
37029
+ function courseSchedule(sessions, siteTimeZone) {
37030
+ const first = sessions[0];
37031
+ if (!first) return null;
37032
+ const last = sessions[sessions.length - 1] ?? first;
37033
+ const firstLabel = formatEventDateTime(first.startsAt, {
37034
+ timeZone: first.timeZone,
37035
+ siteTimeZone
37036
+ });
37037
+ if (last.startsAt === first.startsAt) return `${sessions.length} session${sessions.length === 1 ? "" : "s"} · ${firstLabel}`;
37038
+ const lastLabel = formatEventDateTime(last.startsAt, {
37039
+ timeZone: last.timeZone,
37040
+ siteTimeZone
37041
+ });
37042
+ return `${sessions.length} sessions · ${firstLabel} – ${lastLabel}`;
37043
+ }
37044
+ /**
37045
+ * When a booking happens, in one line: the occurrence's date and time, or a
37046
+ * course's session count and first and last dates. `short` by default (bar,
37047
+ * confirmation); `long` adds the end time (card, panel header).
37048
+ */
37049
+ function formatRegistrationSchedule(event, occurrence, variant = "short") {
37050
+ if (event.registrationSubject.kind === "wholeSeries") return courseSchedule(event.registrationSubject.sessions, event.siteTimeZone);
37051
+ const input = scheduleInput(event, occurrence ?? null);
37052
+ return input ? formatBookingWhen(input, variant) : null;
37053
+ }
37054
+ /** The occurrence's own times when the page names one, otherwise the event's. */
37055
+ function scheduleInput(event, occurrence) {
37056
+ const times = occurrence?.startsAt ? {
37057
+ startsAt: occurrence.startsAt,
37058
+ endsAt: occurrence.endsAt ?? null
37059
+ } : {
37060
+ startsAt: event.startsAt,
37061
+ endsAt: event.endsAt
37062
+ };
37063
+ if (!times.startsAt) return null;
37064
+ return {
37065
+ ...times,
37066
+ timeZone: occurrence?.timeZone ?? event.timeZone ?? null,
37067
+ siteTimeZone: event.siteTimeZone
37068
+ };
37069
+ }
37070
+ //#endregion
37071
+ //#region ../blocks/src/system/runtime/nodes/event-registration/wizardSteps.ts
37072
+ function registrationRoute(input) {
37073
+ const base = input.chooseEvent ? [
37074
+ "select-event",
37075
+ "select-tickets",
37076
+ "enter-details"
37077
+ ] : ["select-tickets", "enter-details"];
37078
+ switch (input.payment) {
37079
+ case "none": return base;
37080
+ case "options": return [...base, "select-payment"];
37081
+ case "optionsAndPayment": return [
37082
+ ...base,
37083
+ "select-payment",
37084
+ "payment"
37085
+ ];
37086
+ }
37087
+ }
37088
+ const REGISTRATION_STEP_LABELS = [
37089
+ "Event",
37090
+ "Date",
37091
+ "Tickets",
37092
+ "Places",
37093
+ "Your details",
37094
+ "How you’ll pay",
37095
+ "Payment"
37096
+ ];
37097
+ /** Several dates of one series are a date choice; anything else is an event choice. */
37098
+ function getEventSelectionMode(events) {
37099
+ const firstSeriesId = events[0]?.seriesId;
37100
+ return events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId) ? "date" : "event";
37101
+ }
37102
+ /** The label facts for a surface: what the visitor chooses from, and what they book. */
37103
+ function registrationStepLabelContext(scopedEvents, bookedEvent) {
37104
+ return {
37105
+ eventSelection: getEventSelectionMode(scopedEvents),
37106
+ booksWholeSeries: bookedEvent?.registrationSubject.kind === "wholeSeries"
37107
+ };
37108
+ }
37109
+ /** One label owner for the live progress and the server placeholder. */
37110
+ function registrationStepLabel(step, context) {
37111
+ switch (step) {
37112
+ case "select-event": return context.eventSelection === "date" ? "Date" : "Event";
37113
+ case "select-tickets": return context.booksWholeSeries ? "Places" : "Tickets";
37114
+ case "enter-details": return "Your details";
37115
+ case "select-payment": return "How you’ll pay";
37116
+ case "payment": return "Payment";
37117
+ }
37118
+ }
37119
+ function bookingButtonText(buttonText) {
37120
+ return buttonText?.trim() || "Book your place";
37121
+ }
37122
+ function placesLabel(event, input, compact) {
37123
+ return placesLeftLabel(selectEventCapacitySource(event, input.attendanceMode), input.display, { compact });
37124
+ }
37125
+ function isFull(event, mode) {
37126
+ return formatRemainingPlaces(selectEventCapacitySource(event, mode), {
37127
+ full: "full",
37128
+ waitlist: "full"
37129
+ }) === "full";
37130
+ }
37131
+ function priceLabel(event) {
37132
+ return formatEventPriceDisplay(event.pricing).trim() || null;
37133
+ }
37134
+ /** The lowest price a display can mean; null when it names no paid amount. */
37135
+ function lowestPaidAmount(display) {
37136
+ switch (display.kind) {
37137
+ case "exact":
37138
+ case "from": return display.amount;
37139
+ case "range": return display.minAmount;
37140
+ case "mixed": return display.paidFromAmount;
37141
+ case "free":
37142
+ case "unavailable":
37143
+ case "external": return null;
37144
+ }
37145
+ }
37146
+ /**
37147
+ * One price for a set of dates: the shared label when every date costs the
37148
+ * same, otherwise "From" the lowest paid amount, through the one formatter.
37149
+ */
37150
+ function seriesPriceLabel(events) {
37151
+ const firstEvent = events[0];
37152
+ if (!firstEvent) return null;
37153
+ const displays = events.map((event) => publicEventPricingDisplay(event.pricing));
37154
+ const labels = new Set(displays.map((display) => formatBookingPriceDisplay(display, { decimals: "auto" })));
37155
+ if (labels.size === 1) return [...labels][0] || null;
37156
+ const lowest = displays.flatMap((display) => {
37157
+ const amount = lowestPaidAmount(display);
37158
+ return amount === null || !("currency" in display) ? [] : [{
37159
+ amount,
37160
+ currency: display.currency
37161
+ }];
37162
+ }).reduce((best, candidate) => best === null || candidate.amount < best.amount ? candidate : best, null);
37163
+ return lowest ? formatBookingPriceDisplay({
37164
+ kind: "from",
37165
+ amount: lowest.amount,
37166
+ currency: lowest.currency
37167
+ }, { decimals: "auto" }) : priceLabel(firstEvent);
37168
+ }
37169
+ function dateTile(event) {
37170
+ if (event.registrationSubject.kind === "wholeSeries" || !event.startsAt) return null;
37171
+ const rail = formatEventCompactDateRail(event.startsAt, { timeZone: event.timeZone });
37172
+ return {
37173
+ month: rail.month,
37174
+ day: rail.day
37175
+ };
37176
+ }
37177
+ /** "Mon 5 Oct": the compact date the chips and the bar share. */
37178
+ function formatBookingShortDate(event) {
37179
+ return formatBookingWhen(event, "day");
37180
+ }
37181
+ function byStart(left, right) {
37182
+ return Date.parse(left.startsAt) - Date.parse(right.startsAt);
37183
+ }
37184
+ function singleEntry(event, input) {
37185
+ const isCourse = event.registrationSubject.kind === "wholeSeries";
37186
+ return {
37187
+ kind: "single",
37188
+ title: event.title,
37189
+ when: formatRegistrationSchedule(event, null, "long"),
37190
+ shortWhen: formatRegistrationSchedule(event),
37191
+ venue: shownVenue(event, input.display),
37192
+ price: priceLabel(event),
37193
+ places: placesLabel(event, input, false),
37194
+ tile: dateTile(event),
37195
+ action: isCourse ? `Book the ${input.noun.label}` : bookingButtonText(input.buttonText)
37196
+ };
37197
+ }
37198
+ function chooseDateEntry(input) {
37199
+ const ordered = [...input.events].sort(byStart);
37200
+ const first = ordered[0];
37201
+ const shown = ordered.slice(0, 4);
37202
+ return {
37203
+ kind: "chooseDate",
37204
+ title: first?.title ?? "",
37205
+ schedule: (first ? formatBookingScheduleSummary(first) : null) ?? (ordered.length === 1 ? "1 date" : `${ordered.length} dates`),
37206
+ venue: first ? shownVenue(first, input.display) : null,
37207
+ price: seriesPriceLabel(ordered),
37208
+ nextDate: first ? formatBookingShortDate(first) : null,
37209
+ dateCount: ordered.length,
37210
+ dates: shown.map((event) => ({
37211
+ selectionId: event.selectionId,
37212
+ label: formatBookingShortDate(event),
37213
+ places: placesLabel(event, input, true),
37214
+ full: isFull(event, input.attendanceMode)
37215
+ })),
37216
+ moreCount: Math.max(0, ordered.length - shown.length),
37217
+ action: "Choose a date"
37218
+ };
37219
+ }
37220
+ /** The card before a booking starts; the server renders this too. */
37221
+ function idleBookingEntry(input) {
37222
+ if (input.event) return singleEntry(input.event, input);
37223
+ const only = input.events.length === 1 ? input.events[0] : void 0;
37224
+ if (only) return singleEntry(only, input);
37225
+ if (getEventSelectionMode(input.events) === "date") return chooseDateEntry(input);
37226
+ return {
37227
+ kind: "chooseEvent",
37228
+ title: input.events[0]?.title ?? "",
37229
+ count: input.events.length,
37230
+ noun: input.noun.pluralLabel,
37231
+ action: "Choose an event"
37232
+ };
37233
+ }
37234
+ //#endregion
37235
+ //#region ../blocks/src/system/blocks/offerings-preview/model.ts
37236
+ const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
37237
+ const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
37238
+ const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
37239
+ {
37240
+ preset: "class",
37241
+ sourceKey: "eventPreset:class",
37242
+ dataKey: "classEvents",
37243
+ label: "Classes"
37244
+ },
37245
+ {
37246
+ preset: "workshop",
37247
+ sourceKey: "eventPreset:workshop",
37248
+ dataKey: "workshopEvents",
37249
+ label: "Workshops"
37250
+ },
37251
+ {
37252
+ preset: "retreat",
37253
+ sourceKey: "eventPreset:retreat",
37254
+ dataKey: "retreatEvents",
37255
+ label: "Retreats"
37256
+ },
37257
+ {
37258
+ preset: "performance",
37259
+ sourceKey: "eventPreset:performance",
37260
+ dataKey: "performanceEvents",
37261
+ label: "Performances"
37262
+ }
37263
+ ];
37264
+ const OFFERINGS_PREVIEW_EVENT_PRESETS = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
37265
+ const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
37266
+ const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
37267
+ Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
37268
+ kind: "eventPreset",
37269
+ preset: definition.preset
37270
+ }]));
37271
+ function isOfferingsPreviewSourceKey(value) {
37272
+ return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
37273
+ }
37274
+ function normalizeOfferingsPreviewGroupOrder(value) {
37275
+ if (value == null) return { kind: "legacy" };
37276
+ if (value.length === 0) return { kind: "default" };
37277
+ const keys = uniqueOfferingsPreviewSourceKeys(value);
37278
+ return keys.length > 0 ? {
37279
+ kind: "custom",
37280
+ keys
37281
+ } : { kind: "default" };
37282
+ }
37283
+ function inferLegacyOfferingsPreviewGroupOrder(overrides) {
37284
+ const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
37285
+ return keys.length >= 2 ? keys : [];
37286
+ }
37287
+ function normalizeOfferingsPreviewContentForWrite(value) {
37288
+ if (!isRecord$3(value)) return null;
37289
+ const groupOrderValue = value.groupOrder;
37290
+ if (Array.isArray(groupOrderValue)) {
37291
+ const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
37292
+ return {
37293
+ ...value,
37294
+ groupOrder: normalized.kind === "custom" ? normalized.keys : []
37295
+ };
37296
+ }
37297
+ if (groupOrderValue != null) return {
37298
+ ...value,
37299
+ groupOrder: []
37300
+ };
37301
+ const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$3) : []);
37302
+ return legacyOrder.length > 0 ? {
37303
+ ...value,
37304
+ groupOrder: legacyOrder
37305
+ } : value;
37306
+ }
37307
+ /** Which offerings a site preset row names; one owner for "the course preset means whole-series runs". */
37308
+ function sourceFromPresetSummary(preset) {
37309
+ if (preset.engine === "event_series" && preset.canonicalBehaviour === "course") return { kind: "wholeSeries" };
37310
+ if (preset.engine !== "event_series") return null;
37311
+ if (isOfferingsPreviewEventPreset(preset.canonicalBehaviour)) return {
37312
+ kind: "eventPreset",
37313
+ preset: preset.canonicalBehaviour
37314
+ };
37315
+ return null;
37316
+ }
37317
+ function uniqueOfferingsPreviewSourceKeys(values) {
37318
+ const keys = [];
37319
+ for (const value of values) {
37320
+ if (!isOfferingsPreviewSourceKey(value)) continue;
37321
+ if (!keys.includes(value)) keys.push(value);
37322
+ }
37323
+ return keys;
37324
+ }
37325
+ function isRecord$3(value) {
37326
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37327
+ }
37328
+ function isOfferingsPreviewEventPreset(value) {
37329
+ return typeof value === "string" && OFFERINGS_PREVIEW_EVENT_PRESETS.includes(value);
37330
+ }
37331
+ //#endregion
37332
+ //#region ../blocks/src/system/runtime/nodes/event-registration/offeringNoun.ts
37333
+ function offeringKindForEvent(event) {
37334
+ return event.registrationSubject.kind === "wholeSeries" ? "course" : event.eventPreset ?? "event";
37335
+ }
37336
+ function presetKind(preset) {
37337
+ const source = sourceFromPresetSummary(preset);
37338
+ if (!source) return null;
37339
+ return source.kind === "wholeSeries" ? "course" : source.preset;
37340
+ }
37341
+ /**
37342
+ * What the site calls this kind of offering, in lower case for use mid-sentence
37343
+ * ("Book the course", "3 workshops"). Site preset labels win; the canonical
37344
+ * name stands in when the site has none.
37345
+ */
37346
+ function offeringNounForEvent(event, presets) {
37347
+ if (!event) return lowerCased(EVENT_PRESET_VOCABULARY.event);
37348
+ const kind = offeringKindForEvent(event);
37349
+ const preset = presets?.find((candidate) => presetKind(candidate) === kind);
37350
+ return lowerCased(preset?.label.trim() && preset.pluralLabel.trim() ? preset : EVENT_PRESET_VOCABULARY[kind]);
37351
+ }
37352
+ function lowerCased(noun) {
37353
+ return {
37354
+ label: noun.label.trim().toLocaleLowerCase(),
37355
+ pluralLabel: noun.pluralLabel.trim().toLocaleLowerCase()
37356
+ };
37357
+ }
37358
+ //#endregion
36641
37359
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
36642
37360
  function toRegistrationEvent(event) {
36643
37361
  return {
@@ -36682,6 +37400,279 @@ function toWholeSeriesRegistrationEvents(events) {
36682
37400
  return groupPublicEventsByRun(events).map(toWholeSeriesRegistrationEvent).filter((event) => event !== null);
36683
37401
  }
36684
37402
  //#endregion
37403
+ //#region ../blocks/src/system/runtime/nodes/event-registration/bookingCardDisplay.ts
37404
+ /**
37405
+ * The booking card the server renders on CMS pages (issue 1581), and the
37406
+ * offering noun it resolves for the wizard. Kept beside the island contract
37407
+ * so the server and the client agree on one card.
37408
+ */
37409
+ const bookingHostSchema = z.enum(["overlay", "inPlace"]);
37410
+ const tileSchema = z.object({
37411
+ month: z.string(),
37412
+ day: z.string()
37413
+ }).nullable();
37414
+ const idleBookingEntrySchema = z.discriminatedUnion("kind", [
37415
+ z.object({
37416
+ kind: z.literal("single"),
37417
+ title: z.string(),
37418
+ when: z.string().nullable(),
37419
+ shortWhen: z.string().nullable(),
37420
+ venue: z.string().nullable(),
37421
+ price: z.string().nullable(),
37422
+ places: z.string().nullable(),
37423
+ tile: tileSchema,
37424
+ action: z.string()
37425
+ }),
37426
+ z.object({
37427
+ kind: z.literal("chooseDate"),
37428
+ title: z.string(),
37429
+ schedule: z.string(),
37430
+ venue: z.string().nullable(),
37431
+ price: z.string().nullable(),
37432
+ nextDate: z.string().nullable(),
37433
+ dateCount: z.number(),
37434
+ dates: z.array(z.object({
37435
+ selectionId: z.string(),
37436
+ label: z.string(),
37437
+ places: z.string().nullable(),
37438
+ full: z.boolean()
37439
+ })),
37440
+ moreCount: z.number(),
37441
+ action: z.string()
37442
+ }),
37443
+ z.object({
37444
+ kind: z.literal("chooseEvent"),
37445
+ title: z.string(),
37446
+ count: z.number(),
37447
+ noun: z.string(),
37448
+ action: z.string()
37449
+ })
37450
+ ]);
37451
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
37452
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
37453
+ }
37454
+ function scopedRegistrationEvents(source, scopedSourceEvents) {
37455
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
37456
+ return {
37457
+ scopedEvents,
37458
+ preSelected: findPreSelectedEvent({
37459
+ scopedEvents,
37460
+ occurrenceContext: source.occurrenceContext
37461
+ })
37462
+ };
37463
+ }
37464
+ function nounFor(scoped, presets) {
37465
+ return offeringNounForEvent(scoped.preSelected ?? scoped.scopedEvents[0] ?? null, presets);
37466
+ }
37467
+ /** What the site calls the offering this block books, from its preset rows. */
37468
+ function resolveOfferingNoun(source, scopedSourceEvents) {
37469
+ return nounFor(scopedRegistrationEvents(source, scopedSourceEvents), source.bookingOfferingPresets);
37470
+ }
37471
+ function idleEntry(source, scopedSourceEvents, display) {
37472
+ const scoped = scopedRegistrationEvents(source, scopedSourceEvents);
37473
+ if (scoped.scopedEvents.length === 0) return null;
37474
+ return idleBookingEntry({
37475
+ events: scoped.scopedEvents,
37476
+ event: withOccurrenceTitle(scoped.preSelected, source.occurrenceContext),
37477
+ display,
37478
+ buttonText: source.buttonText,
37479
+ noun: nounFor(scoped, source.bookingOfferingPresets),
37480
+ attendanceMode: null
37481
+ });
37482
+ }
37483
+ /** The server's card on CMS pages, or null where the shell still applies. */
37484
+ function bookingCardDisplay(source, scopedSourceEvents, display) {
37485
+ if ((source.host ?? "overlay") !== "overlay") return null;
37486
+ const entry = idleEntry(source, scopedSourceEvents, display);
37487
+ if (!entry) return null;
37488
+ return {
37489
+ kind: "event-registration",
37490
+ state: "booking-card",
37491
+ className: source.className ?? null,
37492
+ entry,
37493
+ buttonVariant: source.buttonVariant ?? null,
37494
+ buttonText: source.buttonText?.trim() ?? ""
37495
+ };
37496
+ }
37497
+ const unsupportedEventRegistrationPageSurfaceSubjectSchema = z.object({
37498
+ kind: z.literal("unsupported"),
37499
+ reason: z.enum([
37500
+ "unsupportedSubject",
37501
+ "invalidEventSeriesId",
37502
+ "invalidEventSeriesBookingScope",
37503
+ "invalidEventOfferingId",
37504
+ "invalidEventOfferingBookingScope",
37505
+ "invalidEventOfferingCatalogue"
37506
+ ])
37507
+ });
37508
+ const eventRegistrationPageSurfaceSubjectSchema = z.unknown().transform((value, ctx) => {
37509
+ const unsupported = unsupportedEventRegistrationPageSurfaceSubjectSchema.safeParse(value);
37510
+ if (unsupported.success) return unsupported.data;
37511
+ const parsed = parseEventPageSurfaceSubject(value);
37512
+ if (parsed.ok) return parsed.value;
37513
+ ctx.addIssue({
37514
+ code: z.ZodIssueCode.custom,
37515
+ message: `Invalid event page-surface subject (${parsed.error.kind})`
37516
+ });
37517
+ return z.NEVER;
37518
+ });
37519
+ function resolveEventRegistrationScope(context) {
37520
+ const occurrenceSeriesId = context.occurrenceContext?.seriesId;
37521
+ if (occurrenceSeriesId) return {
37522
+ kind: "occurrenceSeries",
37523
+ seriesId: occurrenceSeriesId
37524
+ };
37525
+ if (context.subject?.kind === "eventSeries") return {
37526
+ kind: "pageSurfaceSeries",
37527
+ seriesId: context.subject.eventSeriesId
37528
+ };
37529
+ if (context.subject?.kind === "eventOffering") return {
37530
+ kind: "pageSurfaceOffering",
37531
+ offeringId: context.subject.eventOfferingId,
37532
+ seriesIds: context.subject.catalogueEventSeriesIds
37533
+ };
37534
+ if (context.subject?.kind === "unsupported") return {
37535
+ kind: "unsupportedPageSurfaceSubject",
37536
+ reason: context.subject.reason
37537
+ };
37538
+ return { kind: "unscoped" };
37539
+ }
37540
+ function resolveEventRegistrationScopedEvents(events, context) {
37541
+ const scope = resolveEventRegistrationScope(context);
37542
+ switch (scope.kind) {
37543
+ case "occurrenceSeries":
37544
+ case "pageSurfaceSeries": return events.filter((event) => event.seriesId === scope.seriesId);
37545
+ case "pageSurfaceOffering": {
37546
+ const seriesIds = new Set(scope.seriesIds);
37547
+ return events.filter((event) => seriesIds.has(event.seriesId));
37548
+ }
37549
+ case "unsupportedPageSurfaceSubject": return [];
37550
+ case "unscoped": return [...events];
37551
+ }
37552
+ }
37553
+ //#endregion
37554
+ //#region ../blocks/src/system/constants/events.ts
37555
+ const eventCalendarDisplayModeValues = [
37556
+ "combined",
37557
+ "calendar",
37558
+ "list",
37559
+ "timetable",
37560
+ "week-board"
37561
+ ];
37562
+ const eventCalendarDefaultViewValues = ["month", "week"];
37563
+ const eventCalendarListLayoutValues = [
37564
+ "stack",
37565
+ "grid",
37566
+ "compact"
37567
+ ];
37568
+ const arrangementOnlyListLayouts = ["ledger", "day-groups"];
37569
+ const eventListArrangementValues = [...eventCalendarListLayoutValues, ...arrangementOnlyListLayouts];
37570
+ const eventCalendarListLayoutOptions = [
37571
+ {
37572
+ id: "stack",
37573
+ label: "Stack"
37574
+ },
37575
+ {
37576
+ id: "grid",
37577
+ label: "Grid"
37578
+ },
37579
+ {
37580
+ id: "compact",
37581
+ label: "Compact"
37582
+ }
37583
+ ];
37584
+ function isEventCalendarDisplayMode(value) {
37585
+ return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
37586
+ }
37587
+ function normalizeEventCalendarDisplayMode$1(input) {
37588
+ if (isEventCalendarDisplayMode(input.variant)) return input.variant;
37589
+ return isEventCalendarDisplayMode(input.displayMode) ? input.displayMode : void 0;
37590
+ }
37591
+ /** One closed authority for the projections required by each rendered mode. */
37592
+ function resolveEventCalendarReadPlan(input) {
37593
+ const mode = normalizeEventCalendarDisplayMode$1(input) ?? "combined";
37594
+ switch (mode) {
37595
+ case "combined": return {
37596
+ mode,
37597
+ jobs: "both",
37598
+ readProfile: "listing-card"
37599
+ };
37600
+ case "list": return {
37601
+ mode,
37602
+ jobs: "list",
37603
+ readProfile: "listing-card"
37604
+ };
37605
+ case "calendar": return {
37606
+ mode,
37607
+ jobs: "calendar",
37608
+ readProfile: "listing-card"
37609
+ };
37610
+ case "timetable":
37611
+ case "week-board": return {
37612
+ mode,
37613
+ jobs: "calendar",
37614
+ readProfile: "timetable"
37615
+ };
37616
+ }
37617
+ }
37618
+ function isEventCalendarDefaultView(value) {
37619
+ return typeof value === "string" && eventCalendarDefaultViewValues.includes(value);
37620
+ }
37621
+ function isEventCalendarListLayout(value) {
37622
+ return typeof value === "string" && eventCalendarListLayoutValues.includes(value);
37623
+ }
37624
+ /** The calendar manifest owns its secondary arrangement axis. */
37625
+ const eventCalendarVariantMeaning = {
37626
+ kind: "mode",
37627
+ arrangementField: "listLayout",
37628
+ keys: eventCalendarListLayoutValues,
37629
+ lookOnlyKeys: [
37630
+ ...arrangementOnlyListLayouts,
37631
+ "month-grid",
37632
+ "month-day"
37633
+ ],
37634
+ defaultKey: "stack",
37635
+ modeLabels: {
37636
+ list: "List",
37637
+ calendar: "Month",
37638
+ combined: "List and month",
37639
+ timetable: "Timetable",
37640
+ "week-board": "Week board"
37641
+ }
37642
+ };
37643
+ /** Weekly lists share the arrangement-Look admission used by calendar arrangements. */
37644
+ function eventCalendarListGroupingDecision(input) {
37645
+ const available = input.arrangementLooks && resolveEventCalendarReadPlan(input).mode === "list";
37646
+ return {
37647
+ available,
37648
+ grouping: available && input.listBy === "week" ? "week" : "upcoming"
37649
+ };
37650
+ }
37651
+ //#endregion
37652
+ //#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
37653
+ const previewStageSchema = z.enum(["preview", "published"]);
37654
+ const registrationButtonVariantSchema = blockButtonRoleSchema;
37655
+ const eventButtonVariantSchema = blockButtonRoleSchema;
37656
+ const eventLayoutSchema = z.enum(eventCalendarListLayoutValues);
37657
+ const eventCalendarDisplayModeSchema = z.enum(eventCalendarDisplayModeValues);
37658
+ const eventCalendarDefaultViewSchema = z.enum(eventCalendarDefaultViewValues);
37659
+ const columnOptionSchema = z.enum([
37660
+ "2",
37661
+ "3",
37662
+ "4"
37663
+ ]);
37664
+ const weekStartSchema = z.enum(["sunday", "monday"]);
37665
+ const filterAutoShowSchema = z.enum([
37666
+ "auto",
37667
+ "always",
37668
+ "never"
37669
+ ]);
37670
+ const calendarTabViewsSchema = z.enum([
37671
+ "both",
37672
+ "month",
37673
+ "week"
37674
+ ]);
37675
+ //#endregion
36685
37676
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.ts
36686
37677
  const { limit: _rawLimit, eventSurface: _rawEventSurface, scheduleScope: _rawScheduleScope, detailLevel: _rawDetailLevel, ...stringEventQueryShape } = publicEventLoaderQuerySchemaShape;
36687
37678
  const eventRecoveryRequestSchema = z.object({
@@ -36724,27 +37715,17 @@ function parseEventLoaderRecoveryRequest(value) {
36724
37715
  const EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS = [
36725
37716
  "Date",
36726
37717
  "Tickets",
36727
- "Details"
36728
- ];
36729
- const EVENT_REGISTRATION_SHELL_STEP_LABELS = [
36730
- "Event",
36731
- "Date",
36732
- "Tickets",
36733
- "Details",
36734
- "Payment options",
36735
- "Payment"
37718
+ "Your details"
36736
37719
  ];
36737
37720
  const eventRegistrationPublicEventSchema = z.preprocess(normalizeEventRegistrationEventInput, publicEventSchema);
36738
- const eventRegistrationHydrationSchemaShape = {
37721
+ const eventRegistrationCommonSchemaShape = {
36739
37722
  demoEmailSuppressed: z.boolean().optional(),
36740
37723
  occurrenceContext: occurrenceContextSchema.optional(),
36741
37724
  subject: eventRegistrationPageSurfaceSubjectSchema.nullable().optional(),
36742
37725
  maxTickets: z.string().optional(),
36743
- showVenue: z.boolean().optional(),
36744
- showCapacity: z.boolean().optional(),
36745
37726
  successMessage: z.string().optional(),
36746
37727
  buttonVariant: registrationButtonVariantSchema.optional(),
36747
- overlayEnabled: z.boolean().optional(),
37728
+ host: bookingHostSchema.optional(),
36748
37729
  spamProtectionEnabled: z.boolean().optional(),
36749
37730
  supportEmail: z.string().optional(),
36750
37731
  sitePasses: publicPassProductCollectionSchema.nullable().optional(),
@@ -36769,32 +37750,51 @@ const eventRegistrationSourceSchema = z.object({
36769
37750
  portalToken: z.string().nullable().optional(),
36770
37751
  previewToken: z.string().nullable().optional(),
36771
37752
  readAuthority: browserReadAuthoritySchema.optional(),
36772
- ...eventRegistrationHydrationSchemaShape,
37753
+ ...eventRegistrationCommonSchemaShape,
37754
+ showVenue: z.boolean().optional(),
37755
+ showCapacity: z.boolean().optional(),
36773
37756
  events: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
36774
37757
  wholeSeriesRunEvents: z.array(eventRegistrationPublicEventSchema).nullable().optional(),
36775
37758
  eventLoaderFailures: z.record(z.string(), z.unknown()).nullable().optional(),
36776
37759
  className: z.string().nullable().optional(),
36777
37760
  buttonText: z.string().nullable().optional(),
36778
37761
  maxTickets: z.string().nullable().optional(),
36779
- stage: previewStageSchema.optional()
37762
+ stage: previewStageSchema.optional(),
37763
+ bookingOfferingPresets: z.array(z.custom((value) => typeof value === "object" && value !== null && typeof value.label === "string")).nullable().optional()
36780
37764
  });
36781
- const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [z.object({
36782
- kind: z.literal("event-registration"),
36783
- state: z.literal("generic-shell"),
36784
- className: z.string().nullable(),
36785
- titleText: z.string(),
36786
- bodyText: z.string(),
36787
- stepLabels: z.array(z.enum(EVENT_REGISTRATION_SHELL_STEP_LABELS)).min(1),
36788
- summaryButtonText: z.string()
36789
- }), z.object({
36790
- kind: z.literal("event-registration"),
36791
- state: z.literal("registration-closed"),
36792
- className: z.string().nullable(),
36793
- titleText: z.string(),
36794
- bodyText: z.string()
36795
- })]);
37765
+ const eventRegistrationDisplaySchema = z.discriminatedUnion("state", [
37766
+ z.object({
37767
+ kind: z.literal("event-registration"),
37768
+ state: z.literal("generic-shell"),
37769
+ className: z.string().nullable(),
37770
+ titleText: z.string(),
37771
+ bodyText: z.string(),
37772
+ stepLabels: z.array(z.enum(REGISTRATION_STEP_LABELS)).min(1),
37773
+ summaryButtonText: z.string()
37774
+ }),
37775
+ z.object({
37776
+ kind: z.literal("event-registration"),
37777
+ state: z.literal("registration-closed"),
37778
+ className: z.string().nullable(),
37779
+ titleText: z.string(),
37780
+ bodyText: z.string()
37781
+ }),
37782
+ z.object({
37783
+ kind: z.literal("event-registration"),
37784
+ state: z.literal("booking-card"),
37785
+ className: z.string().nullable(),
37786
+ entry: idleBookingEntrySchema,
37787
+ buttonVariant: registrationButtonVariantSchema.nullable(),
37788
+ buttonText: z.string()
37789
+ })
37790
+ ]);
36796
37791
  const eventRegistrationHydrationSchema = z.object({
36797
- ...eventRegistrationHydrationSchemaShape,
37792
+ ...eventRegistrationCommonSchemaShape,
37793
+ display: registrationDisplaySchema,
37794
+ offeringNoun: z.object({
37795
+ label: z.string(),
37796
+ pluralLabel: z.string()
37797
+ }).optional(),
36798
37798
  eventSource: eventRegistrationEventSourceSchema
36799
37799
  });
36800
37800
  const eventRegistrationRenderSchema = z.object({
@@ -36809,7 +37809,7 @@ z.object({
36809
37809
  readAuthority: browserReadAuthoritySchema.optional(),
36810
37810
  stage: previewStageSchema.optional()
36811
37811
  });
36812
- function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
37812
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display) {
36813
37813
  const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
36814
37814
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
36815
37815
  kind: "event-registration",
@@ -36818,6 +37818,8 @@ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvent
36818
37818
  titleText: "Registration closed",
36819
37819
  bodyText: "This run is no longer open for bookings."
36820
37820
  };
37821
+ const card = bookingCardDisplay(source, scopedSourceEvents, display);
37822
+ if (card) return card;
36821
37823
  const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
36822
37824
  return {
36823
37825
  kind: "event-registration",
@@ -36834,11 +37836,14 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
36834
37836
  if (selectedRun) {
36835
37837
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
36836
37838
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
36837
- return getWizardStepsForContext(true, resolveEventRequiresPayment({
36838
- eligibility: null,
36839
- selectedTicketType: null,
36840
- event: selectedEvent
36841
- })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
37839
+ return shellStepLabels(registrationRoute({
37840
+ chooseEvent: false,
37841
+ payment: resolveEventRequiresPayment({
37842
+ eligibility: null,
37843
+ selectedTicketType: null,
37844
+ event: selectedEvent
37845
+ }) ? "optionsAndPayment" : "none"
37846
+ }), registrationStepLabelContext([selectedEvent], selectedEvent));
36842
37847
  }
36843
37848
  const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
36844
37849
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -36847,14 +37852,15 @@ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
36847
37852
  occurrenceContext: source.occurrenceContext
36848
37853
  });
36849
37854
  const hasPreSelectedContext = Boolean(source.occurrenceContext || preSelectedEvent);
36850
- return getWizardStepsForContext(hasPreSelectedContext, hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
37855
+ const needsPaymentStep = hasPreSelectedContext && preSelectedEvent ? resolveEventRequiresPayment({
36851
37856
  eligibility: null,
36852
37857
  selectedTicketType: null,
36853
37858
  event: preSelectedEvent
36854
- }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
36855
- }
36856
- function toScopedEventRegistrationEvents(scopedSourceEvents) {
36857
- return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
37859
+ }) : false;
37860
+ return shellStepLabels(registrationRoute({
37861
+ chooseEvent: !hasPreSelectedContext,
37862
+ payment: needsPaymentStep ? "optionsAndPayment" : "none"
37863
+ }), registrationStepLabelContext(scopedEvents, preSelectedEvent));
36858
37864
  }
36859
37865
  function scopeEventRegistrationSourceEvents(source, eventSource) {
36860
37866
  return resolveEventRegistrationScopedEvents(eventSource.kind === "loaded" ? eventSource.events : [], {
@@ -36870,29 +37876,12 @@ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
36870
37876
  const runs = groupPublicEventsByRun(scopedSourceEvents);
36871
37877
  return runs.length === 1 ? runs[0] : null;
36872
37878
  }
36873
- function shellLabelForRegistrationStep(step, events) {
36874
- switch (step) {
36875
- case "select-event": return isDateSelection(events) ? "Date" : "Event";
36876
- case "select-tickets": return "Tickets";
36877
- case "enter-details": return "Details";
36878
- case "select-payment": return "Payment options";
36879
- case "payment": return "Payment";
36880
- case "error":
36881
- case "success":
36882
- case "submitting":
36883
- case "verifying-payment":
36884
- case "verifying-timeout":
36885
- case "payment-status":
36886
- case "payment-failed": return "Details";
36887
- }
36888
- }
36889
- function isDateSelection(events) {
36890
- const firstSeriesId = events[0]?.seriesId;
36891
- return Boolean(events.length > 1 && firstSeriesId && events.every((event) => event.seriesId === firstSeriesId));
37879
+ function shellStepLabels(route, context) {
37880
+ return route.map((step) => registrationStepLabel(step, context));
36892
37881
  }
36893
37882
  function buildEventRegistrationShellBodyText(stepLabels) {
36894
- if (stepLabels.includes("Payment options")) return "Preparing tickets, details, and payment options.";
36895
- if (stepLabels[0] === "Tickets") return "Preparing tickets and booking details.";
37883
+ if (stepLabels.includes("How you’ll pay")) return "Preparing tickets, details, and payment options.";
37884
+ if (stepLabels[0] === "Tickets" || stepLabels[0] === "Places") return "Preparing tickets and booking details.";
36896
37885
  return "Preparing dates, tickets, and booking details.";
36897
37886
  }
36898
37887
  const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
@@ -36902,6 +37891,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36902
37891
  render: createZodCodec(eventRegistrationRenderSchema)
36903
37892
  },
36904
37893
  buildRender: (source) => {
37894
+ const display = resolveRegistrationDisplay(source);
36905
37895
  const eventSource = resolveEventRegistrationEventSource({
36906
37896
  events: source.events,
36907
37897
  wholeSeriesRunEvents: source.wholeSeriesRunEvents,
@@ -36909,7 +37899,7 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36909
37899
  });
36910
37900
  const scopedSourceEvents = scopeEventRegistrationSourceEvents(source, eventSource);
36911
37901
  return {
36912
- display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
37902
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents, display),
36913
37903
  hydration: {
36914
37904
  occurrenceContext: source.occurrenceContext ?? null,
36915
37905
  subject: source.subject ?? null,
@@ -36919,29 +37909,29 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
36919
37909
  } : eventSource,
36920
37910
  demoEmailSuppressed: source.demoEmailSuppressed,
36921
37911
  maxTickets: source.maxTickets ?? void 0,
36922
- showVenue: source.showVenue,
36923
- showCapacity: source.showCapacity,
37912
+ display,
36924
37913
  successMessage: source.successMessage,
36925
37914
  buttonVariant: source.buttonVariant,
36926
- overlayEnabled: source.overlayEnabled,
37915
+ host: source.host ?? "overlay",
36927
37916
  spamProtectionEnabled: source.spamProtectionEnabled,
36928
37917
  supportEmail: source.supportEmail,
36929
37918
  sitePasses: source.sitePasses ?? null,
36930
37919
  siteMemberships: source.siteMemberships ?? null,
36931
37920
  mailingList: source.mailingList,
36932
- siteName: source.siteName
37921
+ siteName: source.siteName,
37922
+ offeringNoun: resolveOfferingNoun(source, scopedSourceEvents)
36933
37923
  }
36934
37924
  };
36935
37925
  }
36936
37926
  });
36937
37927
  function normalizeEventRegistrationEventInput(value) {
36938
- if (!isRecord$3(value)) return value;
37928
+ if (!isRecord$2(value)) return value;
36939
37929
  return {
36940
37930
  ...value,
36941
37931
  destination: resolveRuntimeEventDestination(value)
36942
37932
  };
36943
37933
  }
36944
- function isRecord$3(value) {
37934
+ function isRecord$2(value) {
36945
37935
  return typeof value === "object" && value !== null && !Array.isArray(value);
36946
37936
  }
36947
37937
  const eventRegistrationBlockDefinition = {
@@ -36981,7 +37971,7 @@ const eventRegistrationBlockDefinition = {
36981
37971
  description: "Main heading for the registration form",
36982
37972
  required: false,
36983
37973
  multiline: false,
36984
- defaultValue: "Register for this event"
37974
+ defaultValue: ""
36985
37975
  },
36986
37976
  {
36987
37977
  id: "description",
@@ -37053,11 +38043,11 @@ const eventRegistrationBlockDefinition = {
37053
38043
  {
37054
38044
  id: "buttonText",
37055
38045
  type: "text",
37056
- label: "Submit button text",
37057
- description: "Text for the registration submit button",
38046
+ label: "Button text",
38047
+ description: "Text on the booking button",
37058
38048
  required: false,
37059
38049
  multiline: false,
37060
- defaultValue: "Complete Registration",
38050
+ defaultValue: "Book your place",
37061
38051
  maxLength: 40
37062
38052
  },
37063
38053
  createButtonVariantField()
@@ -37089,21 +38079,21 @@ const eventRegistrationBlockDefinition = {
37089
38079
  successMessage: { $bind: { from: "content.successMessage" } },
37090
38080
  buttonText: { $bind: {
37091
38081
  from: "content.buttonText",
37092
- fallback: "Complete Registration"
38082
+ fallback: "Book your place"
37093
38083
  } },
37094
38084
  buttonVariant: { $bind: {
37095
38085
  from: "content.buttonVariant",
37096
38086
  fallback: "primary"
37097
38087
  } },
37098
- overlayEnabled: { $bind: {
37099
- from: "$root.eventRegistrationOverlayEnabled",
37100
- fallback: false
37101
- } },
37102
38088
  events: { $bind: { from: "data.events" } },
37103
38089
  wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
37104
38090
  loaderFailures: { $bind: { from: "$root.loaderFailures" } },
37105
38091
  mailingList: { $bind: { from: "$root.mailingList" } },
37106
38092
  siteName: { $bind: { from: "$root.siteName" } },
38093
+ bookingOfferingPresets: { $bind: {
38094
+ from: "$root.bookingOfferingPresets",
38095
+ fallback: []
38096
+ } },
37107
38097
  sitePasses: { $bind: { from: "data.sitePasses" } },
37108
38098
  siteMemberships: { $bind: { from: "data.siteMemberships" } },
37109
38099
  stage: { $bind: {
@@ -39764,7 +40754,21 @@ const eventCalendarBlockDefinition = {
39764
40754
  list: { label: "List" }
39765
40755
  }
39766
40756
  }, eventCalendarContentShape),
39767
- normalizeContent: normalizeEventCalendarContent,
40757
+ contentNormalizer: {
40758
+ fieldIds: [
40759
+ "variant",
40760
+ "displayMode",
40761
+ "listLayout",
40762
+ "defaultCalendarView",
40763
+ "eventSurface",
40764
+ "filterEventPresets",
40765
+ "scheduleScope",
40766
+ "filterCategoryIds",
40767
+ "filterStaffMemberId",
40768
+ "filterVenueId"
40769
+ ],
40770
+ normalize: normalizeEventCalendarContent
40771
+ },
39768
40772
  dataSchemas: {
39769
40773
  events: publicEventsArraySchema.optional(),
39770
40774
  calendarWindow: calendarWindowPageSchema.optional()
@@ -40013,90 +41017,6 @@ const recurringScheduleBlockDefinition = {
40013
41017
  } }
40014
41018
  };
40015
41019
  //#endregion
40016
- //#region ../blocks/src/system/blocks/offerings-preview/model.ts
40017
- const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
40018
- const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
40019
- const OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS = [
40020
- {
40021
- preset: "class",
40022
- sourceKey: "eventPreset:class",
40023
- dataKey: "classEvents",
40024
- label: "Classes"
40025
- },
40026
- {
40027
- preset: "workshop",
40028
- sourceKey: "eventPreset:workshop",
40029
- dataKey: "workshopEvents",
40030
- label: "Workshops"
40031
- },
40032
- {
40033
- preset: "retreat",
40034
- sourceKey: "eventPreset:retreat",
40035
- dataKey: "retreatEvents",
40036
- label: "Retreats"
40037
- },
40038
- {
40039
- preset: "performance",
40040
- sourceKey: "eventPreset:performance",
40041
- dataKey: "performanceEvents",
40042
- label: "Performances"
40043
- }
40044
- ];
40045
- OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.preset);
40046
- const OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS = [...OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => definition.sourceKey), "course"];
40047
- const OFFERINGS_PREVIEW_SOURCE_KEYS = OFFERINGS_PREVIEW_AUTHORABLE_SOURCE_KEYS;
40048
- Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.sourceKey, {
40049
- kind: "eventPreset",
40050
- preset: definition.preset
40051
- }]));
40052
- function isOfferingsPreviewSourceKey(value) {
40053
- return typeof value === "string" && OFFERINGS_PREVIEW_SOURCE_KEYS.includes(value);
40054
- }
40055
- function normalizeOfferingsPreviewGroupOrder(value) {
40056
- if (value == null) return { kind: "legacy" };
40057
- if (value.length === 0) return { kind: "default" };
40058
- const keys = uniqueOfferingsPreviewSourceKeys(value);
40059
- return keys.length > 0 ? {
40060
- kind: "custom",
40061
- keys
40062
- } : { kind: "default" };
40063
- }
40064
- function inferLegacyOfferingsPreviewGroupOrder(overrides) {
40065
- const keys = uniqueOfferingsPreviewSourceKeys((overrides ?? []).map((override) => override.sourceKey));
40066
- return keys.length >= 2 ? keys : [];
40067
- }
40068
- function normalizeOfferingsPreviewContentForWrite(value) {
40069
- if (!isRecord$2(value)) return null;
40070
- const groupOrderValue = value.groupOrder;
40071
- if (Array.isArray(groupOrderValue)) {
40072
- const normalized = normalizeOfferingsPreviewGroupOrder(groupOrderValue);
40073
- return {
40074
- ...value,
40075
- groupOrder: normalized.kind === "custom" ? normalized.keys : []
40076
- };
40077
- }
40078
- if (groupOrderValue != null) return {
40079
- ...value,
40080
- groupOrder: []
40081
- };
40082
- const legacyOrder = inferLegacyOfferingsPreviewGroupOrder(Array.isArray(value.groupOverrides) ? value.groupOverrides.filter(isRecord$2) : []);
40083
- return legacyOrder.length > 0 ? {
40084
- ...value,
40085
- groupOrder: legacyOrder
40086
- } : value;
40087
- }
40088
- function uniqueOfferingsPreviewSourceKeys(values) {
40089
- const keys = [];
40090
- for (const value of values) {
40091
- if (!isOfferingsPreviewSourceKey(value)) continue;
40092
- if (!keys.includes(value)) keys.push(value);
40093
- }
40094
- return keys;
40095
- }
40096
- function isRecord$2(value) {
40097
- return typeof value === "object" && value !== null && !Array.isArray(value);
40098
- }
40099
- //#endregion
40100
41020
  //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
40101
41021
  const groupOverrideFields = [
40102
41022
  {
@@ -40460,7 +41380,22 @@ const offeringsPreviewEventDataSchemas = Object.fromEntries(OFFERINGS_PREVIEW_EV
40460
41380
  const offeringsPreviewEventDataLoaders = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, eventPresetLoader(definition.preset)]));
40461
41381
  const offeringsPreviewBlockDefinition = {
40462
41382
  manifest: offeringsPreviewManifest,
40463
- normalizeContent: normalizeOfferingsPreviewBlockContent,
41383
+ contentNormalizer: {
41384
+ fieldIds: [
41385
+ "displayMode",
41386
+ "heading",
41387
+ "description",
41388
+ "itemLimit",
41389
+ "groupOrder",
41390
+ "groupOverrides",
41391
+ "_cardStyles",
41392
+ "buttonVariant",
41393
+ "buttonText",
41394
+ "emptyMessage",
41395
+ "_sectionStyles"
41396
+ ],
41397
+ normalize: normalizeOfferingsPreviewBlockContent
41398
+ },
40464
41399
  dataSchemas: {
40465
41400
  ...offeringsPreviewEventDataSchemas,
40466
41401
  routeCandidates: publicRouteCandidatesSchema.optional(),
@@ -44058,11 +44993,11 @@ function buildNewsletterDisplay(source) {
44058
44993
  successMessage: DEFAULT_MAILING_LIST_SUCCESS_MESSAGE,
44059
44994
  privacyNote: null
44060
44995
  };
44061
- const targeting = source.targeting ?? { mode: "default" };
44062
- const choices = resolveRuntimeChoices(context, targeting);
44996
+ const assessment = assessMailingListTargeting(context, source.targeting ?? { mode: "default" });
44997
+ const choices = resolveRuntimeChoices(assessment);
44063
44998
  return {
44064
44999
  kind: "newsletter-form",
44065
- availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
45000
+ availability: assessment.tag === "available" ? "ready" : "unavailable",
44066
45001
  className: source.className ?? null,
44067
45002
  buttonLabel: form.buttonLabel,
44068
45003
  successMessage: form.successMessage,
@@ -44119,60 +45054,12 @@ function projectNewsletterFormHydrationTarget(source) {
44119
45054
  default: return assertNever$2(source);
44120
45055
  }
44121
45056
  }
44122
- function resolveRuntimeChoices(context, targeting) {
44123
- if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
44124
- switch (targeting.provider) {
44125
- case "native": {
44126
- const offered = new Set(targeting.selection.offeredTopicIds);
44127
- return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
44128
- id: item.id,
44129
- label: item.name
44130
- })) : [];
44131
- }
44132
- case "brevo": {
44133
- const offered = new Set(targeting.selection.offeredListIds);
44134
- return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
44135
- id: String(item.id),
44136
- label: item.name
44137
- })) : [];
44138
- }
44139
- case "mailchimp": {
44140
- const offered = new Set(targeting.selection.offeredInterestIds);
44141
- return (context.provider.provider === "mailchimp" ? context.provider.audiences.find((item) => item.id === targeting.audienceId) : void 0)?.interests.filter((item) => offered.has(item.id)).map((item) => ({
44142
- id: item.id,
44143
- label: item.name
44144
- })) ?? [];
44145
- }
44146
- }
44147
- }
44148
- function runtimeTargetingIsAvailable(context, targeting, choices) {
44149
- if (context?.tag !== "configured") return false;
44150
- const provider = context.provider;
44151
- if (targeting.mode === "default") switch (provider.provider) {
44152
- case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
44153
- case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
44154
- case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
44155
- }
44156
- if (targeting.provider !== provider.provider) return false;
44157
- switch (targeting.provider) {
44158
- case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
44159
- case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
44160
- case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
44161
- }
44162
- }
44163
- function nativeTargetIsAvailable(provider, targeting, choices) {
44164
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
44165
- return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
44166
- }
44167
- function brevoTargetIsAvailable(provider, targeting, choices) {
44168
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
44169
- return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
44170
- }
44171
- function mailchimpTargetIsAvailable(provider, targeting, choices) {
44172
- const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
44173
- if (!audience) return false;
44174
- const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
44175
- return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
45057
+ function resolveRuntimeChoices(assessment) {
45058
+ if (!("value" in assessment) || assessment.value.mode !== "custom" || !("selectedChoices" in assessment.value) || assessment.value.targeting.selection.tag !== "visitor_choice") return [];
45059
+ return assessment.value.selectedChoices.map((item) => ({
45060
+ id: String(item.id),
45061
+ label: item.name
45062
+ }));
44176
45063
  }
44177
45064
  function parseRequiredMailingListSignupBlockId(value) {
44178
45065
  const parsed = parseMailingListBlockId(value);
@@ -44276,6 +45163,10 @@ const newsletterSignupManifest = createBlockManifest({
44276
45163
  [...(newsletterSignupManifest.fields ?? []).map((field) => field.id)];
44277
45164
  const newsletterSignupBlockDefinition = {
44278
45165
  manifest: newsletterSignupManifest,
45166
+ contentNormalizer: {
45167
+ fieldIds: ["targeting"],
45168
+ normalize: normalizeMailingListBlockContent
45169
+ },
44279
45170
  runtime: newsletterFormBlockRuntime
44280
45171
  };
44281
45172
  //#endregion
@@ -45008,7 +45899,20 @@ const locationMapBlockDefinition = {
45008
45899
  })], { gap: "md" })
45009
45900
  })
45010
45901
  }),
45011
- normalizeContent: normalizeLocationMapContent
45902
+ contentNormalizer: {
45903
+ fieldIds: [
45904
+ "heading",
45905
+ "locationSource",
45906
+ "venueId",
45907
+ "location",
45908
+ "showAddressText",
45909
+ "showDirections",
45910
+ "directionsLabel",
45911
+ "zoom",
45912
+ "heightPx"
45913
+ ],
45914
+ normalize: normalizeLocationMapContent
45915
+ }
45012
45916
  };
45013
45917
  //#endregion
45014
45918
  //#region ../blocks/src/system/blocks/index.ts
@@ -46213,17 +47117,7 @@ const linksTransforms = [{
46213
47117
  id: "links.resolve",
46214
47118
  kind: "formatter",
46215
47119
  summary: "Resolve LinkValue objects to href strings using the route map",
46216
- run: (value, _options, context) => {
46217
- if (!value || typeof value !== "object") return null;
46218
- const link = value;
46219
- const kind = typeof link.kind === "string" ? link.kind : null;
46220
- if (kind === "internal") return resolveInternalLink(link, context);
46221
- if (kind === "page") return resolvePageLink(link, context);
46222
- if (kind === "entry") return resolveEntryLink(link, context);
46223
- if (kind === "external" || kind === "url") return resolveExternalLink(link);
46224
- if (!kind && typeof link.href === "string") return resolveExternalLink(link);
46225
- return null;
46226
- }
47120
+ run: (value, _options, context) => resolveAuthoredLinkHref(value, context)
46227
47121
  }, {
46228
47122
  id: "links.label",
46229
47123
  kind: "formatter",
@@ -46250,6 +47144,21 @@ const linksTransforms = [{
46250
47144
  return null;
46251
47145
  }
46252
47146
  }];
47147
+ function resolveAuthoredLinkHref(value, context = {}) {
47148
+ if (!value || typeof value !== "object") return null;
47149
+ const link = value;
47150
+ switch (link.kind) {
47151
+ case "internal": return resolveInternalLink(link, context);
47152
+ case "page": return resolvePageLink(link, context);
47153
+ case "entry": return resolveEntryLink(link, context);
47154
+ case "external":
47155
+ case "url": return resolveExternalLink(link);
47156
+ default: return resolvePreResolvedLink(link);
47157
+ }
47158
+ }
47159
+ function resolvePreResolvedLink(link) {
47160
+ return !(typeof link.kind === "string" ? link.kind : null) && typeof link.href === "string" ? resolveExternalLink(link) : null;
47161
+ }
46253
47162
  const eventCalendarSelectionParam = {
46254
47163
  id: "events.calendarSelectionParam",
46255
47164
  kind: "formatter",