@riverbankcms/sdk 0.132.0 → 0.134.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 (159) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/{PreviewEditorSidebar-DKQ5eFK9.mjs → PreviewEditorSidebar-DfRhJPFQ.mjs} +3 -3
  3. package/dist/{PreviewEditorUI-CNTcOjCC.mjs → PreviewEditorUI-Dppxcr_g.mjs} +2 -2
  4. package/dist/{SdkPreviewModeRuntime-DJu3qu33.mjs → SdkPreviewModeRuntime-8CyGCXir.mjs} +46 -15
  5. package/dist/{SiteChromeCustomizeContext-2TzT3vbj.mjs → SiteChromeCustomizeContext-DVMTMvqA.mjs} +497 -169
  6. package/dist/_dts/ai/src/contracts/proposals.d.ts +384 -384
  7. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +219 -219
  8. package/dist/_dts/api/src/accountApi.d.ts +5 -5
  9. package/dist/_dts/api/src/appointmentEndpointDefinitions.d.ts +1 -1
  10. package/dist/_dts/api/src/bookingOperations.d.ts +2 -14
  11. package/dist/_dts/api/src/bookings.d.ts +2 -2
  12. package/dist/_dts/api/src/contentRuntime.d.ts +1 -1
  13. package/dist/_dts/api/src/courseEndpointDefinitions.d.ts +2 -2
  14. package/dist/_dts/api/src/endpoints.d.ts +3 -3
  15. package/dist/_dts/api/src/indexCoreTypes.d.ts +1 -1
  16. package/dist/_dts/api/src/navigation/contracts.d.ts +9 -6
  17. package/dist/_dts/api/src/navigation/linkResolver.d.ts +6 -2
  18. package/dist/_dts/api/src/navigation/types.d.ts +23 -6
  19. package/dist/_dts/api/src/navigation.d.ts +1 -1
  20. package/dist/_dts/api/src/publicBookingService.d.ts +15 -0
  21. package/dist/_dts/api/src/publicExperience/eventEndpoints.d.ts +3 -1
  22. package/dist/_dts/api/src/publicExperience/wholeSeriesEndpoints.d.ts +2 -0
  23. package/dist/_dts/api/src/sdk-event-upsert.d.ts +1 -1
  24. package/dist/_dts/api/src/sdkThemeWire.d.ts +50 -50
  25. package/dist/_dts/api/src/siteBanner.d.ts +4 -2
  26. package/dist/_dts/api/src/types.d.ts +1 -1
  27. package/dist/_dts/api/src/webhooks.d.ts +12 -12
  28. package/dist/_dts/blocks/src/client.d.ts +1 -1
  29. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +28 -72
  30. package/dist/_dts/blocks/src/index.d.ts +3 -2
  31. package/dist/_dts/blocks/src/primitives/content/components/RichText.d.ts +2 -2
  32. package/dist/_dts/blocks/src/primitives/content/primitives.d.ts +1 -0
  33. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +36 -14
  34. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +3 -0
  35. package/dist/_dts/blocks/src/system/blocks/events/whole-series/schemas.d.ts +3 -3
  36. package/dist/_dts/blocks/src/system/blocks/events/whole-series/types.d.ts +3 -0
  37. package/dist/_dts/blocks/src/system/blocks/location-map.d.ts +4 -4
  38. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +148 -148
  39. package/dist/_dts/blocks/src/system/blocks/offerings-preview/model.d.ts +2 -1
  40. package/dist/_dts/blocks/src/system/blocks/shared/bookingConsent.d.ts +36 -0
  41. package/dist/_dts/blocks/src/system/manifest/hydrateLinks.d.ts +9 -2
  42. package/dist/_dts/blocks/src/system/manifest/resolveBlockData.d.ts +8 -0
  43. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +79 -79
  44. package/dist/_dts/blocks/src/system/runtime/api/types/appointments.d.ts +2 -11
  45. package/dist/_dts/blocks/src/system/runtime/api/types/courses.d.ts +2 -0
  46. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +1 -0
  47. package/dist/_dts/blocks/src/system/runtime/components/multi-step/DynamicFormFields.d.ts +3 -19
  48. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +1 -1
  49. package/dist/_dts/blocks/src/system/runtime/hooks/useSelectedAppointmentConsentForm.d.ts +2 -0
  50. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.consent.d.ts +10 -0
  51. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.field-values.d.ts +4 -0
  52. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.fields.d.ts +21 -0
  53. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +2 -1
  54. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +38 -144
  55. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +5 -19
  56. package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +3 -2
  57. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/AttendeeDetailsStep.d.ts +2 -0
  58. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationSurface.d.ts +18 -1
  59. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +4 -4
  60. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/validation.d.ts +2 -1
  61. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/whole-series/components.d.ts +2 -1
  62. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/whole-series/whole-series-registration.headless.contract.d.ts +2 -0
  63. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +34 -12
  64. package/dist/_dts/blocks/src/system/runtime/shared/components/BookingConsentAgreement.d.ts +7 -0
  65. package/dist/_dts/blocks/src/system/runtime/shared/types.d.ts +1 -0
  66. package/dist/_dts/blocks/src/system/types/link.d.ts +7 -1
  67. package/dist/_dts/content-model/src/content-types/schema.d.ts +4 -4
  68. package/dist/_dts/core/src/booking-consent.d.ts +8 -0
  69. package/dist/_dts/core/src/index.d.ts +1 -0
  70. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +9 -3
  71. package/dist/_dts/db/src/schemas/forms.d.ts +21 -21
  72. package/dist/_dts/db/src/types/jsonb.d.ts +1 -1
  73. package/dist/_dts/editor/src/primitives/rich-text/RichTextEditorSurface.d.ts +2 -1
  74. package/dist/_dts/editor/src/primitives/rich-text/config.d.ts +2 -1
  75. package/dist/_dts/editor/src/primitives/rich-text/toolbar/linkPopoverUtils.d.ts +3 -2
  76. package/dist/_dts/editor/src/primitives/rich-text/useRichTextEditor.d.ts +3 -4
  77. package/dist/_dts/media-core/src/usageScanner.d.ts +1 -1
  78. package/dist/_dts/sdk/src/bookings/types.d.ts +7 -89
  79. package/dist/_dts/sdk/src/config/validation.d.ts +30 -30
  80. package/dist/_dts/sdk/src/contracts/content.d.ts +10 -36
  81. package/dist/_dts/sdk/src/contracts/rendering.d.ts +1 -1
  82. package/dist/_dts/sdk/src/manifest/passes/pass1-fields.d.ts +9 -9
  83. package/dist/_dts/sdk/src/public-api/contracts.d.ts +6 -19
  84. package/dist/_dts/sdk/src/version.d.ts +1 -1
  85. package/dist/_dts/theme-core/src/accordions/types.d.ts +2 -2
  86. package/dist/_dts/theme-core/src/buttons/types.d.ts +12 -12
  87. package/dist/_dts/theme-core/src/cards/types.d.ts +5 -5
  88. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +72 -72
  89. package/dist/_dts/theme-core/src/navigation/types.d.ts +4 -4
  90. package/dist/_dts/theme-core/src/palette/colors.d.ts +1 -1
  91. package/dist/_dts/theme-core/src/palette/curated/types.d.ts +2 -2
  92. package/dist/_dts/theme-core/src/schema.d.ts +82 -82
  93. package/dist/_dts/theme-core/src/site-styles/cardRoleVocabulary.d.ts +1 -1
  94. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +2 -2
  95. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -4
  96. package/dist/_dts/theme-core/src/site-styles/logoBranding.d.ts +4 -4
  97. package/dist/_dts/theme-core/src/tokens/themeColorTokens.d.ts +1 -1
  98. package/dist/_dts/ui/src/badge.d.ts +1 -1
  99. package/dist/_dts/ui/src/button.d.ts +1 -1
  100. package/dist/_dts/ui/src/callout.d.ts +1 -1
  101. package/dist/_dts/ui/src/card.d.ts +1 -1
  102. package/dist/client/{StepIndicator-CGixP_LW.mjs → BookingConsentAgreement-BxgpGEFD.mjs} +26 -4
  103. package/dist/client/{DiscountCodeDisclosure-DO7llLxm.mjs → DiscountCodeDisclosure-CfuPOHD0.mjs} +1 -1
  104. package/dist/client/{EventCombined.client-xID7zN__.mjs → EventCombined.client-DB8Qfzi-.mjs} +3 -3
  105. package/dist/client/{EventListing.client-CriBslp0.mjs → EventListing.client-DD4X8hp4.mjs} +2 -2
  106. package/dist/client/{LeafletStaticMap-DYmHnM8g.mjs → LeafletStaticMap-QhgIBQbv.mjs} +1 -1
  107. package/dist/client/{WholeSeriesRegistrationWizard-BEjzSiOA.mjs → WholeSeriesRegistrationWizard-DwtC7bCs.mjs} +72 -22
  108. package/dist/client/{accordion-CgFbnnnQ.mjs → accordion-DFXpLvwg.mjs} +1 -1
  109. package/dist/client/{bookingForm-dUyGpbxc.mjs → bookingForm-sI05d3lk.mjs} +73 -47
  110. package/dist/client/bookings.mjs +989 -140
  111. package/dist/client/{carousel-BvBwkUzx.mjs → carousel-wjyWTZCh.mjs} +1 -1
  112. package/dist/client/{cart-DiZENT2E.mjs → cart-DCwdpA4i.mjs} +3 -3
  113. package/dist/client/{checkout-Dq13KIgR.mjs → checkout-DTDW3l0R.mjs} +3 -3
  114. package/dist/client/checkoutUrls-DQYC_Br-.mjs +187 -0
  115. package/dist/client/client.mjs +556 -237
  116. package/dist/client/{context-B83PiNzm.mjs → context-TSK1CGx8.mjs} +1 -14
  117. package/dist/client/{courseRegistration-CKC5NmyH.mjs → courseRegistration-A-Tg7qw_.mjs} +3 -3
  118. package/dist/client/{eventCalendar-BF0Jatfw.mjs → eventCalendar-B0Zg-i_w.mjs} +5 -5
  119. package/dist/client/{eventCombined-BuoCF8je.mjs → eventCombined-B8Y2wizd.mjs} +3 -3
  120. package/dist/client/{eventDetails-CKwh0S9s.mjs → eventDetails-DPDOwURJ.mjs} +3 -3
  121. package/dist/client/{eventListing-eUxaWFaB.mjs → eventListing-CjBcmSLn.mjs} +3 -3
  122. package/dist/client/{eventRegistration-CfkNOkPb.mjs → eventRegistration-CK30HsAq.mjs} +111 -64
  123. package/dist/client/{form-C17H1c_H.mjs → form-CUXfsABE.mjs} +2 -2
  124. package/dist/client/{headerSection-DEwUmrtt.mjs → headerSection-N-mN3rjr.mjs} +2 -2
  125. package/dist/client/hooks.mjs +106 -3
  126. package/dist/client/{locationMap-Pf2QwuvW.mjs → locationMap-oiIDQMBt.mjs} +2 -2
  127. package/dist/client/{newsletterForm-BWQAH5Sv.mjs → newsletterForm-CkQ2tTrM.mjs} +2 -2
  128. package/dist/client/{pricing-CVdvbopJ.mjs → pricing-Caa0QWzh.mjs} +1 -1
  129. package/dist/client/{productDetail-BZi1Fa_G.mjs → productDetail-CnidyrsN.mjs} +3 -3
  130. package/dist/client/{productList-qq-55DNk.mjs → productList-CttYcS_S.mjs} +3 -3
  131. package/dist/client/{propsDecoder-BxmqPKdP.mjs → propsDecoder-CeUMxI9a.mjs} +211 -100
  132. package/dist/client/rendering/client.mjs +428 -151
  133. package/dist/client/rendering/islands.mjs +18 -18
  134. package/dist/client/rendering.mjs +643 -324
  135. package/dist/client/{scrollToBlock-B3CcKdtG.mjs → scrollToBlock-lPr1oqxB.mjs} +1 -1
  136. package/dist/client/{shop-DNWJPut5.mjs → shop-DXQtMo2O.mjs} +6 -6
  137. package/dist/client/{shop-commerce-BhvQCosS.mjs → shop-commerce-quDL411J.mjs} +7 -164
  138. package/dist/client/{shop-commerce-store-B04sCNnW.mjs → shop-commerce-store-Dnu-8Iub.mjs} +1 -1
  139. package/dist/client/{shopShared-DNNgZlmW.mjs → shopShared-BWPUURvG.mjs} +3 -3
  140. package/dist/client/{useEventFilters-CUmhE24_.mjs → useEventFilters-DRhWp2_5.mjs} +3 -3
  141. package/dist/client/{usePolling-M5jG675x.mjs → usePolling-UN4WKnaM.mjs} +2 -2
  142. package/dist/preview-next/client/runtime.mjs +3 -3
  143. package/dist/{sdk-runtime-BEXb1wqo.mjs → sdk-runtime-Ca5RD78X.mjs} +1 -1
  144. package/dist/{separator-DGMuip1D.mjs → separator-CMhj6BTi.mjs} +1 -1
  145. package/dist/server/components.mjs +117 -37
  146. package/dist/server/config-validation.mjs +106 -3
  147. package/dist/server/config.mjs +106 -3
  148. package/dist/server/data.mjs +106 -3
  149. package/dist/server/index.mjs +1 -1
  150. package/dist/server/next.mjs +186 -106
  151. package/dist/server/page-converter.mjs +105 -2
  152. package/dist/server/prebuild.mjs +1 -1
  153. package/dist/server/rendering/server.mjs +119 -37
  154. package/dist/server/rendering.mjs +117 -37
  155. package/dist/server/routing.mjs +132 -73
  156. package/dist/server/server.mjs +107 -4
  157. package/dist/server/theme-bridge.mjs +4 -4
  158. package/package.json +2 -2
  159. package/dist/client/checkoutUrls-Ct8Mxna3.mjs +0 -26
@@ -3211,20 +3211,7 @@ function createBlockApi(config) {
3211
3211
  const path = buildPath(WHOLE_SERIES.checkout.path, { siteId: sid });
3212
3212
  return request(WHOLE_SERIES.checkout, {
3213
3213
  path,
3214
- body: {
3215
- eventSeriesId: data.eventSeriesId,
3216
- ...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
3217
- ...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
3218
- email: data.email,
3219
- name: data.name,
3220
- phone: data.phone,
3221
- formData: data.formData,
3222
- altchaSolution: data.altchaSolution,
3223
- discountCode: data.discountCode,
3224
- successUrl: data.successUrl,
3225
- cancelUrl: data.cancelUrl,
3226
- claimToken: data.claimToken
3227
- }
3214
+ body: data
3228
3215
  });
3229
3216
  },
3230
3217
  async getWholeSeriesRegistrationStatus(params) {
@@ -3676,7 +3663,7 @@ function getChallengeUrl() {
3676
3663
  }
3677
3664
  //#endregion
3678
3665
  //#region ../core/src/result.ts
3679
- function ok(value) {
3666
+ function ok$1(value) {
3680
3667
  return {
3681
3668
  ok: true,
3682
3669
  value
@@ -3696,11 +3683,14 @@ function parseUuidBrand(value, unsafeConstructor) {
3696
3683
  const normalized = value.trim().toLowerCase();
3697
3684
  if (normalized.length === 0) return err$1({ kind: "empty_string" });
3698
3685
  if (!UUID_PATTERN.test(normalized)) return err$1({ kind: "invalid_uuid" });
3699
- return ok(unsafeConstructor(normalized));
3686
+ return ok$1(unsafeConstructor(normalized));
3700
3687
  }
3701
3688
  const unsafeAsSiteId = (id) => id;
3702
3689
  const unsafeAsEventOfferingId = (id) => id;
3703
3690
  const unsafeAsEventSeriesId = (id) => id;
3691
+ const unsafeAsMediaAssetId = (id) => id;
3692
+ const unsafeAsStorageBucketName = (value) => value;
3693
+ const unsafeAsStoragePath = (value) => value;
3704
3694
  const unsafeAsFormId = (id) => id;
3705
3695
  const unsafeAsPassId = (id) => id;
3706
3696
  const unsafeAsCustomerPassId = (id) => id;
@@ -3843,7 +3833,7 @@ function parseEventPageSurfaceSubject(value) {
3843
3833
  kind: "invalidEventSeriesBookingScope",
3844
3834
  bookingScope: value.bookingScope
3845
3835
  });
3846
- return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope));
3836
+ return ok$1(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope));
3847
3837
  }
3848
3838
  case "eventOffering": {
3849
3839
  const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
@@ -3864,7 +3854,7 @@ function parseEventPageSurfaceSubject(value) {
3864
3854
  });
3865
3855
  parsedSeriesIds.push(eventSeriesId.value);
3866
3856
  }
3867
- return ok({
3857
+ return ok$1({
3868
3858
  kind: "eventOffering",
3869
3859
  eventOfferingId: eventOfferingId.value,
3870
3860
  availableEventSeriesIds: parsedSeriesIds
@@ -3982,7 +3972,7 @@ function parseBookingTimeZone(timeZone) {
3982
3972
  if (typeof timeZone !== "string") return err$1("invalid-time-zone");
3983
3973
  const normalized = timeZone.trim();
3984
3974
  if (normalized.length === 0 || NUMERIC_OFFSET_TIME_ZONE_PATTERN.test(normalized) || !isValidBookingTimeZone(normalized)) return err$1("invalid-time-zone");
3985
- return ok(normalized);
3975
+ return ok$1(normalized);
3986
3976
  }
3987
3977
  //#endregion
3988
3978
  //#region ../core/src/booking-date-time.ts
@@ -4061,7 +4051,7 @@ function parseUtcIsoDateTimeString(value) {
4061
4051
  if (!hasUtcSuffix && !hasExplicitOffset) return err$1("missing-timezone-offset");
4062
4052
  const date = new Date(normalized);
4063
4053
  if (Number.isNaN(date.getTime())) return err$1("invalid-date-time");
4064
- return ok(date.toISOString());
4054
+ return ok$1(date.toISOString());
4065
4055
  }
4066
4056
  function toUtcIsoDateTimeString(value) {
4067
4057
  const parsed = parseUtcIsoDateTimeString(value);
@@ -4213,6 +4203,109 @@ function assertNever(value, message) {
4213
4203
  throw new Error(message ?? `Unexpected value: ${String(value)}`);
4214
4204
  }
4215
4205
  //#endregion
4206
+ //#region ../blocks/src/system/manifest/richTextSchema.ts
4207
+ /**
4208
+ * Shared Rich Text Schema
4209
+ *
4210
+ * This module provides a single source of truth for TipTap rich text validation
4211
+ * across both the block-form editor and the block rendering system.
4212
+ *
4213
+ * Rich-text fields use one canonical root shape:
4214
+ * `{ type: 'doc', content: [...] }`.
4215
+ */
4216
+ /**
4217
+ * TipTap document node schema (recursive structure)
4218
+ *
4219
+ * Supports the full TipTap document structure including:
4220
+ * - type: Node type (e.g., 'doc', 'paragraph', 'text')
4221
+ * - content: Nested array of child nodes
4222
+ * - text: Text content for text nodes
4223
+ * - attrs: Node attributes (e.g., alignment, color)
4224
+ * - marks: Text formatting marks (e.g., bold, italic)
4225
+ */
4226
+ const TipTapMarkSchema = z.object({
4227
+ type: z.string().min(1),
4228
+ attrs: z.record(z.string(), z.unknown()).optional()
4229
+ }).strict();
4230
+ const TipTapNodeSchema = z.lazy(() => z.object({
4231
+ type: z.string().min(1),
4232
+ content: z.array(TipTapNodeSchema).optional(),
4233
+ text: z.string().optional(),
4234
+ attrs: z.record(z.string(), z.unknown()).optional(),
4235
+ marks: z.array(TipTapMarkSchema).optional()
4236
+ }).strict().superRefine((node, ctx) => {
4237
+ if (node.type === "doc") ctx.addIssue({
4238
+ code: "custom",
4239
+ path: ["type"],
4240
+ message: "Document nodes are only valid at the rich-text root"
4241
+ });
4242
+ if (node.type === "text") {
4243
+ if (node.text === void 0 || node.text.length === 0) ctx.addIssue({
4244
+ code: "custom",
4245
+ path: ["text"],
4246
+ message: "Text nodes require a non-empty text value"
4247
+ });
4248
+ if (node.content !== void 0) ctx.addIssue({
4249
+ code: "custom",
4250
+ path: ["content"],
4251
+ message: "Text nodes cannot contain child nodes"
4252
+ });
4253
+ return;
4254
+ }
4255
+ if (node.text !== void 0) ctx.addIssue({
4256
+ code: "custom",
4257
+ path: ["text"],
4258
+ message: "Only text nodes can contain a text value"
4259
+ });
4260
+ }));
4261
+ /**
4262
+ * Canonical schema for a TipTap rich-text document root.
4263
+ *
4264
+ * Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
4265
+ * `content` is required (possibly empty). This is the shape every producer
4266
+ * (dummy content, editor output, migrations) must emit, and the shape every
4267
+ * non-legacy consumer can rely on.
4268
+ *
4269
+ */
4270
+ const RichTextDocSchema = z.object({
4271
+ type: z.literal("doc"),
4272
+ content: z.array(TipTapNodeSchema)
4273
+ }).strict();
4274
+ //#endregion
4275
+ //#region ../blocks/src/system/blocks/shared/bookingConsent.ts
4276
+ const BOOKING_REQUIREMENT_CONSENT_FIELD_PREFIX = "booking_requirement_consent_";
4277
+ function toBookingRequirementConsentFieldId(requirementId) {
4278
+ return `${BOOKING_REQUIREMENT_CONSENT_FIELD_PREFIX}${requirementId}`;
4279
+ }
4280
+ function isBookingRequirementConsentFieldId(value) {
4281
+ return value.startsWith(BOOKING_REQUIREMENT_CONSENT_FIELD_PREFIX);
4282
+ }
4283
+ function toBookingRequirementConsentField(consent) {
4284
+ return {
4285
+ id: toBookingRequirementConsentFieldId(consent.requirementId),
4286
+ label: consent.title,
4287
+ type: "booking_requirement_consent",
4288
+ required: true,
4289
+ agreement: consent.agreement,
4290
+ checkboxLabel: consent.checkboxLabel
4291
+ };
4292
+ }
4293
+ const blockBookingConsentSchema = z.object({
4294
+ requirementId: z.string(),
4295
+ title: z.string(),
4296
+ agreement: RichTextDocSchema,
4297
+ checkboxLabel: z.string()
4298
+ });
4299
+ const bookingRequirementConsentFieldSchema = z.object({
4300
+ id: z.string(),
4301
+ label: z.string(),
4302
+ type: z.literal("booking_requirement_consent"),
4303
+ required: z.literal(true),
4304
+ agreement: RichTextDocSchema,
4305
+ checkboxLabel: z.string().min(1)
4306
+ });
4307
+ z.union([blockBookingConsentSchema, bookingRequirementConsentFieldSchema]);
4308
+ //#endregion
4216
4309
  //#region ../blocks/src/system/runtime/nodes/booking-form.reserved-fields.ts
4217
4310
  const BOOKING_FORM_DATA_RESERVED_KEY_VALUES = [
4218
4311
  "serviceId",
@@ -4226,6 +4319,28 @@ const BOOKING_FORM_DATA_RESERVED_KEY_VALUES = [
4226
4319
  ];
4227
4320
  const BOOKING_FORM_DATA_RESERVED_KEYS = new Set(BOOKING_FORM_DATA_RESERVED_KEY_VALUES);
4228
4321
  //#endregion
4322
+ //#region ../blocks/src/system/runtime/nodes/booking-form.consent.ts
4323
+ function resolveSelectedAppointmentConsent(service) {
4324
+ return service?.consent ? {
4325
+ kind: "required-for-selected-service",
4326
+ field: toBookingRequirementConsentField(service.consent)
4327
+ } : { kind: "not-applicable" };
4328
+ }
4329
+ function projectSelectedAppointmentConsentIntoForm(form, selectedConsent) {
4330
+ if (!form) return null;
4331
+ const sourceFields = form.schema?.fields ?? [];
4332
+ const baseFields = sourceFields.filter((field) => field.type !== "booking_requirement_consent");
4333
+ const fields = selectedConsent.kind === "required-for-selected-service" ? [...baseFields, selectedConsent.field] : baseFields;
4334
+ if (selectedConsent.kind === "not-applicable" && baseFields.length === sourceFields.length) return form;
4335
+ return {
4336
+ ...form,
4337
+ schema: {
4338
+ ...form.schema,
4339
+ fields
4340
+ }
4341
+ };
4342
+ }
4343
+ //#endregion
4229
4344
  //#region ../blocks/src/system/runtime/nodes/booking-form.state.ts
4230
4345
  function toTrimmedString(value) {
4231
4346
  const trimmed = value?.trim();
@@ -4408,7 +4523,6 @@ function readBookingFieldType(value) {
4408
4523
  case "select":
4409
4524
  case "radio":
4410
4525
  case "checkbox":
4411
- case "booking_requirement_consent":
4412
4526
  case "tel":
4413
4527
  case "url":
4414
4528
  case "number":
@@ -4443,7 +4557,7 @@ function normalizeBookingSettings(value) {
4443
4557
  };
4444
4558
  }
4445
4559
  function isBookingFormService(value) {
4446
- return isRecord$5(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string";
4560
+ return isRecord$5(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
4447
4561
  }
4448
4562
  function collectBookingCustomFields(data) {
4449
4563
  const customFields = {};
@@ -4687,6 +4801,11 @@ function getBookingSuccessMessage(successMessage) {
4687
4801
  return successMessage?.trim() || "Your appointment has been booked! Check your email for confirmation.";
4688
4802
  }
4689
4803
  //#endregion
4804
+ //#region ../blocks/src/system/runtime/hooks/useSelectedAppointmentConsentForm.ts
4805
+ function useSelectedAppointmentConsentForm(form, selectedService) {
4806
+ return useMemo(() => projectSelectedAppointmentConsentIntoForm(form, resolveSelectedAppointmentConsent(selectedService)), [form, selectedService]);
4807
+ }
4808
+ //#endregion
4690
4809
  //#region ../blocks/src/utils/date-formatting.ts
4691
4810
  /**
4692
4811
  * Resolve the browser/runtime timezone for booking surfaces.
@@ -5751,6 +5870,19 @@ function clearBookingFieldError(errors, fieldId) {
5751
5870
  };
5752
5871
  }
5753
5872
  //#endregion
5873
+ //#region ../blocks/src/system/runtime/nodes/booking-form.field-values.ts
5874
+ function collectInitialBookingFieldValues(initialData) {
5875
+ const values = {};
5876
+ for (const [key, value] of Object.entries(initialData)) if (!BOOKING_FORM_DATA_RESERVED_KEYS.has(key) && !isBookingRequirementConsentFieldId(key) && isBookingFieldValue(value)) values[key] = value;
5877
+ return values;
5878
+ }
5879
+ function omitBookingConsentEntries(record) {
5880
+ return Object.fromEntries(Object.entries(record).filter(([key]) => !isBookingRequirementConsentFieldId(key)));
5881
+ }
5882
+ function isBookingFieldValue(value) {
5883
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && value.every((item) => typeof item === "string");
5884
+ }
5885
+ //#endregion
5754
5886
  //#region ../blocks/src/system/runtime/nodes/booking-form.headless.state.ts
5755
5887
  const unsafeAsBookingFieldId = (id) => id;
5756
5888
  function createInitialBookingFlowReducerState(input) {
@@ -5762,7 +5894,7 @@ function createInitialBookingFlowReducerState(input) {
5762
5894
  selectedResourceId: toOptionalAppointmentResourceId(toOptionalString(initialData.resourceId) ?? input.defaultResourceId),
5763
5895
  selectedDate: toOptionalString(initialData.selectedDate),
5764
5896
  selectedSlot: null,
5765
- fieldValues: collectInitialFieldValues(initialData),
5897
+ fieldValues: collectInitialBookingFieldValues(initialData),
5766
5898
  touchedFields: {},
5767
5899
  attemptedStepIds: {},
5768
5900
  discountCode: toOptionalString(initialData.discountCode) ?? "",
@@ -5774,14 +5906,6 @@ function createInitialBookingFlowReducerState(input) {
5774
5906
  feedback: { kind: "none" }
5775
5907
  };
5776
5908
  }
5777
- function collectInitialFieldValues(initialData) {
5778
- const values = {};
5779
- for (const [key, value] of Object.entries(initialData)) {
5780
- if (BOOKING_FORM_DATA_RESERVED_KEYS.has(key) || !isBookingFieldValue(value)) continue;
5781
- values[key] = value;
5782
- }
5783
- return values;
5784
- }
5785
5909
  function toOptionalString(value) {
5786
5910
  if (typeof value !== "string") return void 0;
5787
5911
  const trimmed = value.trim();
@@ -5803,9 +5927,6 @@ function toOptionalAppointmentResourceId(value) {
5803
5927
  function toOptionalCustomerPassId(value) {
5804
5928
  return toOptionalBrandedId(value, unsafeAsCustomerPassId);
5805
5929
  }
5806
- function isBookingFieldValue(value) {
5807
- return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && value.every((item) => typeof item === "string");
5808
- }
5809
5930
  /**
5810
5931
  * Send the flow back to the date step when the slot it was holding went away,
5811
5932
  * leaving a scoped feedback message. A completed booking is left alone.
@@ -5827,17 +5948,7 @@ function applyAvailabilitySelectionInvalidation(state, invalidation) {
5827
5948
  }
5828
5949
  function bookingFlowReducer(state, event) {
5829
5950
  switch (event.type) {
5830
- case "service-selected": return {
5831
- ...state,
5832
- selectedServiceId: event.serviceId,
5833
- selectedResourceId: void 0,
5834
- selectedDate: void 0,
5835
- selectedSlot: null,
5836
- selectedAppointmentPackageId: void 0,
5837
- selectedSlidingAmountInput: "",
5838
- selectedCollectionOptionKind: void 0,
5839
- validationErrors: clearBookingFormError(state.validationErrors)
5840
- };
5951
+ case "service-selected": return applyBookingServiceSelection(state, event.serviceId);
5841
5952
  case "resource-selected": return {
5842
5953
  ...state,
5843
5954
  selectedResourceId: event.resourceId ?? void 0,
@@ -5944,6 +6055,24 @@ function bookingFlowReducer(state, event) {
5944
6055
  default: return assertNever(event);
5945
6056
  }
5946
6057
  }
6058
+ function applyBookingServiceSelection(state, serviceId) {
6059
+ return {
6060
+ ...state,
6061
+ selectedServiceId: serviceId,
6062
+ selectedResourceId: void 0,
6063
+ selectedDate: void 0,
6064
+ selectedSlot: null,
6065
+ selectedAppointmentPackageId: void 0,
6066
+ selectedSlidingAmountInput: "",
6067
+ selectedCollectionOptionKind: void 0,
6068
+ fieldValues: omitBookingConsentEntries(state.fieldValues),
6069
+ touchedFields: omitBookingConsentEntries(state.touchedFields),
6070
+ validationErrors: {
6071
+ ...clearBookingFormError(state.validationErrors),
6072
+ fieldErrors: omitBookingConsentEntries(state.validationErrors.fieldErrors)
6073
+ }
6074
+ };
6075
+ }
5947
6076
  function clearAvailabilityFeedback(feedback) {
5948
6077
  return feedback.kind === "error" && feedback.scope === "availability" ? { kind: "none" } : feedback;
5949
6078
  }
@@ -6104,7 +6233,8 @@ function validateBookingFields(input) {
6104
6233
  return errors;
6105
6234
  }
6106
6235
  function validateBookingField(field, value) {
6107
- if (field.required && isBlankFieldValue(value)) return `${field.label} is required`;
6236
+ const requiredError = validateRequiredBookingField(field, value);
6237
+ if (requiredError) return requiredError;
6108
6238
  if (isBlankFieldValue(value)) return null;
6109
6239
  const strValue = typeof value === "string" ? value : String(value);
6110
6240
  if (field.type === "email") return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(strValue) ? null : "Please enter a valid email address";
@@ -6133,6 +6263,10 @@ function validateBookingField(field, value) {
6133
6263
  }
6134
6264
  return null;
6135
6265
  }
6266
+ function validateRequiredBookingField(field, value) {
6267
+ if (field.type === "booking_requirement_consent") return value === true ? null : `${field.label} is required`;
6268
+ return field.required && isBlankFieldValue(value) ? `${field.label} is required` : null;
6269
+ }
6136
6270
  function deriveBookingFlowSteps(input) {
6137
6271
  const stepResolution = buildBookingStepResolution({
6138
6272
  form: input.form,
@@ -6204,7 +6338,8 @@ function deriveBookingFieldViews(input) {
6204
6338
  return (input.form?.schema?.fields ?? []).map((field) => {
6205
6339
  const fieldId = unsafeAsBookingFieldId(field.id);
6206
6340
  const inputId = `${input.idPrefix}-${fieldId}`;
6207
- const helpTextId = field.helpText ? `${inputId}-help` : null;
6341
+ const helpText = field.type === "booking_requirement_consent" ? null : field.helpText ?? null;
6342
+ const helpTextId = helpText ? `${inputId}-help` : null;
6208
6343
  const fieldValue = input.values[field.id] ?? null;
6209
6344
  const localError = input.touched?.[field.id] ? validateBookingField(field, fieldValue) : null;
6210
6345
  const error = input.errors[field.id] ?? localError;
@@ -6214,17 +6349,17 @@ function deriveBookingFieldViews(input) {
6214
6349
  field,
6215
6350
  id: fieldId,
6216
6351
  label: field.label,
6217
- type: field.type,
6218
6352
  value: fieldValue,
6219
6353
  required: Boolean(field.required),
6220
6354
  error,
6221
- helpText: field.helpText ?? null,
6355
+ helpText,
6222
6356
  inputId,
6223
6357
  labelId: `${inputId}-label`,
6224
6358
  helpTextId,
6225
6359
  errorId,
6226
6360
  describedBy,
6227
- ariaInvalid: Boolean(error)
6361
+ ariaInvalid: Boolean(error),
6362
+ type: field.type
6228
6363
  };
6229
6364
  });
6230
6365
  }
@@ -6486,6 +6621,7 @@ function useHeadlessBookingFlow(options) {
6486
6621
  const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
6487
6622
  const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
6488
6623
  const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
6624
+ const runtimeForm = useSelectedAppointmentConsentForm(options.form, selectedService);
6489
6625
  const lastSlidingServiceIdRef = useRef(null);
6490
6626
  useEffect(() => {
6491
6627
  const selectedId = selectedService?.id ?? null;
@@ -6526,7 +6662,7 @@ function useHeadlessBookingFlow(options) {
6526
6662
  viewerTimeZone
6527
6663
  ]);
6528
6664
  const stepState = useMemo(() => deriveBookingFlowSteps({
6529
- form: options.form,
6665
+ form: runtimeForm,
6530
6666
  services: options.services,
6531
6667
  data,
6532
6668
  defaultServiceId,
@@ -6542,7 +6678,7 @@ function useHeadlessBookingFlow(options) {
6542
6678
  defaultServiceId,
6543
6679
  defaultResourceId,
6544
6680
  options.enableAppointmentPackages,
6545
- options.form,
6681
+ runtimeForm,
6546
6682
  options.requiresResourceSelection,
6547
6683
  options.requiresServiceSelection,
6548
6684
  options.services,
@@ -6596,13 +6732,13 @@ function useHeadlessBookingFlow(options) {
6596
6732
  state.selectedSlot
6597
6733
  ]);
6598
6734
  const fields = useMemo(() => deriveBookingFieldViews({
6599
- form: options.form,
6735
+ form: runtimeForm,
6600
6736
  values: state.fieldValues,
6601
6737
  errors: state.validationErrors.fieldErrors,
6602
6738
  touched: state.touchedFields,
6603
6739
  idPrefix: `booking-${options.formId}`
6604
6740
  }), [
6605
- options.form,
6741
+ runtimeForm,
6606
6742
  options.formId,
6607
6743
  state.fieldValues,
6608
6744
  state.touchedFields,
@@ -6722,7 +6858,7 @@ function useHeadlessBookingFlow(options) {
6722
6858
  const latestRef = useLatestRef({
6723
6859
  state,
6724
6860
  data,
6725
- form: options.form,
6861
+ form: runtimeForm,
6726
6862
  formId: formKey,
6727
6863
  services: options.services,
6728
6864
  defaultServiceId,
@@ -6974,7 +7110,7 @@ function useHeadlessBookingFlow(options) {
6974
7110
  phase: state.phase
6975
7111
  });
6976
7112
  const view = resolveHeadlessBookingView({
6977
- form: options.form,
7113
+ form: runtimeForm,
6978
7114
  feedback: state.feedback
6979
7115
  });
6980
7116
  const appointmentPackagesModel = resolveAppointmentPackagesModel({
@@ -12416,6 +12552,768 @@ function FieldValidationMessage({ id, children, className, live = "assertive" })
12416
12552
  });
12417
12553
  }
12418
12554
  //#endregion
12555
+ //#region ../blocks/src/primitives/content/utils/sanitizeHref.ts
12556
+ const SAFE_PROTOCOLS = /* @__PURE__ */ new Set([
12557
+ "http:",
12558
+ "https:",
12559
+ "mailto:",
12560
+ "tel:"
12561
+ ]);
12562
+ const CONTROL_CHARS_PATTERN = /[\u0000-\u001F\u007F]+/g;
12563
+ function normalizeInput(value) {
12564
+ return value.replace(CONTROL_CHARS_PATTERN, "").trim();
12565
+ }
12566
+ function decodeForSchemeCheck(value) {
12567
+ try {
12568
+ return decodeURIComponent(value);
12569
+ } catch {
12570
+ return value;
12571
+ }
12572
+ }
12573
+ /**
12574
+ * Returns a safe href for rich-text anchors, or null when the value should not be rendered.
12575
+ *
12576
+ * Policy:
12577
+ * - Allow absolute URLs with protocols: http, https, mailto, tel.
12578
+ * - Allow root-relative ("/path") and hash-only ("#section") links.
12579
+ * - Reject protocol-relative URLs ("//example.com"), bare domains ("example.com"),
12580
+ * executable schemes (javascript/data/vbscript), malformed URLs, and empty values.
12581
+ * - Preserve percent-encoding from the original normalized input.
12582
+ */
12583
+ function sanitizeHref(raw) {
12584
+ if (typeof raw !== "string") return null;
12585
+ const normalized = normalizeInput(raw);
12586
+ if (!normalized) return null;
12587
+ if (normalized.startsWith("//")) return null;
12588
+ if (normalized.startsWith("/")) return normalized;
12589
+ if (normalized.startsWith("#")) return normalized;
12590
+ const schemeMatch = normalizeInput(decodeForSchemeCheck(normalized)).match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
12591
+ if (!schemeMatch) return null;
12592
+ const rawScheme = schemeMatch[1];
12593
+ if (!rawScheme) return null;
12594
+ const protocol = `${rawScheme.toLowerCase()}:`;
12595
+ if (!SAFE_PROTOCOLS.has(protocol)) return null;
12596
+ try {
12597
+ const parsed = new URL(normalized);
12598
+ if (!SAFE_PROTOCOLS.has(parsed.protocol.toLowerCase())) return null;
12599
+ } catch {
12600
+ return null;
12601
+ }
12602
+ return normalized;
12603
+ }
12604
+ //#endregion
12605
+ //#region ../media-core/src/fileSafety.ts
12606
+ const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
12607
+ function containsUnsafePathSegmentCharacter(value) {
12608
+ return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
12609
+ }
12610
+ function isReservedPathSegment(value) {
12611
+ return value === "." || value === "..";
12612
+ }
12613
+ function isUnsafePathSegment(segment) {
12614
+ return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
12615
+ }
12616
+ ({
12617
+ publicSite: unsafeAsStorageBucketName("media"),
12618
+ privateSite: unsafeAsStorageBucketName("site-files-private"),
12619
+ temporary: unsafeAsStorageBucketName("temp-uploads")
12620
+ }).publicSite;
12621
+ function parseStoragePath(value) {
12622
+ return joinStoragePathSegments(value.split("/"));
12623
+ }
12624
+ /**
12625
+ * Joins already-branded path segments while preserving the Storage RLS invariant
12626
+ * that every tenant/site identifier remains one segment, never a nested path.
12627
+ */
12628
+ function joinStoragePathSegments(segments) {
12629
+ if (segments.some(isUnsafePathSegment)) return err$1("unsafe-path-segment");
12630
+ return ok$1(unsafeAsStoragePath(segments.join("/")));
12631
+ }
12632
+ //#endregion
12633
+ //#region ../media-core/src/storagePathDomain.ts
12634
+ function ok(value) {
12635
+ return {
12636
+ ok: true,
12637
+ value
12638
+ };
12639
+ }
12640
+ function fail(error) {
12641
+ return {
12642
+ ok: false,
12643
+ error
12644
+ };
12645
+ }
12646
+ function normalizeStringInput(value) {
12647
+ if (typeof value !== "string") return fail({ tag: "MISSING" });
12648
+ const trimmed = value.trim();
12649
+ if (!trimmed) return fail({ tag: "EMPTY" });
12650
+ return ok(trimmed);
12651
+ }
12652
+ function validateRelativeStoragePath(value) {
12653
+ if (value.startsWith("sites/")) return fail({ tag: "SITE_SCOPED_PATH" });
12654
+ if (value.startsWith("/")) return fail({ tag: "LEADING_SLASH" });
12655
+ if (value.includes("//")) return fail({ tag: "DOUBLE_SLASH" });
12656
+ if (value.includes("%")) return fail({ tag: "PERCENT_ENCODING" });
12657
+ if (value.split("/").some((segment) => segment === "." || segment === "..")) return fail({ tag: "TRAVERSAL" });
12658
+ return ok(value);
12659
+ }
12660
+ function brandRelativeStoragePath(value) {
12661
+ return value;
12662
+ }
12663
+ function parseRelativeStoragePath(value) {
12664
+ const normalized = normalizeStringInput(value);
12665
+ if (!normalized.ok) return normalized;
12666
+ const validated = validateRelativeStoragePath(normalized.value);
12667
+ if (!validated.ok) return validated;
12668
+ return ok(brandRelativeStoragePath(validated.value));
12669
+ }
12670
+ //#endregion
12671
+ //#region ../media-core/src/url.ts
12672
+ const DEFAULT_BASE_PATH = "/api/img";
12673
+ function isSiteScopedDirectImageStoragePath(storagePath) {
12674
+ return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
12675
+ }
12676
+ function encodeStoragePath(storagePath) {
12677
+ return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
12678
+ }
12679
+ function resolveDirectImagePathTarget(target) {
12680
+ switch (target.kind) {
12681
+ case "relative": return `sites/${target.siteId}/${target.storagePath}`;
12682
+ case "site-scoped":
12683
+ case "bucket-scoped": return target.storagePath;
12684
+ }
12685
+ }
12686
+ function getDirectImageUrl(supabaseUrl, target, storageBucket = "media", options) {
12687
+ const baseUrl = supabaseUrl.replace(/\/$/, "");
12688
+ const encodedPath = encodeStoragePath(resolveDirectImagePathTarget(target));
12689
+ const isSvg = target.storagePath.toLowerCase().endsWith(".svg");
12690
+ if (options && !isSvg && (options.width || options.height)) {
12691
+ const params = new URLSearchParams();
12692
+ if (options.width) params.set("width", String(options.width));
12693
+ if (options.height) params.set("height", String(options.height));
12694
+ if (options.quality) params.set("quality", String(options.quality));
12695
+ if (options.resize) params.set("resize", options.resize);
12696
+ return `${baseUrl}/storage/v1/render/image/public/${storageBucket}/${encodedPath}?${params.toString()}`;
12697
+ }
12698
+ return `${baseUrl}/storage/v1/object/public/${storageBucket}/${encodedPath}`;
12699
+ }
12700
+ function getDirectImageUrlForRelativePath(supabaseUrl, storagePath, siteId, storageBucket = "media", options) {
12701
+ return getDirectImageUrl(supabaseUrl, {
12702
+ kind: "relative",
12703
+ storagePath,
12704
+ siteId
12705
+ }, storageBucket, options);
12706
+ }
12707
+ function getDirectImageUrlForSiteScopedPath(supabaseUrl, storagePath, storageBucket = "media", options) {
12708
+ return getDirectImageUrl(supabaseUrl, {
12709
+ kind: "site-scoped",
12710
+ storagePath
12711
+ }, storageBucket, options);
12712
+ }
12713
+ const formatNumber = (value) => Number.isInteger(value) ? value.toString() : value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "");
12714
+ const ORDER = [
12715
+ "w",
12716
+ "h",
12717
+ "fit",
12718
+ "rect",
12719
+ "q",
12720
+ "autoFormat",
12721
+ "or"
12722
+ ];
12723
+ const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
12724
+ const path = `${basePath.endsWith("/") ? basePath.slice(0, Math.max(basePath.length - 1, 0)) : basePath}/${encodeURIComponent(asset.id)}`;
12725
+ const searchParams = new URLSearchParams();
12726
+ if (params.w !== void 0) searchParams.set("w", formatNumber(params.w));
12727
+ if (params.h !== void 0) searchParams.set("h", formatNumber(params.h));
12728
+ if (params.fit !== void 0) searchParams.set("fit", params.fit);
12729
+ if (params.rect) searchParams.set("rect", params.rect.map(formatNumber).join(","));
12730
+ if (params.q !== void 0) searchParams.set("q", formatNumber(params.q));
12731
+ if (params.autoFormat) searchParams.append("auto", "format");
12732
+ if (params.or !== void 0) searchParams.set("or", String(params.or));
12733
+ const sorted = new URLSearchParams();
12734
+ ORDER.forEach((key) => {
12735
+ if (key === "autoFormat") {
12736
+ if (params.autoFormat) sorted.append("auto", "format");
12737
+ return;
12738
+ }
12739
+ const value = searchParams.get(key);
12740
+ if (value !== null) sorted.set(key, value);
12741
+ });
12742
+ const query = sorted.toString();
12743
+ return query ? `${path}?${query}` : path;
12744
+ };
12745
+ //#endregion
12746
+ //#region ../media-core/src/transformRect.ts
12747
+ function isValidTransformRectStructure(rect) {
12748
+ if (!Array.isArray(rect) || rect.length !== 4) return false;
12749
+ const [left, top, width, height] = rect;
12750
+ return Number.isFinite(left) && Number.isFinite(top) && Number.isFinite(width) && Number.isFinite(height) && left >= 0 && top >= 0 && width > 0 && height > 0;
12751
+ }
12752
+ //#endregion
12753
+ //#region ../media-core/src/aspectTransforms.ts
12754
+ const DEFAULT_RECT_EPSILON = .5;
12755
+ function parseAspectRatioValue(value) {
12756
+ if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
12757
+ if (typeof value !== "string") return void 0;
12758
+ const normalized = value.trim();
12759
+ if (!normalized) return void 0;
12760
+ if (normalized === "auto") return void 0;
12761
+ if (normalized.includes(":") || normalized.includes("/")) {
12762
+ const delimiter = normalized.includes(":") ? ":" : "/";
12763
+ const [wRaw, hRaw] = normalized.split(delimiter);
12764
+ const width = Number(wRaw?.trim());
12765
+ const height = Number(hRaw?.trim());
12766
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
12767
+ return width / height;
12768
+ }
12769
+ const numeric = Number(normalized);
12770
+ if (!Number.isFinite(numeric) || numeric <= 0) return void 0;
12771
+ return numeric;
12772
+ }
12773
+ const MAX_ASPECT_DISTANCE = .25;
12774
+ function resolveMediaTransformForAspect(transform, targetAspect) {
12775
+ if (!transform) return {};
12776
+ const base = {
12777
+ rect: transform.rect,
12778
+ hotspot: transform.hotspot,
12779
+ rotation: transform.rotation
12780
+ };
12781
+ const target = parseAspectRatioValue(targetAspect);
12782
+ const crops = transform.aspectCrops;
12783
+ if (!target || !crops || Object.keys(crops).length === 0) return base;
12784
+ let best = null;
12785
+ let bestDistance = Number.POSITIVE_INFINITY;
12786
+ for (const [key, candidate] of Object.entries(crops)) {
12787
+ const candidateAspect = parseAspectRatioValue(candidate.aspect ?? key);
12788
+ if (!candidateAspect) continue;
12789
+ const distance = Math.abs(Math.log(candidateAspect / target));
12790
+ if (distance < bestDistance) {
12791
+ best = candidate;
12792
+ bestDistance = distance;
12793
+ }
12794
+ }
12795
+ if (!best) return base;
12796
+ if (bestDistance > MAX_ASPECT_DISTANCE) return base;
12797
+ return {
12798
+ rect: best.rect ?? base.rect,
12799
+ hotspot: best.hotspot ?? base.hotspot,
12800
+ rotation: best.rotation ?? base.rotation
12801
+ };
12802
+ }
12803
+ function isValidTransformRect(rect) {
12804
+ return isValidTransformRectStructure(rect);
12805
+ }
12806
+ function isExplicitRectCrop(rect, sourceWidth, sourceHeight, epsilon = DEFAULT_RECT_EPSILON) {
12807
+ if (!isValidTransformRect(rect)) return false;
12808
+ if (!(typeof sourceWidth === "number" && Number.isFinite(sourceWidth) && sourceWidth > 0 && typeof sourceHeight === "number" && Number.isFinite(sourceHeight) && sourceHeight > 0)) return true;
12809
+ const [x, y, width, height] = rect;
12810
+ return !(Math.abs(x) <= epsilon && Math.abs(y) <= epsilon && Math.abs(width - sourceWidth) <= epsilon && Math.abs(height - sourceHeight) <= epsilon);
12811
+ }
12812
+ //#endregion
12813
+ //#region ../media-core/src/renderableMediaAsset.ts
12814
+ function attachRenderableMediaAssetFields(media, asset) {
12815
+ return {
12816
+ ...media,
12817
+ assetId: asset.id,
12818
+ id: asset.id,
12819
+ identifier: mediaIdentifierOrFallback(media.identifier, asset.identifier),
12820
+ storageBucket: asset.storageBucket ?? media.storageBucket,
12821
+ storagePath: asset.storagePath ?? media.storagePath,
12822
+ width: asset.width ?? media.width,
12823
+ height: asset.height ?? media.height,
12824
+ mimeType: asset.mimeType ?? media.mimeType
12825
+ };
12826
+ }
12827
+ /**
12828
+ * Preserve an explicit per-usage identifier when one exists. Otherwise prefer
12829
+ * the canonical asset identifier, while keeping intentionally blank legacy
12830
+ * string values from becoming null.
12831
+ */
12832
+ function mediaIdentifierOrFallback(current, fallback) {
12833
+ if (typeof current === "string" && current.trim().length > 0) return current;
12834
+ return fallback ?? (typeof current === "string" ? current : void 0);
12835
+ }
12836
+ //#endregion
12837
+ //#region ../blocks/src/system/runtime/nodes/media.tsx
12838
+ const MAX_RENDER_DIMENSION = 4096;
12839
+ const RELATIVE_IMG_PROXY_BASE_PATH = "/api/img";
12840
+ function getValidSourceDimension(value) {
12841
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
12842
+ }
12843
+ function isExplicitCropProxyEnabled() {
12844
+ const raw = process.env.NEXT_PUBLIC_MEDIA_EXPLICIT_CROP_PROXY_ENABLED ?? process.env.MEDIA_EXPLICIT_CROP_PROXY_ENABLED;
12845
+ if (!raw) return true;
12846
+ const normalized = raw.trim().toLowerCase();
12847
+ return !(normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off");
12848
+ }
12849
+ function getRequestedDimension(value) {
12850
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
12851
+ return Math.min(MAX_RENDER_DIMENSION, Math.max(1, Math.round(value)));
12852
+ }
12853
+ function mapResizeToFit(resize) {
12854
+ if (resize === "contain") return "clip";
12855
+ return "crop";
12856
+ }
12857
+ function shouldUseExplicitRectProxy(value, options, resolvedTransform, sourceWidth, sourceHeight) {
12858
+ if ("original" in options && options.original) return false;
12859
+ if (!isExplicitCropProxyEnabled()) return false;
12860
+ if (!value.assetId) return false;
12861
+ if (typeof resolvedTransform.rotation === "number" && resolvedTransform.rotation !== 0) return true;
12862
+ return isExplicitRectCrop(resolvedTransform.rect, sourceWidth, sourceHeight);
12863
+ }
12864
+ function buildProxyTransformParams(options, resolvedTransform) {
12865
+ const params = {
12866
+ w: "width" in options ? getRequestedDimension(options.width) : void 0,
12867
+ h: "height" in options ? getRequestedDimension(options.height) : void 0,
12868
+ q: "quality" in options ? options.quality : void 0,
12869
+ fit: mapResizeToFit("resize" in options ? options.resize : void 0),
12870
+ autoFormat: true
12871
+ };
12872
+ if (resolvedTransform.rect) params.rect = resolvedTransform.rect;
12873
+ if (resolvedTransform.rotation) params.or = resolvedTransform.rotation;
12874
+ return params;
12875
+ }
12876
+ function buildDirectTransformOptions(options) {
12877
+ if ("original" in options && options.original) return void 0;
12878
+ return {
12879
+ width: "width" in options ? getRequestedDimension(options.width) : void 0,
12880
+ height: "height" in options ? getRequestedDimension(options.height) : void 0,
12881
+ quality: "quality" in options ? options.quality : void 0,
12882
+ resize: "resize" in options ? options.resize : void 0
12883
+ };
12884
+ }
12885
+ function resolveTransformResolution(value, targetAspectRatio) {
12886
+ return {
12887
+ resolvedTransform: resolveMediaTransformForAspect(value.transform, targetAspectRatio),
12888
+ sourceWidth: getValidSourceDimension(value.width),
12889
+ sourceHeight: getValidSourceDimension(value.height)
12890
+ };
12891
+ }
12892
+ function getProxyImageUrl(value, options, transformAspectRatio, resolved) {
12893
+ if (!value.assetId) return void 0;
12894
+ const targetAspect = transformAspectRatio ?? resolveTargetAspectRatio(options);
12895
+ const { resolvedTransform, sourceWidth, sourceHeight } = resolved ?? resolveTransformResolution(value, targetAspect);
12896
+ if (!shouldUseExplicitRectProxy(value, options, resolvedTransform, sourceWidth, sourceHeight)) return void 0;
12897
+ const params = buildProxyTransformParams(options, resolvedTransform);
12898
+ const proxyBasePath = getImageProxyBasePath();
12899
+ const proxyUrl = getImageUrl({ id: unsafeAsMediaAssetId(value.assetId) }, params, proxyBasePath);
12900
+ debugMediaNodeLog("resolve:proxy-img", {
12901
+ assetId: value.assetId,
12902
+ params,
12903
+ proxyBasePath,
12904
+ proxyUrl
12905
+ });
12906
+ return proxyUrl;
12907
+ }
12908
+ function warnForMediaUrlResolution(result) {
12909
+ switch (result.kind) {
12910
+ case "missing-site-context":
12911
+ console.warn(`[blocks] Relative storagePath "${result.storagePath}" requires siteId context. Media URL may be incorrect. Ensure siteId is set via runWithMediaContext or setContextSiteId.`);
12912
+ return;
12913
+ case "missing-supabase-url":
12914
+ console.warn(`[blocks] ${result.message} Media URL may be unavailable. Ensure NEXT_PUBLIC_SUPABASE_URL is configured or pass supabaseUrl in the media context.`);
12915
+ return;
12916
+ case "invalid-storage-path":
12917
+ console.warn(`[blocks] Invalid media storagePath "${result.storagePath}".`);
12918
+ return;
12919
+ case "resolved":
12920
+ case "unsupported-media-type":
12921
+ case "missing-storage-path":
12922
+ case "missing-image-url": return;
12923
+ }
12924
+ }
12925
+ function resolveMediaStorageUrlResult(value, context, transformOptions) {
12926
+ const canonical = resolveMediaWithCanonicalAsset(value, context ?? void 0);
12927
+ if (!canonical.storagePath || canonical.storagePath.length === 0) return {
12928
+ kind: "missing-storage-path",
12929
+ assetId: canonical.assetId,
12930
+ identifier: canonical.identifier
12931
+ };
12932
+ const siteId = context?.siteId ?? getSiteId();
12933
+ const bucket = canonical.storageBucket ?? "media";
12934
+ const siteScopedPath = isSiteScopedDirectImageStoragePath(canonical.storagePath) ? canonical.storagePath : null;
12935
+ let supabaseUrl;
12936
+ try {
12937
+ supabaseUrl = context?.supabaseUrl?.replace(/\/$/, "") ?? getSupabaseUrl();
12938
+ } catch (error) {
12939
+ return {
12940
+ kind: "missing-supabase-url",
12941
+ message: error instanceof Error ? error.message : "Supabase URL is not configured."
12942
+ };
12943
+ }
12944
+ let directUrl;
12945
+ if (siteScopedPath) directUrl = getDirectImageUrlForSiteScopedPath(supabaseUrl, siteScopedPath, bucket, transformOptions);
12946
+ else {
12947
+ const relativePath = parseRelativeStoragePath(canonical.storagePath);
12948
+ if (!relativePath.ok) return {
12949
+ kind: "invalid-storage-path",
12950
+ storagePath: canonical.storagePath
12951
+ };
12952
+ if (!siteId) return {
12953
+ kind: "missing-site-context",
12954
+ storagePath: canonical.storagePath
12955
+ };
12956
+ directUrl = getDirectImageUrlForRelativePath(supabaseUrl, relativePath.value, siteId, bucket, transformOptions);
12957
+ }
12958
+ debugMediaNodeLog("resolve:direct-supabase", {
12959
+ storagePath: canonical.storagePath,
12960
+ siteId,
12961
+ bucket,
12962
+ directUrl,
12963
+ transformOptions
12964
+ });
12965
+ return {
12966
+ kind: "resolved",
12967
+ url: directUrl
12968
+ };
12969
+ }
12970
+ function getDirectImageUrlForValue(value, options, context) {
12971
+ const result = resolveMediaStorageUrlResult(value, context, buildDirectTransformOptions(options));
12972
+ return result.kind === "resolved" ? result.url : void 0;
12973
+ }
12974
+ function resolveStorageImageUrl(value, options, context, transformAspectRatio, resolved) {
12975
+ const proxyUrl = getProxyImageUrl(value, options, transformAspectRatio, resolved);
12976
+ if (proxyUrl) return proxyUrl;
12977
+ const directUrl = getDirectImageUrlForValue(value, options, context);
12978
+ if (directUrl) return directUrl;
12979
+ }
12980
+ /**
12981
+ * Common image size presets.
12982
+ * Use these for consistent sizing across the application.
12983
+ */
12984
+ const ImagePresets = {
12985
+ /** Small thumbnails (200px) - for block previews, lists */
12986
+ thumbnail: {
12987
+ width: 200,
12988
+ quality: 80,
12989
+ resize: "contain"
12990
+ },
12991
+ /** Card-sized images (600px) - for cards, grids */
12992
+ card: {
12993
+ width: 600,
12994
+ quality: 80,
12995
+ resize: "contain"
12996
+ },
12997
+ /** Hero images (1200px) - for above-fold content */
12998
+ hero: {
12999
+ width: 1200,
13000
+ quality: 85,
13001
+ resize: "contain"
13002
+ },
13003
+ /** Full-width images (1920px) - for backgrounds, large displays */
13004
+ full: {
13005
+ width: 1920,
13006
+ quality: 85,
13007
+ resize: "contain"
13008
+ }
13009
+ };
13010
+ /**
13011
+ * Server-side: AsyncLocalStorage for request isolation.
13012
+ * Prevents race conditions when multiple requests are processed concurrently.
13013
+ *
13014
+ * Lazy initialization to avoid issues in environments that don't support async_hooks.
13015
+ */
13016
+ let _serverStorage = null;
13017
+ let _serverStorageChecked = false;
13018
+ function getServerStorage() {
13019
+ if (typeof window !== "undefined") return null;
13020
+ if (_serverStorageChecked) return _serverStorage;
13021
+ _serverStorageChecked = true;
13022
+ try {
13023
+ const globalWithNonWebpackRequire = globalThis;
13024
+ const AsyncLocalStorage = (typeof globalWithNonWebpackRequire.__non_webpack_require__ === "function" ? globalWithNonWebpackRequire.__non_webpack_require__ : new Function("id", "return require(id)"))("async_hooks").AsyncLocalStorage;
13025
+ if (AsyncLocalStorage) {
13026
+ _serverStorage = new AsyncLocalStorage();
13027
+ return _serverStorage;
13028
+ }
13029
+ return null;
13030
+ } catch {
13031
+ console.warn("[blocks] AsyncLocalStorage not available - using module-level state");
13032
+ return null;
13033
+ }
13034
+ }
13035
+ /**
13036
+ * Client-side context storage.
13037
+ * Safe because browsers are single-threaded per tab.
13038
+ *
13039
+ * IMPORTANT: This must be shared across bundles.
13040
+ * SDK preview mode can load multiple independently-bundled entrypoints
13041
+ * (e.g. `@riverbankcms/sdk/rendering` + `@riverbankcms/sdk/preview-next/client`),
13042
+ * which can otherwise each get their own module-scoped singleton.
13043
+ */
13044
+ const RB_MEDIA_CONTEXT_KEY = Symbol.for("riverbankcms.blocks.mediaContext");
13045
+ function getClientContextStore() {
13046
+ const g = globalThis;
13047
+ const existing = g[RB_MEDIA_CONTEXT_KEY];
13048
+ if (existing && typeof existing === "object") return existing;
13049
+ const next = {};
13050
+ g[RB_MEDIA_CONTEXT_KEY] = next;
13051
+ return next;
13052
+ }
13053
+ /**
13054
+ * Get the current media context.
13055
+ * Server-side: Uses AsyncLocalStorage for request isolation.
13056
+ * Client-side: Uses module-level state (safe in single-threaded browser).
13057
+ */
13058
+ function getMediaContext() {
13059
+ const storage = getServerStorage();
13060
+ if (storage) return storage.getStore() ?? {};
13061
+ return getClientContextStore();
13062
+ }
13063
+ function getCurrentResolveImageUrlContext() {
13064
+ const context = getMediaContext();
13065
+ if (!context.supabaseUrl && !context.siteId && !context.mediaAssetIndex) return;
13066
+ return {
13067
+ supabaseUrl: context.supabaseUrl,
13068
+ siteId: context.siteId,
13069
+ mediaAssetIndex: context.mediaAssetIndex
13070
+ };
13071
+ }
13072
+ /**
13073
+ * Get the Supabase URL for constructing direct storage URLs.
13074
+ * Used for serving images directly from Supabase storage CDN.
13075
+ *
13076
+ * Resolution order:
13077
+ * 1. Context URL (set via runWithMediaContext or setContextSupabaseUrl)
13078
+ * 2. Environment variable NEXT_PUBLIC_SUPABASE_URL
13079
+ *
13080
+ * SDK sites receive the URL from the API, so they don't need the env var.
13081
+ * Dashboard and frontend apps use the env var.
13082
+ */
13083
+ function getSupabaseUrl() {
13084
+ const context = getMediaContext();
13085
+ if (context.supabaseUrl) return context.supabaseUrl;
13086
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
13087
+ if (!url) throw new Error("NEXT_PUBLIC_SUPABASE_URL is not configured. Set it to your Supabase project URL (e.g., https://xxx.supabase.co)");
13088
+ return url.replace(/\/$/, "");
13089
+ }
13090
+ function resolveDashboardOrigin(raw) {
13091
+ if (!raw) return void 0;
13092
+ const trimmed = raw.trim();
13093
+ if (!trimmed) return void 0;
13094
+ const withoutApiSuffix = trimmed.replace(/\/+$/, "").replace(/\/api\/?$/, "");
13095
+ const normalized = /^https?:\/\//i.test(withoutApiSuffix) ? withoutApiSuffix : `http://${withoutApiSuffix.replace(/^\/+/, "")}`;
13096
+ try {
13097
+ return new URL(normalized).origin;
13098
+ } catch {
13099
+ return;
13100
+ }
13101
+ }
13102
+ function getImageProxyBasePath() {
13103
+ const dashboardOrigin = resolveDashboardOrigin(process.env.NEXT_PUBLIC_DASHBOARD_URL);
13104
+ if (!dashboardOrigin) return RELATIVE_IMG_PROXY_BASE_PATH;
13105
+ return `${dashboardOrigin}${RELATIVE_IMG_PROXY_BASE_PATH}`;
13106
+ }
13107
+ /**
13108
+ * Get the site ID for constructing storage paths.
13109
+ * Required for relative path resolution.
13110
+ */
13111
+ function getSiteId() {
13112
+ return getMediaContext().siteId;
13113
+ }
13114
+ function getMediaAssetIndex(context) {
13115
+ return context?.mediaAssetIndex ?? getMediaContext().mediaAssetIndex;
13116
+ }
13117
+ function resolveCanonicalMediaAsset(value, context) {
13118
+ const index = getMediaAssetIndex(context);
13119
+ if (!index) return void 0;
13120
+ if (value.assetId) {
13121
+ const hit = index.byId.get(value.assetId);
13122
+ if (hit) return hit;
13123
+ }
13124
+ const identifier = typeof value.identifier === "string" ? value.identifier.trim() : "";
13125
+ if (identifier) {
13126
+ const hit = index.byIdentifier.get(identifier);
13127
+ if (hit) return hit;
13128
+ }
13129
+ }
13130
+ function resolveMediaWithCanonicalAsset(value, context) {
13131
+ const asset = resolveCanonicalMediaAsset(value, context);
13132
+ if (!asset) return value;
13133
+ return attachRenderableMediaAssetFields(value, asset);
13134
+ }
13135
+ const debugMediaNodeLog = (...args) => {
13136
+ if (typeof window === "undefined") return;
13137
+ if (Reflect.get(window, "__DEBUG_MEDIA_NODE__") === true) console.log("[MediaNode]", ...args);
13138
+ };
13139
+ function resolveTargetAspectRatio(size) {
13140
+ if ("original" in size) return void 0;
13141
+ if ("width" in size && "height" in size && typeof size.width === "number" && typeof size.height === "number") return size.width / size.height;
13142
+ }
13143
+ function resolveImageUrlWithContext(value, options, context, transformAspectRatio) {
13144
+ const result = resolveImageUrlWithContextResult(value, options, context, transformAspectRatio);
13145
+ warnForMediaUrlResolution(result);
13146
+ return result.kind === "resolved" ? result.url : void 0;
13147
+ }
13148
+ function resolveImageUrlWithContextResult(value, options, context, transformAspectRatio) {
13149
+ if (value.type !== "image") return {
13150
+ kind: "unsupported-media-type",
13151
+ mediaType: value.type
13152
+ };
13153
+ const url = resolveImageUrlWithContextInternal(value, options, context ?? void 0, transformAspectRatio);
13154
+ if (url) return {
13155
+ kind: "resolved",
13156
+ url
13157
+ };
13158
+ const canonical = resolveMediaWithCanonicalAsset(value, context ?? void 0);
13159
+ if (canonical.storagePath && canonical.storagePath.length > 0) return resolveMediaStorageUrlResult(canonical, context, buildDirectTransformOptions(options));
13160
+ return {
13161
+ kind: "missing-image-url",
13162
+ assetId: canonical.assetId,
13163
+ identifier: canonical.identifier
13164
+ };
13165
+ }
13166
+ function resolveImageUrlWithContextInternal(value, options, context, transformAspectRatio, resolved) {
13167
+ if (value.type !== "image") return void 0;
13168
+ const canonical = resolveMediaWithCanonicalAsset(value, context);
13169
+ debugMediaNodeLog("resolveImageUrl input", {
13170
+ assetId: canonical.assetId,
13171
+ storagePath: canonical.storagePath,
13172
+ src: canonical.src,
13173
+ type: canonical.type,
13174
+ options
13175
+ });
13176
+ if (canonical.storagePath && canonical.storagePath.length > 0) {
13177
+ const resolvedStorageUrl = resolveStorageImageUrl(canonical, options, context, transformAspectRatio, resolved);
13178
+ if (resolvedStorageUrl) return resolvedStorageUrl;
13179
+ }
13180
+ if (typeof canonical.src === "string" && canonical.src.length > 0) {
13181
+ debugMediaNodeLog("resolve:explicit-src", { src: canonical.src });
13182
+ return canonical.src;
13183
+ }
13184
+ const legacyUrl = typeof canonical === "object" && canonical !== null ? Reflect.get(canonical, "url") : void 0;
13185
+ debugMediaNodeLog("resolve:fallback-url", { fallbackUrl: typeof legacyUrl === "string" ? legacyUrl : null });
13186
+ return typeof legacyUrl === "string" ? legacyUrl : void 0;
13187
+ }
13188
+ //#endregion
13189
+ //#region ../blocks/src/primitives/content/components/RichText.tsx
13190
+ function parseDimension(value) {
13191
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.round(value);
13192
+ if (typeof value === "string" && value.trim().length > 0) {
13193
+ const parsed = Number(value);
13194
+ if (Number.isFinite(parsed) && parsed > 0) return Math.round(parsed);
13195
+ }
13196
+ }
13197
+ function normalizeRel(target, rel) {
13198
+ const normalizedRel = rel?.trim();
13199
+ if (target !== "_blank") return normalizedRel && normalizedRel.length > 0 ? normalizedRel : void 0;
13200
+ const tokens = new Set((normalizedRel ?? "").split(/\s+/).map((token) => token.trim().toLowerCase()).filter(Boolean));
13201
+ tokens.add("noopener");
13202
+ tokens.add("noreferrer");
13203
+ return Array.from(tokens).join(" ");
13204
+ }
13205
+ function renderMarks(text, marks, key, linkTarget) {
13206
+ if (!marks || marks.length === 0) return text;
13207
+ return marks.reduce((acc, mark) => {
13208
+ switch (mark.type) {
13209
+ case "bold":
13210
+ case "strong": return /* @__PURE__ */ jsx("strong", { children: acc }, `${key}-b`);
13211
+ case "italic":
13212
+ case "em": return /* @__PURE__ */ jsx("em", { children: acc }, `${key}-i`);
13213
+ case "link": {
13214
+ if (typeof acc === "string" && acc.length === 0) return acc;
13215
+ const attrs = mark.attrs ?? {};
13216
+ const href = sanitizeHref(attrs.href);
13217
+ if (!href) return acc;
13218
+ const storedTarget = typeof attrs.target === "string" && attrs.target.trim().length > 0 ? attrs.target.trim() : void 0;
13219
+ const target = linkTarget === "new-tab" ? "_blank" : storedTarget;
13220
+ return /* @__PURE__ */ jsx("a", {
13221
+ href,
13222
+ target,
13223
+ rel: normalizeRel(target, typeof attrs.rel === "string" ? attrs.rel : void 0),
13224
+ children: acc
13225
+ }, `${key}-l`);
13226
+ }
13227
+ default: return acc;
13228
+ }
13229
+ }, text);
13230
+ }
13231
+ function renderDoc(node, key, imageResolveContext, linkTarget) {
13232
+ if (!node || !node.type) return null;
13233
+ const children = node.content?.map((child, index) => renderDoc(child, `${key}-${index}`, imageResolveContext, linkTarget));
13234
+ switch (node.type) {
13235
+ case "doc": return /* @__PURE__ */ jsx(Fragment, { children });
13236
+ case "paragraph":
13237
+ if (!children && !node.text) return /* @__PURE__ */ jsx("p", {}, key);
13238
+ return /* @__PURE__ */ jsx("p", { children: children ?? renderMarks(node.text ?? "", node.marks, key, linkTarget) }, key);
13239
+ case "heading": {
13240
+ const headingTag = `h${Math.min(Math.max(node.attrs?.level ?? 2, 1), 6)}`;
13241
+ return React.createElement(headingTag, { key }, children);
13242
+ }
13243
+ case "bulletList": return /* @__PURE__ */ jsx("ul", { children }, key);
13244
+ case "orderedList": return /* @__PURE__ */ jsx("ol", { children }, key);
13245
+ case "listItem": return /* @__PURE__ */ jsx("li", { children }, key);
13246
+ case "blockquote": return /* @__PURE__ */ jsx("blockquote", { children }, key);
13247
+ case "horizontalRule": return /* @__PURE__ */ jsx("hr", {}, key);
13248
+ case "hardBreak": return /* @__PURE__ */ jsx("br", {}, key);
13249
+ case "image": {
13250
+ const attrs = node.attrs ?? {};
13251
+ const src = typeof attrs.src === "string" ? attrs.src.trim() : "";
13252
+ const identifier = typeof attrs.identifier === "string" ? attrs.identifier.trim() : "";
13253
+ const alt = typeof attrs.alt === "string" ? attrs.alt : "";
13254
+ const title = typeof attrs.title === "string" && attrs.title.trim().length > 0 ? attrs.title : void 0;
13255
+ const width = parseDimension(attrs.width);
13256
+ const height = parseDimension(attrs.height);
13257
+ const resolvedSrc = src || (identifier ? resolveImageUrlWithContext({
13258
+ type: "image",
13259
+ identifier
13260
+ }, width ? {
13261
+ width,
13262
+ resize: "contain",
13263
+ quality: 85
13264
+ } : height ? {
13265
+ height,
13266
+ resize: "contain",
13267
+ quality: 85
13268
+ } : ImagePresets.full, imageResolveContext ?? void 0) ?? "" : "");
13269
+ if (!resolvedSrc) return null;
13270
+ return /* @__PURE__ */ jsx("img", {
13271
+ src: resolvedSrc,
13272
+ alt,
13273
+ title,
13274
+ width,
13275
+ height,
13276
+ loading: "lazy",
13277
+ decoding: "async"
13278
+ }, key);
13279
+ }
13280
+ case "text": return renderMarks(node.text ?? "", node.marks, key, linkTarget);
13281
+ default: return null;
13282
+ }
13283
+ }
13284
+ const DEFAULT_RICH_TEXT_CLASS = "rb-prose rb-max-w-none";
13285
+ function RichText({ as: Comp = "div", className, doc, path, linkTarget = "stored" }) {
13286
+ const imageResolveContext = getCurrentResolveImageUrlContext() ?? null;
13287
+ const content = doc ? renderDoc(doc, "doc", imageResolveContext, linkTarget) : null;
13288
+ const dataAttributes = path ? { "data-builder-richtext": path } : void 0;
13289
+ return /* @__PURE__ */ jsx(Comp, {
13290
+ className: className?.trim() || DEFAULT_RICH_TEXT_CLASS,
13291
+ ...dataAttributes,
13292
+ children: content
13293
+ });
13294
+ }
13295
+ //#endregion
13296
+ //#region ../blocks/src/system/runtime/shared/components/BookingConsentAgreement.tsx
13297
+ function BookingConsentAgreement({ agreement, showTitle = true, title, titleId }) {
13298
+ return /* @__PURE__ */ jsxs(Fragment, { children: [showTitle ? /* @__PURE__ */ jsxs("div", {
13299
+ id: titleId,
13300
+ className: "form-label",
13301
+ children: [title, /* @__PURE__ */ jsx("span", {
13302
+ className: "required-marker",
13303
+ children: "*"
13304
+ })]
13305
+ }) : null, /* @__PURE__ */ jsx("div", {
13306
+ role: "group",
13307
+ "aria-label": showTitle ? void 0 : title,
13308
+ "aria-labelledby": showTitle ? titleId : void 0,
13309
+ children: /* @__PURE__ */ jsx(RichText, {
13310
+ doc: agreement,
13311
+ linkTarget: "new-tab",
13312
+ className: "rb-prose rb-max-w-none"
13313
+ })
13314
+ })] });
13315
+ }
13316
+ //#endregion
12419
13317
  //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
12420
13318
  function optionCardClassName(isSelected) {
12421
13319
  return `rb-option-card${isSelected ? " rb-option-card--selected" : ""}`;
@@ -12864,7 +13762,11 @@ function DefaultDynamicField({ booking, field }) {
12864
13762
  return /* @__PURE__ */ jsxs("div", {
12865
13763
  className: "rb-space-y-2",
12866
13764
  children: [
12867
- /* @__PURE__ */ jsxs("label", {
13765
+ field.field.type === "booking_requirement_consent" ? /* @__PURE__ */ jsx(BookingConsentAgreement, {
13766
+ agreement: field.field.agreement,
13767
+ title: field.label,
13768
+ titleId: field.labelId
13769
+ }) : /* @__PURE__ */ jsxs("label", {
12868
13770
  id: field.labelId,
12869
13771
  htmlFor: field.inputId,
12870
13772
  className: "form-label",
@@ -12909,7 +13811,7 @@ function DefaultFieldInput({ describedBy, error, errorId, field, onBlur, onChang
12909
13811
  required: field.required,
12910
13812
  ...validationProps
12911
13813
  };
12912
- switch (field.type) {
13814
+ switch (field.field.type) {
12913
13815
  case "textarea": return /* @__PURE__ */ jsx("textarea", {
12914
13816
  ...commonProps,
12915
13817
  value: strValue,
@@ -13023,7 +13925,7 @@ function DefaultFieldInput({ describedBy, error, errorId, field, onBlur, onChang
13023
13925
  className: "form-checkbox rb-mt-0.5"
13024
13926
  }), /* @__PURE__ */ jsx("span", {
13025
13927
  className: "rb-caption",
13026
- children: field.field.placeholder || field.label
13928
+ children: field.field.checkboxLabel
13027
13929
  })]
13028
13930
  });
13029
13931
  case "number": return /* @__PURE__ */ jsx("input", {
@@ -13092,10 +13994,10 @@ function DefaultBookingDiscount({ booking }) {
13092
13994
  children: /* @__PURE__ */ jsxs("p", {
13093
13995
  className: "ab-text",
13094
13996
  children: [
13095
- "Discount available: ",
13096
- booking.discount.autoAppliedDiscount.publicLabel,
13997
+ "Discount available:",
13097
13998
  " ",
13098
- "will apply automatically at checkout."
13999
+ booking.discount.autoAppliedDiscount.publicLabel,
14000
+ " will apply automatically at checkout."
13099
14001
  ]
13100
14002
  })
13101
14003
  }) : null]
@@ -13680,11 +14582,21 @@ function readAmount(value) {
13680
14582
  }
13681
14583
  //#endregion
13682
14584
  //#region ../blocks/src/system/runtime/nodes/event-registration/validation.ts
13683
- function validateFormData(formData) {
14585
+ function createInitialRegistrationFormData() {
14586
+ return {
14587
+ name: "",
14588
+ email: "",
14589
+ phone: "",
14590
+ notes: "",
14591
+ consentAccepted: false
14592
+ };
14593
+ }
14594
+ function validateFormData(formData, requiresConsent = false) {
13684
14595
  const errors = {};
13685
14596
  if (!formData.name.trim()) errors.name = "Name is required";
13686
14597
  if (!formData.email.trim()) errors.email = "Email is required";
13687
14598
  else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) errors.email = "Invalid email address";
14599
+ if (requiresConsent && formData.consentAccepted !== true) errors.consentAccepted = "Please read and accept the agreement before continuing.";
13688
14600
  return errors;
13689
14601
  }
13690
14602
  /**
@@ -14608,6 +15520,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
14608
15520
  name: options.formData.name.trim(),
14609
15521
  phone: options.formData.phone.trim() || void 0,
14610
15522
  notes: options.formData.notes.trim() || void 0,
15523
+ consentAccepted: options.formData.consentAccepted,
14611
15524
  altchaSolution: options.altchaSolution,
14612
15525
  paymentMethod: method,
14613
15526
  customerPassId: method === "pass" ? passId : void 0,
@@ -14825,75 +15738,6 @@ function requiresPaymentStep(params) {
14825
15738
  return !isEntitlementSelection(params.paymentSelection);
14826
15739
  }
14827
15740
  //#endregion
14828
- //#region ../blocks/src/system/manifest/richTextSchema.ts
14829
- /**
14830
- * Shared Rich Text Schema
14831
- *
14832
- * This module provides a single source of truth for TipTap rich text validation
14833
- * across both the block-form editor and the block rendering system.
14834
- *
14835
- * Rich-text fields use one canonical root shape:
14836
- * `{ type: 'doc', content: [...] }`.
14837
- */
14838
- /**
14839
- * TipTap document node schema (recursive structure)
14840
- *
14841
- * Supports the full TipTap document structure including:
14842
- * - type: Node type (e.g., 'doc', 'paragraph', 'text')
14843
- * - content: Nested array of child nodes
14844
- * - text: Text content for text nodes
14845
- * - attrs: Node attributes (e.g., alignment, color)
14846
- * - marks: Text formatting marks (e.g., bold, italic)
14847
- */
14848
- const TipTapMarkSchema = z.object({
14849
- type: z.string().min(1),
14850
- attrs: z.record(z.string(), z.unknown()).optional()
14851
- }).strict();
14852
- const TipTapNodeSchema = z.lazy(() => z.object({
14853
- type: z.string().min(1),
14854
- content: z.array(TipTapNodeSchema).optional(),
14855
- text: z.string().optional(),
14856
- attrs: z.record(z.string(), z.unknown()).optional(),
14857
- marks: z.array(TipTapMarkSchema).optional()
14858
- }).strict().superRefine((node, ctx) => {
14859
- if (node.type === "doc") ctx.addIssue({
14860
- code: "custom",
14861
- path: ["type"],
14862
- message: "Document nodes are only valid at the rich-text root"
14863
- });
14864
- if (node.type === "text") {
14865
- if (node.text === void 0 || node.text.length === 0) ctx.addIssue({
14866
- code: "custom",
14867
- path: ["text"],
14868
- message: "Text nodes require a non-empty text value"
14869
- });
14870
- if (node.content !== void 0) ctx.addIssue({
14871
- code: "custom",
14872
- path: ["content"],
14873
- message: "Text nodes cannot contain child nodes"
14874
- });
14875
- return;
14876
- }
14877
- if (node.text !== void 0) ctx.addIssue({
14878
- code: "custom",
14879
- path: ["text"],
14880
- message: "Only text nodes can contain a text value"
14881
- });
14882
- }));
14883
- /**
14884
- * Canonical schema for a TipTap rich-text document root.
14885
- *
14886
- * Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
14887
- * `content` is required (possibly empty). This is the shape every producer
14888
- * (dummy content, editor output, migrations) must emit, and the shape every
14889
- * non-legacy consumer can rely on.
14890
- *
14891
- */
14892
- const RichTextDocSchema = z.object({
14893
- type: z.literal("doc"),
14894
- content: z.array(TipTapNodeSchema)
14895
- }).strict();
14896
- //#endregion
14897
15741
  //#region ../blocks/src/system/blocks/events/whole-series/pricing.ts
14898
15742
  function isSaleWindowOpen(option, now) {
14899
15743
  if (!option.isActive) return false;
@@ -15116,6 +15960,7 @@ const publicWholeSeriesOfferingSchema = z.object({
15116
15960
  pricing: wholeSeriesPricingSchema,
15117
15961
  currency: z.string(),
15118
15962
  paymentTerms: blockBookingPaymentTermsSchema,
15963
+ consent: blockBookingConsentSchema.nullable(),
15119
15964
  enrollmentCapacity: z.number().nullable(),
15120
15965
  acceptsMemberships: z.boolean(),
15121
15966
  status: z.enum([
@@ -15433,6 +16278,7 @@ const publicEventSchema = z.object({
15433
16278
  timeZone: z.string(),
15434
16279
  pricing: publicEventPricingSchema,
15435
16280
  paymentTerms: blockBookingPaymentTermsSchema,
16281
+ consent: blockBookingConsentSchema.nullable(),
15436
16282
  accessRestriction: accessRestrictionSchema,
15437
16283
  allowedCategoryIds: z.array(z.string()),
15438
16284
  requireGuestDetails: z.boolean().optional(),
@@ -15951,12 +16797,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
15951
16797
  const step = flowState.step;
15952
16798
  const paymentSelection = flowState.paymentSelection;
15953
16799
  const [selectedEvent, setSelectedEvent] = useState(preSelectedEvent ?? null);
15954
- const [formData, setFormData] = useState({
15955
- name: "",
15956
- email: "",
15957
- phone: "",
15958
- notes: ""
15959
- });
16800
+ const [formData, setFormData] = useState(createInitialRegistrationFormData);
15960
16801
  const [errors, setErrors] = useState({});
15961
16802
  const [guests, setGuests] = useState([]);
15962
16803
  const [guestErrors, setGuestErrors] = useState({});
@@ -16254,7 +17095,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
16254
17095
  guests
16255
17096
  ]);
16256
17097
  const validateForm = () => {
16257
- const newErrors = validateFormData(formData);
17098
+ const newErrors = validateFormData(formData, Boolean(currentEvent?.consent));
16258
17099
  setErrors(newErrors);
16259
17100
  const newGuestErrors = {};
16260
17101
  if (requireGuestDetails && guests.length > 0) {
@@ -16443,6 +17284,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
16443
17284
  });
16444
17285
  const handleEventSelect = (event) => {
16445
17286
  setSelectedEvent(event);
17287
+ setFormData((current) => ({
17288
+ ...current,
17289
+ consentAccepted: false
17290
+ }));
16446
17291
  const slidingScale = getSlidingScaleForEvent(event);
16447
17292
  setSelectedSlidingAmountInput(slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput$1(slidingScale.suggestedAmount) : "");
16448
17293
  setSelectedAttendanceMode((event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person");
@@ -17328,6 +18173,7 @@ function validateSubmission(state, setters) {
17328
18173
  message: "Choose an available set of dates to continue."
17329
18174
  };
17330
18175
  const errors = validateFormData$1(state.formData);
18176
+ if (state.selectedRun.consent && state.formData.consentAccepted !== true) errors.consentAccepted = "Please read and accept the agreement before continuing.";
17331
18177
  setters.setErrors(errors);
17332
18178
  if (Object.keys(errors).length > 0) return { kind: "validation-failed" };
17333
18179
  const pricingMessage = getMissingWholeSeriesPricingSelectionMessage(state.pricing);
@@ -17351,6 +18197,7 @@ function registrantPayload(state) {
17351
18197
  name: state.formData.name.trim(),
17352
18198
  phone: state.formData.phone.trim() || void 0,
17353
18199
  formData: { notes: state.formData.notes.trim() || void 0 },
18200
+ consentAccepted: state.formData.consentAccepted,
17354
18201
  altchaSolution: state.altchaSolution,
17355
18202
  claimToken: state.claimToken ?? void 0
17356
18203
  };
@@ -17474,7 +18321,8 @@ const EMPTY_FORM = {
17474
18321
  name: "",
17475
18322
  email: "",
17476
18323
  phone: "",
17477
- notes: ""
18324
+ notes: "",
18325
+ consentAccepted: false
17478
18326
  };
17479
18327
  function useWholeSeriesRegistrationPortal(api) {
17480
18328
  const [formData, setFormData] = useState(EMPTY_FORM);
@@ -18080,6 +18928,7 @@ function toPublicWholeSeriesOffering(run) {
18080
18928
  slug: run.slug,
18081
18929
  description: run.description,
18082
18930
  teamMembers: run.teamMembers ? [...run.teamMembers] : void 0,
18931
+ consent: run.consent,
18083
18932
  enrollmentOpen: run.registrationOpen,
18084
18933
  pricing,
18085
18934
  currency: run.pricing.currency,