@riverbankcms/sdk 0.140.0 → 0.142.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 (145) hide show
  1. package/dist/{PreviewEditorSidebar-CUyTzd5z.mjs → PreviewEditorSidebar-Dk6Dt11f.mjs} +24 -15
  2. package/dist/{PreviewEditorUI-Agkw4v5i.mjs → PreviewEditorUI-D6EQTsCA.mjs} +3 -3
  3. package/dist/{PreviewQueryProvider-CN0ewic5.mjs → PreviewQueryProvider-CC-EPkyQ.mjs} +1 -1
  4. package/dist/{SdkPreviewModeRuntime-DdbeAdS8.mjs → SdkPreviewModeRuntime-DH1n4ytY.mjs} +85 -54
  5. package/dist/{SiteChromeCustomizeContext-CqGOz-Kv.mjs → SiteChromeCustomizeContext-DunIzY0K.mjs} +797 -216
  6. package/dist/_dts/api/src/billing.d.ts +6 -1
  7. package/dist/_dts/api/src/bookingConfig.d.ts +1 -1
  8. package/dist/_dts/api/src/commercePortalEndpoints.d.ts +2 -2
  9. package/dist/_dts/api/src/contentRuntime.d.ts +12 -31
  10. package/dist/_dts/api/src/domainConnection.d.ts +4 -0
  11. package/dist/_dts/api/src/endpoints.d.ts +0 -27
  12. package/dist/_dts/api/src/footerCompositionContracts.d.ts +25 -0
  13. package/dist/_dts/api/src/index.d.ts +2 -2
  14. package/dist/_dts/api/src/launchReadiness.d.ts +1 -0
  15. package/dist/_dts/api/src/navigation/dashboardSiteNav.d.ts +2 -1
  16. package/dist/_dts/api/src/publicBookingService.d.ts +1 -0
  17. package/dist/_dts/api/src/sdkConfig/layout.d.ts +13 -0
  18. package/dist/_dts/api/src/sdkContracts.d.ts +3 -8
  19. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +3 -26
  20. package/dist/_dts/api/src/siteOnboarding.d.ts +7 -0
  21. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +1 -31
  22. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +6 -1
  23. package/dist/_dts/billing/src/site-arrangements.d.ts +18 -5
  24. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +4 -0
  25. package/dist/_dts/blocks/src/pageRendererContracts.d.ts +2 -0
  26. package/dist/_dts/blocks/src/system/blocks/events/event-calendar.d.ts +2 -0
  27. package/dist/_dts/blocks/src/system/blocks/events/eventSelectionFields.d.ts +122 -0
  28. package/dist/_dts/blocks/src/system/blocks/events/shared/calendarWindow.d.ts +2 -1
  29. package/dist/_dts/blocks/src/system/blocks/events/shared/eventSelection.d.ts +11 -0
  30. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +56 -0
  31. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +8 -0
  32. package/dist/_dts/blocks/src/system/blocks/location-map.d.ts +2 -2
  33. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +47 -47
  34. package/dist/_dts/blocks/src/system/data/prefetchBlockData.d.ts +5 -0
  35. package/dist/_dts/blocks/src/system/data/utils.d.ts +1 -1
  36. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +27 -1
  37. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +12 -180
  38. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +6 -90
  39. package/dist/_dts/blocks/src/system/runtime/hooks/useEventFilters.d.ts +2 -1
  40. package/dist/_dts/blocks/src/system/runtime/nodes/appointment-services.d.ts +5 -1
  41. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +4 -0
  42. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.state.d.ts +5 -0
  43. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useDiscountCodeUi.d.ts +3 -0
  44. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useDiscountCodeUi.state.d.ts +10 -3
  45. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventCheckoutHandoffRestore.d.ts +9 -0
  46. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +4 -5
  47. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useSettledCommerceView.d.ts +37 -0
  48. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.client.d.ts +4 -3
  49. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.interactive.d.ts +3 -0
  50. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventListing.interactive.d.ts +11 -101
  51. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +63 -2
  52. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/listOptions.d.ts +2 -0
  53. package/dist/_dts/blocks/src/system/transforms/registry/eventCalendar.d.ts +12 -0
  54. package/dist/_dts/commerce/src/purchase/bookingInstalmentSchedule.d.ts +4 -0
  55. package/dist/_dts/commerce/src/purchase/stripeCommissionMetadata.d.ts +1 -2
  56. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +3102 -3122
  57. package/dist/_dts/editor-blocks/src/block-editor/ColourFieldOptionsProvider.d.ts +7 -0
  58. package/dist/_dts/editor-blocks/src/block-editor/colourFieldVisibility.d.ts +7 -0
  59. package/dist/_dts/editor-blocks/src/block-editor/preparedManifest.d.ts +2 -0
  60. package/dist/_dts/editor-blocks/src/index.d.ts +1 -0
  61. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/loaders.d.ts +27 -1
  62. package/dist/_dts/preview-next/src/client/preview/PreviewEditorSidebar.d.ts +2 -2
  63. package/dist/_dts/preview-next/src/client/preview/PreviewFooterEditorPanel.d.ts +3 -6
  64. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +2 -2
  65. package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +2 -2
  66. package/dist/_dts/preview-next/src/client/preview/footerInspection.d.ts +16 -0
  67. package/dist/_dts/preview-next/src/client/preview/sdkConfig.d.ts +6 -0
  68. package/dist/_dts/sdk/src/config/types.d.ts +2 -18
  69. package/dist/_dts/sdk/src/config/validation.d.ts +6 -0
  70. package/dist/_dts/sdk/src/public-api/contracts.d.ts +4 -1
  71. package/dist/_dts/sdk/src/version.d.ts +1 -1
  72. package/dist/_dts/theme-core/src/ThemeFontLoader.d.ts +17 -1
  73. package/dist/_dts/theme-core/src/buttons/affordanceSuppression.d.ts +17 -0
  74. package/dist/_dts/theme-core/src/buttons/buttonColourContract.d.ts +1 -1
  75. package/dist/_dts/theme-core/src/buttons/classNames.d.ts +16 -0
  76. package/dist/_dts/theme-core/src/buttons/generateButtonCss.d.ts +0 -6
  77. package/dist/_dts/theme-core/src/buttons/resolvedButtonCss.d.ts +4 -4
  78. package/dist/_dts/theme-core/src/footer/mode.d.ts +10 -1
  79. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  80. package/dist/_dts/theme-core/src/hosted-booking-appearance.d.ts +54 -12
  81. package/dist/_dts/theme-core/src/index.d.ts +1 -1
  82. package/dist/_dts/theme-core/src/schema.d.ts +1 -7
  83. package/dist/_dts/theme-core/src/site-styles/effectivePageDesign.d.ts +32 -0
  84. package/dist/_dts/theme-core/src/site-styles/index.d.ts +1 -0
  85. package/dist/client/{BookingConsentAgreement-CuKa82V4.mjs → BookingConsentAgreement-CnJeq3_r.mjs} +2 -2
  86. package/dist/client/{DiscountCodeDisclosure-BbYieKWB.mjs → DiscountCodeDisclosure-BINEO6J9.mjs} +1 -1
  87. package/dist/client/{EventCombined.client-LTfR1Aby.mjs → EventCombined.client-srxNQ6sZ.mjs} +11 -15
  88. package/dist/client/{EventListing.client-DUHpYSuo.mjs → EventListing.client-DbyVyGkX.mjs} +4 -17
  89. package/dist/client/{LeafletStaticMap-CHRupL6m.mjs → LeafletStaticMap-B-wpijzL.mjs} +1 -1
  90. package/dist/client/{accordion-6HiIFxNL.mjs → accordion-B-iUXU7n.mjs} +1 -1
  91. package/dist/client/{bookingForm-Dlmid43H.mjs → bookingForm-BXYPke5q.mjs} +5 -5
  92. package/dist/client/bookings.mjs +194 -102
  93. package/dist/client/{carousel-DmkZRRq_.mjs → carousel-Cr4aj63H.mjs} +1 -1
  94. package/dist/client/{cart-CIC24330.mjs → cart-DibP9WWa.mjs} +3 -3
  95. package/dist/client/{checkout-D1Zw9imo.mjs → checkout-CrSBVnL8.mjs} +3 -3
  96. package/dist/client/client.mjs +643 -192
  97. package/dist/client/{context-Cy1OayHx.mjs → context-Ces-AgdR.mjs} +1 -1
  98. package/dist/client/{dates-aDcfoLHr.mjs → dates-BXyNeNhB.mjs} +2 -2
  99. package/dist/client/{eventCalendar-FQxMPAjf.mjs → eventCalendar-HwpPeqrH.mjs} +11 -7
  100. package/dist/client/{eventCombined-Dr3ac6Hb.mjs → eventCombined-BpFUkuEI.mjs} +3 -3
  101. package/dist/client/{eventDetails-D-AzUHFQ.mjs → eventDetails-DK_agiWy.mjs} +4 -4
  102. package/dist/client/{eventListing-B3XxBf3i.mjs → eventListing-DhzHqP1c.mjs} +3 -3
  103. package/dist/client/{eventRegistration-I6EaokFD.mjs → eventRegistration-BdQ_Dn2d.mjs} +124 -32
  104. package/dist/client/{form-BbWckVWY.mjs → form-B1frcuzy.mjs} +2 -2
  105. package/dist/client/{headerSection-C9qU4Y7x.mjs → headerSection-CT5z460s.mjs} +2 -2
  106. package/dist/client/hooks.mjs +274 -45
  107. package/dist/client/{locationMap-CLCShvlG.mjs → locationMap-CIQXB9i2.mjs} +2 -2
  108. package/dist/client/{newsletterForm-Briukxzi.mjs → newsletterForm-BYNYZSes.mjs} +23 -10
  109. package/dist/client/{pricing-Copadu-2.mjs → pricing-CbTlAsmt.mjs} +1 -1
  110. package/dist/client/{productDetail-oujlRd-r.mjs → productDetail-CR7Z3yjS.mjs} +3 -3
  111. package/dist/client/{productList-BRiIRUcd.mjs → productList-DXj00vzR.mjs} +3 -3
  112. package/dist/client/{propsDecoder-CB2KS_ko.mjs → propsDecoder-DiLiBdek.mjs} +57 -16
  113. package/dist/client/rendering/client.mjs +476 -132
  114. package/dist/client/rendering/islands.mjs +17 -17
  115. package/dist/client/rendering.mjs +686 -195
  116. package/dist/client/{scrollToBlock-B1IS1_ua.mjs → scrollToBlock-CrcbBf0h.mjs} +28 -11
  117. package/dist/client/{shop-Vn6yNFED.mjs → shop-DgqiB_1b.mjs} +6 -6
  118. package/dist/client/{shop-commerce-BVTqipYp.mjs → shop-commerce-D_J781in.mjs} +5 -5
  119. package/dist/client/{shop-commerce-store-BVnJVy7v.mjs → shop-commerce-store-PGUbDwqe.mjs} +1 -1
  120. package/dist/client/{shopShared-COIeK77T.mjs → shopShared-C_Bb2zwG.mjs} +3 -3
  121. package/dist/client/{useEventFilters-DzIUcS9n.mjs → useEventFilters-CM17Xpr1.mjs} +17 -11
  122. package/dist/preview-next/before-render.mjs +0 -37
  123. package/dist/preview-next/client/runtime.mjs +4 -4
  124. package/dist/preview-next/editor-chrome.css +4 -8
  125. package/dist/preview-next/keepAlive.mjs +0 -37
  126. package/dist/preview-next/middleware.mjs +0 -37
  127. package/dist/{previewAuthStore-Gjb6r6vz.mjs → previewAuthStore-oVttoRDO.mjs} +0 -37
  128. package/dist/{sdk-runtime-CnCLvZRv.mjs → sdk-runtime-DhmuRhIX.mjs} +2 -2
  129. package/dist/{separator-DKDRJ1oH.mjs → separator-C3hlg9LH.mjs} +1 -1
  130. package/dist/server/components.mjs +452 -111
  131. package/dist/server/config-validation.mjs +263 -40
  132. package/dist/server/config.mjs +263 -40
  133. package/dist/server/data.mjs +274 -45
  134. package/dist/server/index.mjs +1 -38
  135. package/dist/server/next.mjs +427 -119
  136. package/dist/server/page-converter.mjs +208 -33
  137. package/dist/server/prebuild.mjs +1 -1
  138. package/dist/server/rendering/server.mjs +467 -119
  139. package/dist/server/rendering.mjs +467 -117
  140. package/dist/server/routing.mjs +301 -50
  141. package/dist/server/server.mjs +275 -83
  142. package/dist/server/theme-bridge.mjs +57 -23
  143. package/dist/server/theme.mjs +11 -9
  144. package/dist/styles/index.css +28 -2
  145. package/package.json +1 -1
@@ -2461,6 +2461,7 @@ function isDisplayMediaKind$2(kind) {
2461
2461
  }
2462
2462
  const siteOptionSources = [
2463
2463
  "eventCategories",
2464
+ "eventSeries",
2464
2465
  "venues",
2465
2466
  "staffMembers"
2466
2467
  ];
@@ -7186,14 +7187,12 @@ function generateVariantCSS(variant, themeId, cardClasses, tokens, theme) {
7186
7187
  const shadowValue = generateShadowValue$1(shadow);
7187
7188
  rules.push(`box-shadow: ${shadowValue}`);
7188
7189
  }
7189
- rules.push(`display: flex`);
7190
- rules.push(`flex-direction: column`);
7191
- rules.push(`position: relative`);
7192
- rules.push(`overflow: visible`);
7190
+ const structureSelector = cardClasses.map((cardClass) => `:where([data-theme-scope="${themeId}"]) :where(.${cardClass})`).join(",\n");
7193
7191
  const mediaRadius = MEDIA_CORNER_MAP[corners];
7194
7192
  const mediaRules = [`border-top-left-radius: ${mediaRadius}`, `border-top-right-radius: ${mediaRadius}`];
7195
7193
  const contentRules = [`padding: ${resolveContentPadding(variant)}`];
7196
7194
  return [
7195
+ `${structureSelector} {\n display: flex;\n flex-direction: column;\n position: relative;\n overflow: visible;\n}`,
7197
7196
  `${containerSelector} {\n ${rules.join(";\n ")};\n}`,
7198
7197
  ...cardDecorationPseudoRules(containerSelectors, variant.decorations, resolveColor),
7199
7198
  `${fallbackBackgroundSelector} {\n background: transparent;\n}`,
@@ -12663,6 +12662,54 @@ function defaultTypographyForRecipe(recipe) {
12663
12662
  case "classic-serif": return classicSerifTypography;
12664
12663
  }
12665
12664
  }
12665
+ //#endregion
12666
+ //#region ../theme-core/src/footer/mode.ts
12667
+ const footerModeSchema = z$1.enum([
12668
+ "default",
12669
+ "blocks",
12670
+ "default+blocks",
12671
+ "none"
12672
+ ]);
12673
+ /** Reads only SDK footer intent; unrelated config and theme fields are not authorities. */
12674
+ function resolveSdkFooterMode(config) {
12675
+ const parsed = z$1.object({ layout: z$1.object({ footerMode: footerModeSchema.optional() }).optional() }).safeParse(config);
12676
+ return parsed.success ? parsed.data.layout?.footerMode ?? "default" : "default";
12677
+ }
12678
+ /**
12679
+ * Resolve what to render for the footer given the current mode and whether the
12680
+ * site is SDK-managed.
12681
+ *
12682
+ * Backwards compatible: missing/undefined mode is treated as 'default'.
12683
+ */
12684
+ function resolveFooterRenderPlan(params) {
12685
+ if (!params.isSdkSite) return {
12686
+ mode: "default",
12687
+ renderDefaultFooter: true,
12688
+ renderFooterBlocks: false
12689
+ };
12690
+ switch (params.mode ?? "default") {
12691
+ case "blocks": return {
12692
+ mode: "blocks",
12693
+ renderDefaultFooter: false,
12694
+ renderFooterBlocks: true
12695
+ };
12696
+ case "default+blocks": return {
12697
+ mode: "default+blocks",
12698
+ renderDefaultFooter: true,
12699
+ renderFooterBlocks: true
12700
+ };
12701
+ case "none": return {
12702
+ mode: "none",
12703
+ renderDefaultFooter: false,
12704
+ renderFooterBlocks: false
12705
+ };
12706
+ default: return {
12707
+ mode: "default",
12708
+ renderDefaultFooter: true,
12709
+ renderFooterBlocks: false
12710
+ };
12711
+ }
12712
+ }
12666
12713
  const hotspotSchema = z$1.object({
12667
12714
  x: z$1.number().min(0).max(1),
12668
12715
  y: z$1.number().min(0).max(1),
@@ -13224,7 +13271,7 @@ const BUTTON_CONTRAST_POLICY = {
13224
13271
  nonTextMinimumRatio: 3,
13225
13272
  gradientSamplesPerSegment: 20,
13226
13273
  alphaBackgrounds: "reject-without-known-surface",
13227
- disabled: "retain-full-colour-contract"
13274
+ disabled: "validated-colour-pair-with-muted-control-opacity"
13228
13275
  };
13229
13276
  function formatButtonColourContractError(error) {
13230
13277
  switch (error.tag) {
@@ -13812,6 +13859,25 @@ function parseThemeForPersistence(value) {
13812
13859
  };
13813
13860
  }
13814
13861
  //#endregion
13862
+ //#region ../theme-core/src/buttons/affordanceSuppression.ts
13863
+ /**
13864
+ * Internal: the opt-out the generated affordance rule is written against.
13865
+ *
13866
+ * A button style may declare a trailing `affordance`, which the theme renders as
13867
+ * a CSS `::after` on every button of that variant. An authored button that
13868
+ * renders its own trailing icon must not also receive the theme's, or it shows
13869
+ * two.
13870
+ *
13871
+ * Deliberately not exported from `./index`. This used to be public, and two
13872
+ * block components reached past the theme to draw arrows of their own — one of
13873
+ * them doubling up on the styles that declare an affordance, both of them
13874
+ * overriding the twelve styles that deliberately declare none. Whether a button
13875
+ * carries a trailing affordance is the style's decision; the only caller that
13876
+ * legitimately opts out is the authored-button renderer, which reaches it
13877
+ * through `themeButtonClassNameWithTrailingIcon`.
13878
+ */
13879
+ const AFFORDANCE_SUPPRESS_CLASS = "rb-affordance-off";
13880
+ //#endregion
13815
13881
  //#region ../theme-core/src/buttons/classNames.ts
13816
13882
  /**
13817
13883
  * Parse content/runtime variant identifiers into the closed theme-button class
@@ -13844,6 +13910,27 @@ function themeButtonClassName(spec) {
13844
13910
  spec.extraClassName
13845
13911
  ].filter(Boolean).join(" ");
13846
13912
  }
13913
+ /**
13914
+ * Button classes for an authored button that renders its own trailing icon.
13915
+ *
13916
+ * A separate function rather than a flag on `themeButtonClassName`, because
13917
+ * there is exactly one caller and it should stay visible in a diff. The name
13918
+ * states what the caller knows to be true of its own markup — that it renders a
13919
+ * trailing icon — rather than what the theme does about it. The consequence is
13920
+ * the theme's to decide and to change; a caller naming the consequence would be
13921
+ * asserting a policy it does not own, and would quietly become a lie the moment
13922
+ * that policy moved.
13923
+ *
13924
+ * Do not reach for this to remove an arrow you simply do not want. A trailing
13925
+ * affordance is declared by the button style, and a block overriding that is the
13926
+ * defect this replaced.
13927
+ */
13928
+ function themeButtonClassNameWithTrailingIcon(spec) {
13929
+ return themeButtonClassName({
13930
+ ...spec,
13931
+ extraClassName: [spec.extraClassName, AFFORDANCE_SUPPRESS_CLASS].filter(Boolean).join(" ")
13932
+ });
13933
+ }
13847
13934
  //#endregion
13848
13935
  //#region ../theme-core/src/interactive/generateBaseCSS.ts
13849
13936
  /**
@@ -15756,26 +15843,19 @@ function resolvedButtonFocusDeclarations(indicator, role) {
15756
15843
  ] : [["outline", value("outline", `3px solid ${indicator.colorCss}`)], ["outline-offset", value("outline-offset", "3px")]];
15757
15844
  }
15758
15845
  /**
15759
- * Disabled buttons retain their validated colour pair and add a non-layout
15760
- * visual affordance. `currentColor` keeps the cue inside the same contrast
15761
- * relationship, while retaining pointer events lets the not-allowed cursor
15762
- * remain observable.
15846
+ * Disabled buttons are deliberately muted as unavailable controls. Native
15847
+ * disabled controls are exempt from active-control contrast requirements;
15848
+ * opacity distinguishes the state without introducing a focus-like outline.
15849
+ * Retaining pointer events lets the not-allowed cursor remain observable.
15763
15850
  */
15764
15851
  const RESOLVED_BUTTON_DISABLED_DECLARATIONS = [
15765
15852
  ["cursor", "not-allowed"],
15766
15853
  ["transform", "none"],
15767
15854
  ["box-shadow", "none"],
15768
- ["outline", "2px dashed currentColor"],
15769
- ["outline-offset", "-4px"]
15855
+ ["opacity", "0.55"]
15770
15856
  ];
15771
15857
  //#endregion
15772
15858
  //#region ../theme-core/src/buttons/generateButtonCss.ts
15773
- /**
15774
- * Class the `button` runtime node adds when it renders its own trailing icon,
15775
- * so a node-level icon suppresses the theme's CSS `::after` affordance.
15776
- * Shared so generator and renderer can't drift.
15777
- */
15778
- const AFFORDANCE_SUPPRESS_CLASS = "rb-affordance-off";
15779
15859
  const BUTTON_SIZE_NAMES = buttonSizeNameSchema.options;
15780
15860
  /**
15781
15861
  * Letter spacing token → CSS value.
@@ -17748,12 +17828,6 @@ const footerVariant = z$1.enum([
17748
17828
  "split"
17749
17829
  ]);
17750
17830
  const footerMaxWidth = z$1.enum(["container", "full"]);
17751
- const footerMode = z$1.enum([
17752
- "default",
17753
- "blocks",
17754
- "default+blocks",
17755
- "none"
17756
- ]);
17757
17831
  const footerNavLayoutMode = z$1.enum([
17758
17832
  "stack",
17759
17833
  "inline",
@@ -17842,9 +17916,9 @@ const footerSchema = z$1.object({
17842
17916
  /**
17843
17917
  * SDK-only footer rendering mode.
17844
17918
  *
17845
- * Backwards compatible: missing/undefined behaves as 'default'.
17919
+ * Historical persisted field; SDK rendering mode now lives in layout.footerMode.
17846
17920
  */
17847
- mode: footerMode.optional(),
17921
+ mode: footerModeSchema.optional(),
17848
17922
  variant: footerVariant,
17849
17923
  maxWidth: footerMaxWidth,
17850
17924
  showLogoText: z$1.boolean().optional(),
@@ -28078,6 +28152,15 @@ const publicEventBaseSchema = z$1.object({
28078
28152
  timeZone: z$1.string(),
28079
28153
  pricing: publicEventPricingSchema,
28080
28154
  paymentTerms: blockBookingPaymentTermsSchema,
28155
+ cancellationPolicy: z$1.object({ refunds: z$1.object({
28156
+ mode: z$1.enum([
28157
+ "never",
28158
+ "always",
28159
+ "cutoff"
28160
+ ]),
28161
+ cutoffHours: z$1.number(),
28162
+ method: z$1.enum(["original_payment_method", "site_credit"])
28163
+ }) }),
28081
28164
  consent: blockBookingConsentSchema.nullable(),
28082
28165
  accessRestriction: accessRestrictionSchema,
28083
28166
  allowedCategoryIds: z$1.array(z$1.string()),
@@ -28208,6 +28291,7 @@ const calendarWindowQuerySchema = z$1.object({
28208
28291
  eventSurface: z$1.enum(eventSurfaceScopeValues).optional(),
28209
28292
  eventPresets: z$1.string().optional(),
28210
28293
  scheduleScope: z$1.enum(eventScheduleScopeValues).optional(),
28294
+ seriesIds: z$1.string().optional(),
28211
28295
  categoryIds: z$1.string().optional(),
28212
28296
  venueId: z$1.string().transform(normalizeCalendarFilterId).optional(),
28213
28297
  staffMemberId: z$1.string().transform(normalizeCalendarFilterId).optional()
@@ -28258,7 +28342,56 @@ const calendarWindowPageSchema = z$1.discriminatedUnion("kind", [z$1.object({
28258
28342
  kind: z$1.literal("partial"),
28259
28343
  nextCursor: calendarSourceCursorSchema
28260
28344
  })]);
28345
+ //#endregion
28346
+ //#region ../blocks/src/system/blocks/events/shared/eventSelection.ts
28347
+ /** Invalid saved selections fail closed rather than widening discovery. */
28348
+ function resolveEventSelection(mode, value) {
28349
+ if (mode !== "selected") return { kind: "matching" };
28350
+ const candidates = Array.isArray(value) ? value : typeof value === "string" ? value.split(",").filter(Boolean) : [];
28351
+ const ids = [];
28352
+ for (const candidate of candidates) {
28353
+ if (typeof candidate !== "string") return {
28354
+ kind: "selected",
28355
+ seriesIds: []
28356
+ };
28357
+ const parsed = parseEventSeriesId(candidate.trim());
28358
+ if (!parsed.ok) return {
28359
+ kind: "selected",
28360
+ seriesIds: []
28361
+ };
28362
+ ids.push(parsed.value);
28363
+ }
28364
+ return {
28365
+ kind: "selected",
28366
+ seriesIds: [...new Set(ids)].sort()
28367
+ };
28368
+ }
28369
+ /** Undefined means discovery; an empty string is an explicitly empty selection. */
28370
+ function eventSeriesScopeCsv(value) {
28371
+ if (value == null) return void 0;
28372
+ return typeof value === "string" ? value : value.join(",");
28373
+ }
28261
28374
  const defaultTransforms = createTransformRegistry([
28375
+ {
28376
+ id: "events.calendarSelectionParam",
28377
+ kind: "formatter",
28378
+ summary: "Resolve selected events instead of discovery filters",
28379
+ schema: z$1.object({ key: z$1.enum([
28380
+ "seriesIds",
28381
+ "eventSurface",
28382
+ "filterEventPresets",
28383
+ "scheduleScope",
28384
+ "filterCategoryIds",
28385
+ "filterStaffMemberId",
28386
+ "filterVenueId"
28387
+ ]) }),
28388
+ run(value, { key }) {
28389
+ const content = asPartialObject(value) ?? {};
28390
+ const selection = resolveEventSelection(content.eventSelection, content.selectedEventSeriesIds);
28391
+ if (selection.kind === "selected") return key === "seriesIds" ? selection.seriesIds : void 0;
28392
+ return key === "seriesIds" ? void 0 : content[key];
28393
+ }
28394
+ },
28262
28395
  {
28263
28396
  id: "events.calendarReadEnabled",
28264
28397
  kind: "formatter",
@@ -30012,17 +30145,16 @@ const ButtonNode = ({ value, children, href, variantId = "primary", size = "md",
30012
30145
  if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
30013
30146
  return null;
30014
30147
  }
30015
- const extraClassName = affordanceIcon ? [className, AFFORDANCE_SUPPRESS_CLASS].filter(Boolean).join(" ") : className;
30016
30148
  const resolvedVariantId = parseThemeButtonVariantId(variantId);
30017
30149
  if (variantId && !resolvedVariantId) {
30018
30150
  console.error("[ButtonNode] Invalid button variant", { variantId });
30019
30151
  return null;
30020
30152
  }
30021
- const combinedClassName = resolvedVariantId ? themeButtonClassName({
30153
+ const combinedClassName = resolvedVariantId ? (affordanceIcon ? themeButtonClassNameWithTrailingIcon : themeButtonClassName)({
30022
30154
  variant: resolvedVariantId,
30023
30155
  size,
30024
- extraClassName
30025
- }) : extraClassName;
30156
+ extraClassName: className
30157
+ }) : className;
30026
30158
  if (resolvedHref) return /* @__PURE__ */ jsxs("a", {
30027
30159
  href: resolvedHref,
30028
30160
  target,
@@ -31294,6 +31426,7 @@ const publicPricingSchema = z$1.object({
31294
31426
  });
31295
31427
  const publicAppointmentServiceSchema = z$1.object({
31296
31428
  id: z$1.string().min(1),
31429
+ slug: z$1.string().min(1).optional(),
31297
31430
  title: z$1.string(),
31298
31431
  description: z$1.string().nullable().optional(),
31299
31432
  durationMinutes: z$1.number().int().positive(),
@@ -31370,7 +31503,7 @@ function formatAmountOrFree(amount, currency) {
31370
31503
  currency
31371
31504
  }, { decimals: "auto" });
31372
31505
  }
31373
- function buildRows(services, pathname) {
31506
+ function buildRows(services, pathname, servicePathPrefix) {
31374
31507
  return services.map((service) => ({
31375
31508
  id: service.id,
31376
31509
  title: service.title,
@@ -31381,7 +31514,8 @@ function buildRows(services, pathname) {
31381
31514
  href: resolveDedicatedBookingHref(pathname, {
31382
31515
  kind: "appointment_service",
31383
31516
  serviceId: service.id
31384
- })
31517
+ }),
31518
+ detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
31385
31519
  }));
31386
31520
  }
31387
31521
  function buildAppointmentServicesView(input) {
@@ -31400,13 +31534,14 @@ function buildAppointmentServicesView(input) {
31400
31534
  };
31401
31535
  return {
31402
31536
  kind: "items",
31403
- rows: buildRows(parsed.services, input.pathname)
31537
+ rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
31404
31538
  };
31405
31539
  }
31406
- function AppointmentServicesNode({ sectionHeading, services, pathname, className }) {
31540
+ function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
31407
31541
  const view = buildAppointmentServicesView({
31408
31542
  services,
31409
- pathname: typeof pathname === "string" ? pathname : void 0
31543
+ pathname: typeof pathname === "string" ? pathname : void 0,
31544
+ servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
31410
31545
  });
31411
31546
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
31412
31547
  const RowTitle = sectionHeading ? "h3" : "h2";
@@ -31437,7 +31572,10 @@ function AppointmentServicesNode({ sectionHeading, services, pathname, className
31437
31572
  children: [
31438
31573
  /* @__PURE__ */ jsx(RowTitle, {
31439
31574
  className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
31440
- children: row.title
31575
+ children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
31576
+ href: row.detailsHref,
31577
+ children: row.title
31578
+ }) : row.title
31441
31579
  }),
31442
31580
  row.description ? /* @__PURE__ */ jsx("p", {
31443
31581
  className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
@@ -33944,12 +34082,12 @@ const EventCompactRow = ({ event, buttonVariant = "primary", buttonText }) => {
33944
34082
  }), cta.hidden ? /* @__PURE__ */ jsx("span", {
33945
34083
  className: "event-compact-row-status",
33946
34084
  children: cta.label
33947
- }) : /* @__PURE__ */ jsxs("a", {
34085
+ }) : /* @__PURE__ */ jsx("a", {
33948
34086
  href: cta.href,
33949
34087
  target: cta.external ? "_blank" : void 0,
33950
34088
  rel: cta.external ? "noopener noreferrer" : void 0,
33951
34089
  className: `${buttonClass} event-compact-row-cta`,
33952
- children: [/* @__PURE__ */ jsx("span", { children: cta.label }), /* @__PURE__ */ jsx(ArrowRightIcon, { "aria-hidden": "true" })]
34090
+ children: /* @__PURE__ */ jsx("span", { children: cta.label })
33953
34091
  })]
33954
34092
  })]
33955
34093
  })]
@@ -35042,6 +35180,7 @@ const eventCollectionFilterSourceSchemaShape = {
35042
35180
  eventSurface: eventSurfaceScopeSchema$1.nullable().optional(),
35043
35181
  filterEventPresets: z$1.union([z$1.string(), z$1.array(z$1.string())]).nullable().optional(),
35044
35182
  scheduleScope: eventScheduleScopeSchema$1.nullable().optional(),
35183
+ seriesIds: z$1.union([z$1.string(), z$1.array(z$1.string())]).nullable().optional(),
35045
35184
  filterCategoryIds: z$1.union([z$1.string(), z$1.array(z$1.string())]).nullable().optional(),
35046
35185
  filterStaffMemberId: z$1.string().nullable().optional(),
35047
35186
  filterVenueId: z$1.string().nullable().optional()
@@ -35088,6 +35227,7 @@ const eventCollectionDisplaySchemaShape = {
35088
35227
  eventSurface: eventSurfaceScopeSchema$1,
35089
35228
  filterEventPresets: z$1.string().nullable(),
35090
35229
  scheduleScope: eventScheduleScopeSchema$1,
35230
+ seriesIds: z$1.string().nullable().optional(),
35091
35231
  filterCategoryIds: z$1.string().nullable(),
35092
35232
  filterStaffMemberId: z$1.string().nullable(),
35093
35233
  filterVenueId: z$1.string().nullable(),
@@ -35135,11 +35275,15 @@ function buildEventCollectionDisplay(source) {
35135
35275
  };
35136
35276
  }
35137
35277
  function buildEventCollectionFilterDisplay(source) {
35138
- return {
35139
- showFilters: source.showFilters ?? true,
35140
- showCategoryFilter: source.showCategoryFilter ?? "auto",
35141
- showVenueFilter: source.showVenueFilter ?? false,
35142
- showStaffFilter: source.showStaffFilter ?? true,
35278
+ const selected = source.seriesIds != null;
35279
+ const matchingFilters = selected ? {
35280
+ eventSurface: "all",
35281
+ filterEventPresets: null,
35282
+ scheduleScope: "any",
35283
+ filterCategoryIds: null,
35284
+ filterStaffMemberId: null,
35285
+ filterVenueId: null
35286
+ } : {
35143
35287
  eventSurface: normalizeEventSurfaceScope(source.eventSurface),
35144
35288
  filterEventPresets: normalizeEventSurfacePresetsCsv(source.filterEventPresets),
35145
35289
  scheduleScope: normalizeEventScheduleScope(source.scheduleScope),
@@ -35147,6 +35291,14 @@ function buildEventCollectionFilterDisplay(source) {
35147
35291
  filterStaffMemberId: normalizeEventStaffMemberId(source.filterStaffMemberId),
35148
35292
  filterVenueId: normalizeEventVenueId(source.filterVenueId)
35149
35293
  };
35294
+ return {
35295
+ seriesIds: eventSeriesScopeCsv(source.seriesIds),
35296
+ showFilters: selected ? false : source.showFilters ?? true,
35297
+ showCategoryFilter: source.showCategoryFilter ?? "auto",
35298
+ showVenueFilter: source.showVenueFilter ?? false,
35299
+ showStaffFilter: source.showStaffFilter ?? true,
35300
+ ...matchingFilters
35301
+ };
35150
35302
  }
35151
35303
  function buildEventCollectionPresentationDisplay(source) {
35152
35304
  return {
@@ -35161,14 +35313,15 @@ function buildEventCollectionPresentationDisplay(source) {
35161
35313
  }
35162
35314
  function buildEventCollectionHydration(source, now = /* @__PURE__ */ new Date()) {
35163
35315
  const timeZone = source.calendarWindow?.scope.timeZone ?? toBookingTimeZone(source.bookingTimeZone);
35316
+ const selected = source.seriesIds != null;
35164
35317
  return {
35165
35318
  calendarWindow: source.calendarWindow ?? void 0,
35166
35319
  siteId: source.siteId,
35167
35320
  stage: source.stage,
35168
35321
  prefetchedEvents: source.events != null,
35169
- eventSurface: normalizeEventSurfaceScope(source.eventSurface),
35170
- filterEventPresets: normalizeEventSurfacePresetsCsv(source.filterEventPresets),
35171
- scheduleScope: normalizeEventScheduleScope(source.scheduleScope),
35322
+ eventSurface: selected ? "all" : normalizeEventSurfaceScope(source.eventSurface),
35323
+ filterEventPresets: selected ? null : normalizeEventSurfacePresetsCsv(source.filterEventPresets),
35324
+ scheduleScope: selected ? "any" : normalizeEventScheduleScope(source.scheduleScope),
35172
35325
  calendarContext: {
35173
35326
  timeZone,
35174
35327
  initialToday: source.calendarWindow?.initialToday ?? getLocalScheduleDate(now, timeZone)
@@ -35738,13 +35891,10 @@ function OfferingsPreviewContent(props) {
35738
35891
  }),
35739
35892
  groupCta ? /* @__PURE__ */ jsx("footer", {
35740
35893
  className: "offerings-preview-card-footer rb-mt-auto rb-flex",
35741
- children: /* @__PURE__ */ jsxs("a", {
35894
+ children: /* @__PURE__ */ jsx("a", {
35742
35895
  className: groupLinkClass,
35743
35896
  href: groupCta.href,
35744
- children: [/* @__PURE__ */ jsx("span", { children: groupCta.label }), /* @__PURE__ */ jsx(ArrowRightIcon, {
35745
- "aria-hidden": "true",
35746
- className: "offerings-preview-link-icon rb-h-4 rb-w-4"
35747
- })]
35897
+ children: /* @__PURE__ */ jsx("span", { children: groupCta.label })
35748
35898
  })
35749
35899
  }) : null
35750
35900
  ]
@@ -38689,6 +38839,65 @@ const formBlockDefinition = {
38689
38839
  runtime: formBlockRuntime
38690
38840
  };
38691
38841
  //#endregion
38842
+ //#region ../blocks/src/system/blocks/events/eventSelectionFields.ts
38843
+ const eventSelectionFields = [{
38844
+ id: "eventSelection",
38845
+ type: "select",
38846
+ label: "Events to show",
38847
+ required: false,
38848
+ multiple: false,
38849
+ defaultValue: "matching",
38850
+ options: [{
38851
+ value: "matching",
38852
+ label: "All matching events"
38853
+ }, {
38854
+ value: "selected",
38855
+ label: "Selected events"
38856
+ }],
38857
+ ui: { intent: "behavior" }
38858
+ }, createSiteOptionSelectField({
38859
+ id: "selectedEventSeriesIds",
38860
+ label: "Selected events",
38861
+ description: "Show upcoming dates for these events. Other event filters do not apply.",
38862
+ required: false,
38863
+ multiple: true,
38864
+ defaultValue: [],
38865
+ optionSource: "eventSeries",
38866
+ ui: {
38867
+ intent: "behavior",
38868
+ searchThreshold: 1,
38869
+ visibleWhen: {
38870
+ field: "eventSelection",
38871
+ equals: "selected"
38872
+ }
38873
+ }
38874
+ })];
38875
+ function withEventSelectionVisibility(field) {
38876
+ if (![
38877
+ "eventSurface",
38878
+ "filterEventPresets",
38879
+ "scheduleScope",
38880
+ "filterCategoryIds",
38881
+ "filterStaffMemberId",
38882
+ "filterVenueId",
38883
+ "showFilters",
38884
+ "showCategoryFilter",
38885
+ "showVenueFilter",
38886
+ "showStaffFilter"
38887
+ ].includes(field.id)) return field;
38888
+ const visibleWhen = field.ui?.visibleWhen;
38889
+ return {
38890
+ ...field,
38891
+ ui: {
38892
+ ...field.ui,
38893
+ visibleWhen: { allOf: [{
38894
+ field: "eventSelection",
38895
+ notEquals: "selected"
38896
+ }, ...visibleWhen ? [visibleWhen] : []] }
38897
+ }
38898
+ };
38899
+ }
38900
+ //#endregion
38692
38901
  //#region ../blocks/src/system/runtime/nodes/events/EventCalendar.interactive.ts
38693
38902
  const eventCalendarSourceSchema = z$1.object({
38694
38903
  ...eventCollectionSourceSchemaShape,
@@ -38786,19 +38995,59 @@ function buildEventCalendarInteractiveIslandProps(props) {
38786
38995
  //#region ../blocks/src/system/blocks/events/eventCalendarDataLoaders.ts
38787
38996
  const scopeParams = {
38788
38997
  siteId: { $bind: { from: "$root.siteId" } },
38998
+ seriesIds: { $bind: {
38999
+ from: "content",
39000
+ transforms: [{
39001
+ id: "events.calendarSelectionParam",
39002
+ options: { key: "seriesIds" }
39003
+ }]
39004
+ } },
38789
39005
  stage: { $bind: {
38790
39006
  from: "$root.previewStage",
38791
39007
  fallback: "published"
38792
39008
  } },
38793
- eventSurface: { $bind: { from: "content.eventSurface" } },
39009
+ eventSurface: { $bind: {
39010
+ from: "content",
39011
+ transforms: [{
39012
+ id: "events.calendarSelectionParam",
39013
+ options: { key: "eventSurface" }
39014
+ }, { id: "value.csv" }]
39015
+ } },
38794
39016
  eventPresets: { $bind: {
38795
- from: "content.filterEventPresets",
38796
- transforms: [{ id: "value.csv" }]
39017
+ from: "content",
39018
+ transforms: [{
39019
+ id: "events.calendarSelectionParam",
39020
+ options: { key: "filterEventPresets" }
39021
+ }, { id: "value.csv" }]
39022
+ } },
39023
+ scheduleScope: { $bind: {
39024
+ from: "content",
39025
+ transforms: [{
39026
+ id: "events.calendarSelectionParam",
39027
+ options: { key: "scheduleScope" }
39028
+ }, { id: "value.csv" }]
39029
+ } },
39030
+ categoryIds: { $bind: {
39031
+ from: "content",
39032
+ transforms: [{
39033
+ id: "events.calendarSelectionParam",
39034
+ options: { key: "filterCategoryIds" }
39035
+ }, { id: "value.csv" }]
39036
+ } },
39037
+ staffMemberId: { $bind: {
39038
+ from: "content",
39039
+ transforms: [{
39040
+ id: "events.calendarSelectionParam",
39041
+ options: { key: "filterStaffMemberId" }
39042
+ }, { id: "value.csv" }]
38797
39043
  } },
38798
- scheduleScope: { $bind: { from: "content.scheduleScope" } },
38799
- categoryIds: { $bind: { from: "content.filterCategoryIds" } },
38800
- staffMemberId: { $bind: { from: "content.filterStaffMemberId" } },
38801
- venueId: { $bind: { from: "content.filterVenueId" } }
39044
+ venueId: { $bind: {
39045
+ from: "content",
39046
+ transforms: [{
39047
+ id: "events.calendarSelectionParam",
39048
+ options: { key: "filterVenueId" }
39049
+ }, { id: "value.csv" }]
39050
+ } }
38802
39051
  };
38803
39052
  /** Combined renders two jobs; calendar-only variants never pay for the list seed. */
38804
39053
  const eventCalendarDataLoaders = {
@@ -38928,6 +39177,13 @@ const eventCalendarLayout = [styledSection({
38928
39177
  from: "content.defaultCalendarView",
38929
39178
  fallback: "month"
38930
39179
  } },
39180
+ seriesIds: { $bind: {
39181
+ from: "content",
39182
+ transforms: [{
39183
+ id: "events.calendarSelectionParam",
39184
+ options: { key: "seriesIds" }
39185
+ }]
39186
+ } },
38931
39187
  showFilters: { $bind: {
38932
39188
  from: "content.showFilters",
38933
39189
  fallback: true
@@ -38949,17 +39205,47 @@ const eventCalendarLayout = [styledSection({
38949
39205
  fallback: true
38950
39206
  } },
38951
39207
  eventSurface: { $bind: {
38952
- from: "content.eventSurface",
38953
- fallback: "all"
39208
+ from: "content",
39209
+ transforms: [{
39210
+ id: "events.calendarSelectionParam",
39211
+ options: { key: "eventSurface" }
39212
+ }]
39213
+ } },
39214
+ filterEventPresets: { $bind: {
39215
+ from: "content",
39216
+ transforms: [{
39217
+ id: "events.calendarSelectionParam",
39218
+ options: { key: "filterEventPresets" }
39219
+ }]
38954
39220
  } },
38955
- filterEventPresets: { $bind: { from: "content.filterEventPresets" } },
38956
39221
  scheduleScope: { $bind: {
38957
- from: "content.scheduleScope",
38958
- fallback: "any"
39222
+ from: "content",
39223
+ transforms: [{
39224
+ id: "events.calendarSelectionParam",
39225
+ options: { key: "scheduleScope" }
39226
+ }]
39227
+ } },
39228
+ filterCategoryIds: { $bind: {
39229
+ from: "content",
39230
+ transforms: [{
39231
+ id: "events.calendarSelectionParam",
39232
+ options: { key: "filterCategoryIds" }
39233
+ }]
39234
+ } },
39235
+ filterStaffMemberId: { $bind: {
39236
+ from: "content",
39237
+ transforms: [{
39238
+ id: "events.calendarSelectionParam",
39239
+ options: { key: "filterStaffMemberId" }
39240
+ }]
39241
+ } },
39242
+ filterVenueId: { $bind: {
39243
+ from: "content",
39244
+ transforms: [{
39245
+ id: "events.calendarSelectionParam",
39246
+ options: { key: "filterVenueId" }
39247
+ }]
38959
39248
  } },
38960
- filterCategoryIds: { $bind: { from: "content.filterCategoryIds" } },
38961
- filterStaffMemberId: { $bind: { from: "content.filterStaffMemberId" } },
38962
- filterVenueId: { $bind: { from: "content.filterVenueId" } },
38963
39249
  cardRole: { $bind: {
38964
39250
  from: "content._cardStyles.role",
38965
39251
  fallback: "default"
@@ -39463,6 +39749,7 @@ const eventCalendarAdditionalFields = [
39463
39749
  }
39464
39750
  }
39465
39751
  },
39752
+ ...eventSelectionFields,
39466
39753
  {
39467
39754
  id: "eventSurface",
39468
39755
  type: "select",
@@ -39672,7 +39959,7 @@ const eventCalendarBlockDefinition = {
39672
39959
  titleSource: "heading",
39673
39960
  description: "Display events with calendar, list, or combined views with filtering",
39674
39961
  skipSectionStyles: true,
39675
- additionalFields: eventCalendarAdditionalFields,
39962
+ additionalFields: eventCalendarAdditionalFields.map(withEventSelectionVisibility),
39676
39963
  styleTokens: {
39677
39964
  background: "surface",
39678
39965
  typography: "body",
@@ -39781,6 +40068,7 @@ const eventListingHydrationSchema = z$1.object({
39781
40068
  eventSurface: eventSurfaceScopeSchema,
39782
40069
  filterEventPresets: z$1.string().nullable(),
39783
40070
  scheduleScope: eventScheduleScopeSchema,
40071
+ seriesIds: z$1.string().nullable().optional(),
39784
40072
  filterCategoryIds: z$1.string().nullable().optional(),
39785
40073
  filterStaffMemberId: z$1.string().nullable().optional(),
39786
40074
  filterVenueId: z$1.string().nullable().optional(),
@@ -39841,6 +40129,7 @@ const eventListingBlockRuntime = defineProgressiveBlockRuntime({
39841
40129
  eventSurface: normalizeEventSurfaceScope(source.eventSurface),
39842
40130
  filterEventPresets: normalizeEventSurfacePresetsCsv(source.filterEventPresets),
39843
40131
  scheduleScope: normalizeEventScheduleScope(source.scheduleScope),
40132
+ seriesIds: eventSeriesScopeCsv(source.seriesIds),
39844
40133
  filterCategoryIds: normalizeEventCategoryIdsCsv(source.filterCategoryIds),
39845
40134
  filterStaffMemberId: normalizeEventStaffMemberId(source.filterStaffMemberId),
39846
40135
  filterVenueId: normalizeEventVenueId(source.filterVenueId),
@@ -45198,9 +45487,56 @@ function generateNavLinkCSS(options) {
45198
45487
  if (baseRules.length > 0) cssChunks.unshift(`\n${selector} {\n ${baseRules.join("\n ")}\n}\n`);
45199
45488
  if (hoverRules.length > 0) cssChunks.push(`\n${hoverSelector} {\n ${hoverRules.join("\n ")}\n}\n`);
45200
45489
  if (activeRules.length > 0) cssChunks.push(`\n${activeSelector} {\n ${activeRules.join("\n ")}\n}\n`);
45490
+ cssChunks.push(navLinkFocusVisibleCss(selector, style, theme, tokens));
45201
45491
  cssChunks.push(generateInteractiveChildInheritanceCSS(selector));
45202
45492
  return cssChunks.join("");
45203
45493
  }
45494
+ /**
45495
+ * Keyboard focus indicator for nav links, in the header, the footer and header
45496
+ * dropdown menus — the three surfaces `generateNavLinkCSS` serves.
45497
+ *
45498
+ * Emitted unconditionally rather than as an effect, because focus visibility is
45499
+ * a floor rather than a style choice. Only the interaction-palette pill ever
45500
+ * wrote an `effects.focus` group, so every other nav — every `quiet` palette,
45501
+ * every footer, every dropdown — previously fell back to whatever outline the
45502
+ * browser drew.
45503
+ *
45504
+ * The outline takes the link's own resolved colour, so on a header that adapts
45505
+ * to the section it floats over it moves with the adaptation instead of
45506
+ * asserting a brand colour against an unknown backdrop.
45507
+ *
45508
+ * Where the style asks for `soft-glow`, the same halo goes on the outline. That
45509
+ * treatment exists because the theme has already conceded the resolved colour
45510
+ * alone is not reliable over an unpredictable backdrop — a hero photograph with
45511
+ * mixed luminance. Without it the text would stay legible by its halo while the
45512
+ * focus ring, which is the part with a contrast requirement of its own, would
45513
+ * not.
45514
+ *
45515
+ * A style that also paints a focus pill keeps it: this adds an outline around
45516
+ * that pill rather than replacing it.
45517
+ */
45518
+ function navLinkFocusVisibleCss(selector, style, theme, tokens) {
45519
+ const color = tokens.getColor(style.colorToken);
45520
+ const halo = buildTextContrastShadow({
45521
+ theme,
45522
+ colorToken: style.colorToken,
45523
+ treatment: style.contrastTreatment
45524
+ });
45525
+ return `
45526
+ ${selector}:focus-visible {
45527
+ outline: 2px solid ${color};
45528
+ outline-offset: ${focusOutlineOffset(selector)};${halo ? `\n box-shadow: ${halo};` : ""}
45529
+ }
45530
+ `;
45531
+ }
45532
+ /**
45533
+ * Dropdown items are full-width inside a panel with no horizontal padding, so
45534
+ * an outset outline would paint over and past the panel's own border. Keeping
45535
+ * it flush is the only way it reads as belonging to the item.
45536
+ */
45537
+ function focusOutlineOffset(selector) {
45538
+ return selector.includes("nav-dropdown-link") ? "-2px" : "3px";
45539
+ }
45204
45540
  function buildTextContrastShadow(input) {
45205
45541
  if (input.treatment !== "soft-glow") return null;
45206
45542
  return isLikelyLightColorToken(input.theme, input.colorToken) ? "0 1px 3px rgb(0 0 0 / 0.55), 0 0 18px rgb(0 0 0 / 0.38)" : "0 1px 3px rgb(255 255 255 / 0.72), 0 0 18px rgb(255 255 255 / 0.5)";
@@ -48333,7 +48669,7 @@ function pageBlockPosition(index, blockCount) {
48333
48669
  //#endregion
48334
48670
  //#region ../blocks/src/pageRendererContracts.ts
48335
48671
  function buildPageRendererRootRuntime(dataContext) {
48336
- const { siteId = null, pageId = null, pathname = null, bookingTimeZone, previewStage = "published", apiBaseUrl = null, portalToken = null, bookingOfferingPresets = [], mailingList = { tag: "not_configured" }, eventRegistrationOverlayEnabled = false, loaderFailures = {} } = dataContext ?? {};
48672
+ const { siteId = null, pageId = null, pathname = null, bookingTimeZone, previewStage = "published", apiBaseUrl = null, portalToken = null, bookingOfferingPresets = [], appointmentServicePathPrefix = null, mailingList = { tag: "not_configured" }, eventRegistrationOverlayEnabled = false, loaderFailures = {} } = dataContext ?? {};
48337
48673
  return {
48338
48674
  siteId,
48339
48675
  pageId,
@@ -48343,6 +48679,7 @@ function buildPageRendererRootRuntime(dataContext) {
48343
48679
  apiBaseUrl,
48344
48680
  portalToken,
48345
48681
  bookingOfferingPresets,
48682
+ appointmentServicePathPrefix,
48346
48683
  mailingList,
48347
48684
  eventRegistrationOverlayEnabled,
48348
48685
  loaderFailures
@@ -53085,7 +53422,8 @@ const appointmentServicesBlockDefinition = {
53085
53422
  el("appointment-services", {
53086
53423
  sectionHeading: { $bind: { from: "content.heading" } },
53087
53424
  services: { $bind: { from: "data.services" } },
53088
- pathname: { $bind: { from: "$root.pathname" } }
53425
+ pathname: { $bind: { from: "$root.pathname" } },
53426
+ servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
53089
53427
  })
53090
53428
  ], { gap: "lg" })
53091
53429
  })]
@@ -59779,10 +60117,19 @@ const containerResponsiveThemeCss = `/*
59779
60117
  padding-top: 2.5rem;
59780
60118
  padding-bottom: 2.5rem;
59781
60119
  }
60120
+ /* Clearance for a surface that is not an authored block page.
60121
+ An authored page gets this through its opening block's container (see the
60122
+ overlay-clearance rule in styles.css). A reserved surface or a system
60123
+ destination page has no such block, so it consumes the same variable here.
60124
+ Marked with one class rather than per-surface selectors, because the last
60125
+ two additions to this family were both missed and shipped overlapping. */
59782
60126
  :where(.theme-scope)
59783
60127
  [data-site-document-layout-mode="header-overlay"]
59784
- [data-rb-reserved-surface].rb-reserved-surface-main {
59785
- padding-top: calc(var(--rb-reserved-surface-header-offset, 5.5rem) + 2.5rem);
60128
+ .rb-overlay-clearance-surface {
60129
+ padding-top: calc(
60130
+ var(--site-overlay-header-clearance, 5.5rem) +
60131
+ var(--site-overlay-opening-content-extra-pad, clamp(1rem, 2vw, 1.75rem))
60132
+ );
59786
60133
  }
59787
60134
  .rb-preview-editor-offset {
59788
60135
  padding-bottom: calc(
@@ -70139,6 +70486,23 @@ const containerResponsiveThemeCss = `/*
70139
70486
  gap: 0.5rem;
70140
70487
  }
70141
70488
 
70489
+ /* Error panel and field notes. The form block scopes these to its own
70490
+ data-block, so the newsletter form (which also renders in the footer)
70491
+ needs its own copy of the treatment. */
70492
+ :where(.theme-scope) .newsletter-form .fb-error:not([data-field-validation-message]) {
70493
+ background-color: var(--fb-error-bg);
70494
+ color: var(--fb-error-text);
70495
+ border: 1px solid var(--fb-error-border);
70496
+ border-radius: var(--rb-block-radius);
70497
+ padding: 1rem;
70498
+ font-size: 0.875rem;
70499
+ }
70500
+
70501
+ :where(.theme-scope) .newsletter-form [data-field-validation-message] {
70502
+ font-size: 0.875rem;
70503
+ color: var(--fb-error-text);
70504
+ }
70505
+
70142
70506
  /* Required indicator */
70143
70507
  :where(.theme-scope) .newsletter-required {
70144
70508
  color: rgb(var(--tb-primary));
@@ -72405,41 +72769,6 @@ function ThemeScope$1({ theme, children, themeId = "theme-default", responsiveMo
72405
72769
  children
72406
72770
  });
72407
72771
  }
72408
- /**
72409
- * Resolve what to render for the footer given the current mode and whether the
72410
- * site is SDK-managed.
72411
- *
72412
- * Backwards compatible: missing/undefined mode is treated as 'default'.
72413
- */
72414
- function resolveFooterRenderPlan(params) {
72415
- if (!params.isSdkSite) return {
72416
- mode: "default",
72417
- renderDefaultFooter: true,
72418
- renderFooterBlocks: false
72419
- };
72420
- switch (params.mode ?? "default") {
72421
- case "blocks": return {
72422
- mode: "blocks",
72423
- renderDefaultFooter: false,
72424
- renderFooterBlocks: true
72425
- };
72426
- case "default+blocks": return {
72427
- mode: "default+blocks",
72428
- renderDefaultFooter: true,
72429
- renderFooterBlocks: true
72430
- };
72431
- case "none": return {
72432
- mode: "none",
72433
- renderDefaultFooter: false,
72434
- renderFooterBlocks: false
72435
- };
72436
- default: return {
72437
- mode: "default",
72438
- renderDefaultFooter: true,
72439
- renderFooterBlocks: false
72440
- };
72441
- }
72442
- }
72443
72772
  //#endregion
72444
72773
  //#region ../theme-core/src/ResponsiveScope.tsx
72445
72774
  /**
@@ -72507,6 +72836,15 @@ z$1.object({
72507
72836
  }).optional(),
72508
72837
  overrides: z$1.record(z$1.string(), z$1.string()).optional()
72509
72838
  }).passthrough();
72839
+ z$1.object({
72840
+ palette: paletteColorsSchema,
72841
+ paletteMode: paletteModeSchema,
72842
+ typography: z$1.object({
72843
+ headingFamily: z$1.string().trim().min(1),
72844
+ bodyFamily: z$1.string().trim().min(1)
72845
+ }).strict(),
72846
+ corners
72847
+ }).strict();
72510
72848
  //#endregion
72511
72849
  //#region src/rendering/blocks-theme-runtime.ts
72512
72850
  function parseBlocksTheme(theme) {
@@ -74461,12 +74799,12 @@ function normalizeContent(block) {
74461
74799
  }
74462
74800
  /**
74463
74801
  * Normalize params for API calls.
74464
- * Converts all values to strings.
74802
+ * Converts supplied values to strings, preserving absent versus explicitly empty params.
74465
74803
  */
74466
74804
  function normalizeParams(input) {
74467
74805
  if (!input || typeof input !== "object") return {};
74468
74806
  const output = {};
74469
- for (const [key, value] of Object.entries(input)) output[key] = coerceToString(value);
74807
+ for (const [key, value] of Object.entries(input)) if (value !== void 0 && value !== null) output[key] = coerceToString(value);
74470
74808
  return output;
74471
74809
  }
74472
74810
  function isNonArrayObject(value) {
@@ -74530,6 +74868,14 @@ function coercePayload(payload) {
74530
74868
  * - Frontend: provide internal API client
74531
74869
  * - SDK: provide SDK client with endpoint mapping
74532
74870
  */
74871
+ function projectDataLoaders(loaders, shouldLoad, blockId, blockKind) {
74872
+ if (!shouldLoad) return [...loaders];
74873
+ return loaders.filter(({ key }) => shouldLoad({
74874
+ blockId,
74875
+ blockKind,
74876
+ loaderKey: key
74877
+ }));
74878
+ }
74533
74879
  function shouldSkipResolvedLoader(input) {
74534
74880
  if (input.loader.requiredParams?.some((param) => !input.params[param]?.trim())) return true;
74535
74881
  return Boolean(input.isValidEndpoint && !input.isValidEndpoint(input.endpoint));
@@ -74558,7 +74904,7 @@ async function prefetchBlockData$1(page, context, options) {
74558
74904
  return (await prefetchBlockDataWithFailures(page, context, options)).resolvedData;
74559
74905
  }
74560
74906
  async function prefetchBlockDataWithFailures(page, context, options) {
74561
- const { apiClient, isValidEndpoint, onError, getCustomBlockLoaders, getBlockDefinition: getBlockDefinition$1 = getBlockDefinition, resolveParamsBindings: resolveParamsBindings$1 = resolveParamsBindings } = options;
74907
+ const { apiClient, isValidEndpoint, onError, getCustomBlockLoaders, getBlockDefinition: getBlockDefinition$1 = getBlockDefinition, resolveParamsBindings: resolveParamsBindings$1 = resolveParamsBindings, shouldLoad } = options;
74562
74908
  const results = {};
74563
74909
  const loaderFailures = {};
74564
74910
  const tasks = [];
@@ -74575,6 +74921,7 @@ async function prefetchBlockDataWithFailures(page, context, options) {
74575
74921
  loader
74576
74922
  })) : [];
74577
74923
  } else loaders = [];
74924
+ loaders = projectDataLoaders(loaders, shouldLoad, blockId, block.kind);
74578
74925
  if (loaders.length === 0) continue;
74579
74926
  const root = {
74580
74927
  ...context.root ?? {},
@@ -74672,8 +75019,8 @@ function toStringParams(value) {
74672
75019
  for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
74673
75020
  return out;
74674
75021
  }
74675
- function stripEmptyStringValues(params) {
74676
- for (const [key, value] of Object.entries(params)) if (value === "") delete params[key];
75022
+ function stripEmptyStringValues(params, endpoint) {
75023
+ for (const [key, value] of Object.entries(params)) if (value === "" && !(key === "seriesIds" && (endpoint === "listPublicEvents" || endpoint === "listPublicEventCalendarWindow"))) delete params[key];
74677
75024
  }
74678
75025
  function ensureSiteId(params, context) {
74679
75026
  if (!params.siteId && typeof context.siteId === "string" && context.siteId) params.siteId = context.siteId;
@@ -74686,7 +75033,7 @@ function ensureSiteId(params, context) {
74686
75033
  */
74687
75034
  function normalizeLoaderParams(endpoint, params, context) {
74688
75035
  const out = toStringParams(params);
74689
- stripEmptyStringValues(out);
75036
+ stripEmptyStringValues(out, endpoint);
74690
75037
  if (endpoint === "getPublicFormById") {
74691
75038
  ensureSiteId(out, context);
74692
75039
  if (!out.formId && out.formSlug) {
@@ -75114,7 +75461,7 @@ async function Layout(props) {
75114
75461
  if (footer === true) {
75115
75462
  const footerPlan = resolveFooterRenderPlan({
75116
75463
  isSdkSite: Boolean(site.is_sdk_site),
75117
- mode: theme.footer?.mode
75464
+ mode: resolveSdkFooterMode(siteData.sdkConfig)
75118
75465
  });
75119
75466
  const footerBlocksElement = footerPlan.renderFooterBlocks && footerBlocks.length > 0 ? /* @__PURE__ */ jsx(PageRenderer, {
75120
75467
  theme,
@@ -77735,11 +78082,14 @@ const sdkSiteStyleConfigSchema = z$1.object({
77735
78082
  sectionOptions: sdkSectionOptionsConfigSchema.optional(),
77736
78083
  containerOptions: sdkContainerOptionsConfigSchema.optional()
77737
78084
  });
77738
- const sdkSiteLayoutConfigSchema = z$1.object({ footerBlocks: z$1.array(z$1.object({
77739
- id: z$1.string().min(1).optional(),
77740
- kind: z$1.string().min(1),
77741
- content: z$1.record(z$1.string(), z$1.unknown()).optional()
77742
- })).optional() });
78085
+ const sdkSiteLayoutConfigSchema = z$1.object({
78086
+ footerMode: footerModeSchema.optional(),
78087
+ footerBlocks: z$1.array(z$1.object({
78088
+ id: z$1.string().min(1).optional(),
78089
+ kind: z$1.string().min(1),
78090
+ content: z$1.record(z$1.string(), z$1.unknown()).optional()
78091
+ })).optional()
78092
+ });
77743
78093
  const sdkFieldSelectOptionSchema = z$1.object({
77744
78094
  value: z$1.string(),
77745
78095
  label: z$1.string()