@riverbankcms/sdk 0.136.0 → 0.137.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.
- package/CHANGELOG.md +101 -0
- package/dist/{PreviewEditorSidebar-BU5ZlhQR.mjs → PreviewEditorSidebar-Bn6cONPa.mjs} +8 -8
- package/dist/{PreviewEditorUI-9XkHL-bC.mjs → PreviewEditorUI-LAwXZPSx.mjs} +3 -3
- package/dist/{PreviewQueryProvider-wbEpXryy.mjs → PreviewQueryProvider-ztQUdsNo.mjs} +1 -1
- package/dist/{SdkPreviewModeRuntime-CnWmj-2B.mjs → SdkPreviewModeRuntime-BbbGsI_v.mjs} +596 -164
- package/dist/{SiteChromeCustomizeContext-B6d5zkog.mjs → SiteChromeCustomizeContext-BbkNSt5a.mjs} +2888 -1404
- package/dist/_dts/ai/src/contracts/proposals.d.ts +12 -298
- package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +21 -14
- package/dist/_dts/ai/src/designer/themePatch.d.ts +5 -10
- package/dist/_dts/api/src/accessAdmin.d.ts +1 -9
- package/dist/_dts/api/src/accountApi.d.ts +49 -2
- package/dist/_dts/api/src/contentRuntime.d.ts +1 -4
- package/dist/_dts/api/src/domainConnection.d.ts +41 -0
- package/dist/_dts/api/src/endpoints.d.ts +194 -181
- package/dist/_dts/api/src/index.d.ts +4 -3
- package/dist/_dts/api/src/media.d.ts +19 -2
- package/dist/_dts/api/src/sdkContracts.d.ts +2 -15
- package/dist/_dts/api/src/sdkMediaContracts.d.ts +40 -0
- package/dist/_dts/api/src/sdkMediaEndpointDefinitions.d.ts +61 -0
- package/dist/_dts/api/src/sdkThemeWire.d.ts +15 -4
- package/dist/_dts/api/src/siteInfrastructure.d.ts +17 -23
- package/dist/_dts/api/src/siteManagementEndpoints.d.ts +7 -21
- package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +27 -3
- package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +37 -17
- package/dist/_dts/api/src/types.d.ts +1 -1
- package/dist/_dts/blocks/src/PageRenderer.d.ts +2 -2
- package/dist/_dts/blocks/src/index.d.ts +1 -1
- package/dist/_dts/blocks/src/pageRenderAdmission.d.ts +9 -0
- package/dist/_dts/blocks/src/pageRendererBlockSupport.d.ts +75 -0
- package/dist/_dts/blocks/src/pageRendererBoundaries.d.ts +2 -0
- package/dist/_dts/blocks/src/server.d.ts +1 -1
- package/dist/_dts/blocks/src/system/blocks/event-registration.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +2 -1
- package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.d.ts +3 -3
- package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.state.d.ts +34 -22
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/ErrorStep.d.ts +3 -5
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorPresentation.d.ts +5 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +25 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationWizardContext.d.ts +4 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +7 -3
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +5 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useWaitlistClaimRestoration.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/capacity.d.ts +164 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/cta.d.ts +29 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/dates.d.ts +47 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/index.d.ts +14 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/people.d.ts +6 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/pricing.d.ts +31 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/OfferingTitle.d.ts +11 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekAgendaView.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/index.d.ts +3 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/listingLayout.d.ts +23 -0
- package/dist/_dts/commerce/src/purchase/event-registration-errors.d.ts +57 -0
- package/dist/_dts/commerce/src/purchase/index.d.ts +1 -0
- package/dist/_dts/db/src/generated/supabase/database.types.d.ts +8 -40
- package/dist/_dts/db/src/index.d.ts +1 -0
- package/dist/_dts/db/src/utils/postgrestFilter.d.ts +2 -0
- package/dist/_dts/media-core/src/bulkDeletePlanning.d.ts +4 -0
- package/dist/_dts/media-core/src/fileDomain.d.ts +3 -0
- package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +1 -0
- package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +1 -0
- package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +3 -2
- package/dist/_dts/preview-next/src/client/preview/siteChromeBrandPreferences.d.ts +1 -1
- package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlHistoryState.d.ts +2 -0
- package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlSaveRequest.d.ts +51 -0
- package/dist/_dts/preview-next/src/client/preview/siteChromeSavePlan.d.ts +24 -2
- package/dist/_dts/preview-next/src/client/preview/siteStylePreviewPlanner.d.ts +23 -1
- package/dist/_dts/preview-next/src/client/preview/usePersistedHeaderPreferences.d.ts +8 -0
- package/dist/_dts/sdk/src/public-api/contracts.d.ts +2 -1
- package/dist/_dts/sdk/src/version.d.ts +1 -1
- package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
- package/dist/_dts/theme-core/src/gradients/types.d.ts +5 -0
- package/dist/_dts/theme-core/src/header/interactionPalette.d.ts +25 -2
- package/dist/_dts/theme-core/src/mock-themes/all.d.ts +3 -3
- package/dist/_dts/theme-core/src/schema.d.ts +12 -11
- package/dist/_dts/theme-core/src/site-styles/compilationPlan.d.ts +43 -3
- package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +10 -0
- package/dist/_dts/theme-core/src/site-styles/headerBrandPresentation.d.ts +63 -0
- package/dist/_dts/theme-core/src/site-styles/headerBytesComparison.d.ts +30 -0
- package/dist/_dts/theme-core/src/site-styles/headerInteractionPalette.d.ts +50 -1
- package/dist/_dts/theme-core/src/site-styles/headerLayoutCatalog.d.ts +48 -9
- package/dist/_dts/theme-core/src/site-styles/headerPreferences.d.ts +162 -0
- package/dist/_dts/theme-core/src/site-styles/headerPreferencesPersistence.d.ts +73 -0
- package/dist/_dts/theme-core/src/site-styles/headerRecompile.d.ts +118 -0
- package/dist/_dts/theme-core/src/site-styles/headerStyleCompiler.d.ts +10 -1
- package/dist/_dts/theme-core/src/site-styles/headerStyleDefaults.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/headerStyleRecipe.d.ts +7 -0
- package/dist/_dts/theme-core/src/site-styles/index.d.ts +5 -0
- package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/siteChromeBackgrounds.d.ts +20 -1
- package/dist/_dts/theme-core/src/site-styles/siteChromeNavPreferences.d.ts +43 -2
- package/dist/client/{BookingConsentAgreement-DCch21a6.mjs → BookingConsentAgreement--_fUCZe-.mjs} +2 -2
- package/dist/client/{DiscountCodeDisclosure-DIj1T59K.mjs → DiscountCodeDisclosure-BMipSoDJ.mjs} +1 -1
- package/dist/client/{EventCombined.client-BWVNlXTx.mjs → EventCombined.client-CwJOxn7A.mjs} +154 -178
- package/dist/client/{EventListing.client-DVFCDe3i.mjs → EventListing.client-eVwiylPW.mjs} +2 -2
- package/dist/client/{LeafletStaticMap-BY5mIHw9.mjs → LeafletStaticMap-IKenna5u.mjs} +1 -1
- package/dist/client/{accordion-CK29E8rG.mjs → accordion-MGAO3PuE.mjs} +1 -1
- package/dist/client/{bookingForm-DFvnet-N.mjs → bookingForm-CudlQPUu.mjs} +5 -5
- package/dist/client/bookings.mjs +418 -79
- package/dist/client/capacity-BW00PO7s.mjs +271 -0
- package/dist/client/{carousel-BVU5NQlb.mjs → carousel-BrbGJ2Jl.mjs} +1 -1
- package/dist/client/{cart-BNItcZVa.mjs → cart-Bq5eU0xZ.mjs} +3 -3
- package/dist/client/{checkout-Bu6iCdkP.mjs → checkout-BAFVRdrK.mjs} +3 -3
- package/dist/client/client.mjs +2919 -1918
- package/dist/client/{context-BImarOAq.mjs → context-DCkiHVdD.mjs} +8 -5
- package/dist/client/dates-2RPXHQy4.mjs +264 -0
- package/dist/client/{eventCalendar-DS2h-UXD.mjs → eventCalendar-B0O-hPF7.mjs} +10 -17
- package/dist/client/{eventCombined-Drt4Oq9p.mjs → eventCombined-DCkDaoDI.mjs} +3 -3
- package/dist/client/{eventDetails-Bl2vWgTX.mjs → eventDetails-D8GG2dnD.mjs} +5 -3
- package/dist/client/{eventListing-CS7PHY7v.mjs → eventListing-3ssV_bxz.mjs} +3 -3
- package/dist/client/{eventRegistration-D-j8eNy2.mjs → eventRegistration-bPQo3kVw.mjs} +562 -309
- package/dist/client/{form-RMpoLTyr.mjs → form-qMv8RDkk.mjs} +2 -2
- package/dist/client/{headerSection-BYE3Lb3k.mjs → headerSection-CH0Sc5hX.mjs} +2 -2
- package/dist/client/hooks.mjs +289 -32
- package/dist/client/{locationMap-BxSr7Kkh.mjs → locationMap-0_xlBnNI.mjs} +2 -2
- package/dist/client/{newsletterForm-DerbXwFC.mjs → newsletterForm-NUW7m9RI.mjs} +2 -2
- package/dist/client/{productDetail-4SZCLhAf.mjs → productDetail-FBEOM7jW.mjs} +3 -3
- package/dist/client/{productList-D7AP4GNq.mjs → productList-R0OSGhnn.mjs} +3 -3
- package/dist/client/{propsDecoder-pzBeCh14.mjs → propsDecoder-pYvx_Srl.mjs} +88 -380
- package/dist/client/rendering/client.mjs +2616 -1637
- package/dist/client/rendering/islands.mjs +17 -17
- package/dist/client/rendering.mjs +2710 -1702
- package/dist/client/{scrollToBlock-CduAKf8T.mjs → scrollToBlock-ByQv96M2.mjs} +1 -1
- package/dist/client/{shop-Cp5p_kGe.mjs → shop-RUG9DmRE.mjs} +6 -6
- package/dist/client/{shop-commerce-DicXYaYX.mjs → shop-commerce-B76Jz23B.mjs} +5 -5
- package/dist/client/{shop-commerce-store-CGWnpY4K.mjs → shop-commerce-store-QuQNdDsD.mjs} +1 -1
- package/dist/client/{shopShared-BDf9ljkm.mjs → shopShared-vXU1omwJ.mjs} +3 -3
- package/dist/client/{useEventFilters-DeeyGYuk.mjs → useEventFilters-ByuU6HLA.mjs} +33 -18
- package/dist/preview-next/before-render.mjs +79 -51
- package/dist/preview-next/client/runtime.mjs +4 -4
- package/dist/preview-next/keepAlive.mjs +79 -51
- package/dist/preview-next/middleware.mjs +79 -51
- package/dist/{previewAuthStore-DbqBol0V.mjs → previewAuthStore-L_cKW8Sb.mjs} +79 -51
- package/dist/{sdk-runtime-19MO27F0.mjs → sdk-runtime-sUMr5d70.mjs} +2 -2
- package/dist/{separator-DaPLtJ1f.mjs → separator-BIHkie5i.mjs} +1 -1
- package/dist/server/components.mjs +2077 -1274
- package/dist/server/config-validation.mjs +289 -32
- package/dist/server/config.mjs +289 -32
- package/dist/server/data.mjs +289 -32
- package/dist/server/index.mjs +80 -52
- package/dist/server/next.mjs +1822 -1111
- package/dist/server/page-converter.mjs +167 -31
- package/dist/server/prebuild.mjs +1 -1
- package/dist/server/rendering/server.mjs +1988 -1286
- package/dist/server/rendering.mjs +1943 -1182
- package/dist/server/routing.mjs +351 -86
- package/dist/server/server.mjs +293 -128
- package/dist/server/theme-bridge.mjs +52 -8
- package/dist/server/theme.mjs +27 -8
- package/dist/styles/index.css +15 -0
- package/package.json +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCapacity.d.ts +0 -48
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/pricing.d.ts +0 -17
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/utils.d.ts +0 -100
- /package/dist/_dts/blocks/src/system/runtime/nodes/events/{shared → domain}/destination.d.ts +0 -0
package/dist/server/next.mjs
CHANGED
|
@@ -2179,6 +2179,83 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
2179
2179
|
errors: ["validation:invalid_input", "server:internal_error"]
|
|
2180
2180
|
}
|
|
2181
2181
|
});
|
|
2182
|
+
//#endregion
|
|
2183
|
+
//#region ../api/src/sdkMediaEndpointDefinitions.ts
|
|
2184
|
+
const SDK_MEDIA_ENDPOINT_DEFINITIONS = {
|
|
2185
|
+
sdkMediaExists: {
|
|
2186
|
+
path: "/sdk/{siteId}/media/exists",
|
|
2187
|
+
method: "POST",
|
|
2188
|
+
errors: ["validation:invalid_input"],
|
|
2189
|
+
auth: "service",
|
|
2190
|
+
responseKind: "json"
|
|
2191
|
+
},
|
|
2192
|
+
sdkMediaFindByChecksum: {
|
|
2193
|
+
path: "/sdk/{siteId}/media/find-by-checksum",
|
|
2194
|
+
method: "POST",
|
|
2195
|
+
errors: ["server:internal_error"],
|
|
2196
|
+
auth: "service",
|
|
2197
|
+
responseKind: "json"
|
|
2198
|
+
},
|
|
2199
|
+
sdkMediaResolve: {
|
|
2200
|
+
path: "/sdk/{siteId}/media/resolve",
|
|
2201
|
+
method: "POST",
|
|
2202
|
+
errors: ["server:internal_error"],
|
|
2203
|
+
auth: "service",
|
|
2204
|
+
responseKind: "json"
|
|
2205
|
+
},
|
|
2206
|
+
sdkMediaList: {
|
|
2207
|
+
path: "/sdk/{siteId}/media",
|
|
2208
|
+
method: "GET",
|
|
2209
|
+
auth: "service",
|
|
2210
|
+
responseKind: "json"
|
|
2211
|
+
},
|
|
2212
|
+
sdkMediaDownload: {
|
|
2213
|
+
path: "/sdk/{siteId}/media/{identifier}/download",
|
|
2214
|
+
method: "GET",
|
|
2215
|
+
auth: "service",
|
|
2216
|
+
responseKind: "json"
|
|
2217
|
+
},
|
|
2218
|
+
sdkMediaDeletePreview: {
|
|
2219
|
+
path: "/sdk/{siteId}/media/{identifier}/delete-preview",
|
|
2220
|
+
method: "POST",
|
|
2221
|
+
auth: "service",
|
|
2222
|
+
responseKind: "json"
|
|
2223
|
+
},
|
|
2224
|
+
sdkMediaDeleteApply: {
|
|
2225
|
+
path: "/sdk/{siteId}/media/{identifier}",
|
|
2226
|
+
method: "DELETE",
|
|
2227
|
+
auth: "service",
|
|
2228
|
+
responseKind: "json"
|
|
2229
|
+
},
|
|
2230
|
+
sdkMediaUploadUrl: {
|
|
2231
|
+
path: "/sdk/{siteId}/media/upload-url",
|
|
2232
|
+
method: "POST",
|
|
2233
|
+
errors: [
|
|
2234
|
+
"resource:not_found",
|
|
2235
|
+
"validation:invalid_input",
|
|
2236
|
+
"resource:conflict",
|
|
2237
|
+
"validation:payload_too_large",
|
|
2238
|
+
"billing:plan_limit_exceeded",
|
|
2239
|
+
"server:internal_error"
|
|
2240
|
+
],
|
|
2241
|
+
auth: "service",
|
|
2242
|
+
responseKind: "json"
|
|
2243
|
+
},
|
|
2244
|
+
sdkMediaUploadFinalize: {
|
|
2245
|
+
path: "/sdk/{siteId}/media/upload",
|
|
2246
|
+
method: "POST",
|
|
2247
|
+
errors: [
|
|
2248
|
+
"resource:not_found",
|
|
2249
|
+
"validation:invalid_input",
|
|
2250
|
+
"resource:conflict",
|
|
2251
|
+
"validation:payload_too_large",
|
|
2252
|
+
"billing:plan_limit_exceeded",
|
|
2253
|
+
"server:internal_error"
|
|
2254
|
+
],
|
|
2255
|
+
auth: "service",
|
|
2256
|
+
responseKind: "json"
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2182
2259
|
const API_ENDPOINTS$1 = {
|
|
2183
2260
|
...ACCOUNT_ENDPOINT_DEFINITIONS,
|
|
2184
2261
|
...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
|
|
@@ -4319,26 +4396,6 @@ const API_ENDPOINTS$1 = {
|
|
|
4319
4396
|
auth: "public",
|
|
4320
4397
|
responseKind: "json"
|
|
4321
4398
|
},
|
|
4322
|
-
createTheme: {
|
|
4323
|
-
path: "/ai/chat/create-theme",
|
|
4324
|
-
method: "POST",
|
|
4325
|
-
revalidate: 60,
|
|
4326
|
-
tags: ["theme", "ai-theme"],
|
|
4327
|
-
auth: "user",
|
|
4328
|
-
responseKind: "json"
|
|
4329
|
-
},
|
|
4330
|
-
generateThemes: {
|
|
4331
|
-
path: "/theme/generate",
|
|
4332
|
-
method: "POST",
|
|
4333
|
-
errors: [
|
|
4334
|
-
"validation:invalid_input",
|
|
4335
|
-
"resource:not_found",
|
|
4336
|
-
"server:internal_error"
|
|
4337
|
-
],
|
|
4338
|
-
tags: ["theme"],
|
|
4339
|
-
auth: "user",
|
|
4340
|
-
responseKind: "json"
|
|
4341
|
-
},
|
|
4342
4399
|
extractThemeFromInspiration: {
|
|
4343
4400
|
path: "/theme/extract-from-inspiration",
|
|
4344
4401
|
method: "POST",
|
|
@@ -4512,7 +4569,7 @@ const API_ENDPOINTS$1 = {
|
|
|
4512
4569
|
method: "DELETE",
|
|
4513
4570
|
tags: ["media", "media-{assetId}"],
|
|
4514
4571
|
auth: "user",
|
|
4515
|
-
responseKind: "
|
|
4572
|
+
responseKind: "json"
|
|
4516
4573
|
},
|
|
4517
4574
|
mediaBulkDelete: {
|
|
4518
4575
|
path: "/media/bulk-delete",
|
|
@@ -7098,42 +7155,13 @@ const API_ENDPOINTS$1 = {
|
|
|
7098
7155
|
auth: "user",
|
|
7099
7156
|
responseKind: "json"
|
|
7100
7157
|
},
|
|
7158
|
+
...SDK_MEDIA_ENDPOINT_DEFINITIONS,
|
|
7101
7159
|
sdkIdentifiersBackfill: {
|
|
7102
7160
|
path: "/sdk/{siteId}/identifiers/backfill",
|
|
7103
7161
|
method: "POST",
|
|
7104
7162
|
auth: "service",
|
|
7105
7163
|
responseKind: "json"
|
|
7106
7164
|
},
|
|
7107
|
-
sdkMediaExists: {
|
|
7108
|
-
path: "/sdk/{siteId}/media/exists",
|
|
7109
|
-
method: "POST",
|
|
7110
|
-
auth: "service",
|
|
7111
|
-
responseKind: "json"
|
|
7112
|
-
},
|
|
7113
|
-
sdkMediaFindByChecksum: {
|
|
7114
|
-
path: "/sdk/{siteId}/media/find-by-checksum",
|
|
7115
|
-
method: "POST",
|
|
7116
|
-
auth: "service",
|
|
7117
|
-
responseKind: "json"
|
|
7118
|
-
},
|
|
7119
|
-
sdkMediaResolve: {
|
|
7120
|
-
path: "/sdk/{siteId}/media/resolve",
|
|
7121
|
-
method: "POST",
|
|
7122
|
-
auth: "service",
|
|
7123
|
-
responseKind: "json"
|
|
7124
|
-
},
|
|
7125
|
-
sdkMediaUploadUrl: {
|
|
7126
|
-
path: "/sdk/{siteId}/media/upload-url",
|
|
7127
|
-
method: "POST",
|
|
7128
|
-
auth: "service",
|
|
7129
|
-
responseKind: "json"
|
|
7130
|
-
},
|
|
7131
|
-
sdkMediaUploadFinalize: {
|
|
7132
|
-
path: "/sdk/{siteId}/media/upload",
|
|
7133
|
-
method: "POST",
|
|
7134
|
-
auth: "service",
|
|
7135
|
-
responseKind: "json"
|
|
7136
|
-
},
|
|
7137
7165
|
sdkUpdateWorkflowRunStatus: {
|
|
7138
7166
|
path: "/sdk/{siteId}/workflows/runs/{runId}/status",
|
|
7139
7167
|
method: "POST",
|
|
@@ -16987,6 +17015,11 @@ const gradientConfigSchema = z$1.object({
|
|
|
16987
17015
|
position: z$1.string().optional(),
|
|
16988
17016
|
stops: z$1.array(gradientStopSchema).min(2)
|
|
16989
17017
|
});
|
|
17018
|
+
const gradientsSchema = z$1.object({
|
|
17019
|
+
button: z$1.string().min(1).nullable().optional(),
|
|
17020
|
+
hero: z$1.string().min(1).nullable().optional(),
|
|
17021
|
+
background: z$1.string().min(1).nullable().optional()
|
|
17022
|
+
});
|
|
16990
17023
|
//#endregion
|
|
16991
17024
|
//#region ../theme-core/src/interactive/baseSchema.ts
|
|
16992
17025
|
/**
|
|
@@ -17987,11 +18020,30 @@ const headerContainerSchema = z$1.object({
|
|
|
17987
18020
|
tint: z$1.string().nullable().optional(),
|
|
17988
18021
|
opacity: z$1.number().min(0).max(1).default(.12)
|
|
17989
18022
|
}).optional();
|
|
17990
|
-
|
|
18023
|
+
//#endregion
|
|
18024
|
+
//#region ../theme-core/src/header/interactionPalette.ts
|
|
18025
|
+
/**
|
|
18026
|
+
* The palettes that fill the nav link with a colour on hover and focus. Each
|
|
18027
|
+
* one names a background/foreground token pair.
|
|
18028
|
+
*/
|
|
18029
|
+
const headerInteractionPillPaletteValues = [
|
|
17991
18030
|
"primary",
|
|
17992
18031
|
"secondary",
|
|
17993
18032
|
"accent"
|
|
17994
|
-
]
|
|
18033
|
+
];
|
|
18034
|
+
/**
|
|
18035
|
+
* "No pill": the nav link keeps whatever hover treatment its recipe compiled,
|
|
18036
|
+
* and the palette step writes nothing at all (#1318 Phase 4c decision 2).
|
|
18037
|
+
*
|
|
18038
|
+
* It exists because "a colour change on hover, no filled background" was not
|
|
18039
|
+
* expressible in the vocabulary, and it is what
|
|
18040
|
+
* `anna-raven-therapy-and-yoga` and `kirtan-lucy` have rendered since before
|
|
18041
|
+
* the palette existed. Without it those two rows can only be classified as a
|
|
18042
|
+
* conscious visual change.
|
|
18043
|
+
*/
|
|
18044
|
+
const headerInteractionQuietPalette = "quiet";
|
|
18045
|
+
const headerInteractionPaletteValues = [...headerInteractionPillPaletteValues, headerInteractionQuietPalette];
|
|
18046
|
+
const headerInteractionPaletteSchema = z$1.enum(headerInteractionPaletteValues);
|
|
17995
18047
|
//#endregion
|
|
17996
18048
|
//#region ../theme-core/src/boxBackgroundSchema.ts
|
|
17997
18049
|
const boxBackgroundOverlaySchema = z$1.preprocess(normalizeAuthoringOpacity, z$1.discriminatedUnion("type", [
|
|
@@ -18373,11 +18425,12 @@ const inputStyle = z$1.object({
|
|
|
18373
18425
|
]),
|
|
18374
18426
|
label: z$1.enum(["inside", "above"])
|
|
18375
18427
|
});
|
|
18376
|
-
const
|
|
18428
|
+
const headerPositioningValues = [
|
|
18377
18429
|
"static",
|
|
18378
18430
|
"sticky",
|
|
18379
18431
|
"fixed"
|
|
18380
|
-
]
|
|
18432
|
+
];
|
|
18433
|
+
const headerPositioning = z$1.enum(headerPositioningValues);
|
|
18381
18434
|
const headerNavStyle = z$1.enum([
|
|
18382
18435
|
"minimal",
|
|
18383
18436
|
"underline",
|
|
@@ -18512,7 +18565,7 @@ const headerSchema = z$1.object({
|
|
|
18512
18565
|
variant: headerVariantWithLegacyAliasSchema,
|
|
18513
18566
|
positioning: headerPositioning,
|
|
18514
18567
|
shrinkOnScroll: z$1.boolean(),
|
|
18515
|
-
maxWidth: headerMaxWidth,
|
|
18568
|
+
maxWidth: headerMaxWidth.default("container"),
|
|
18516
18569
|
scrollTreatment: headerScrollTreatmentSchema,
|
|
18517
18570
|
background: headerBackgroundSchema.default({
|
|
18518
18571
|
type: "color",
|
|
@@ -18711,11 +18764,6 @@ const bodyTextSchema = z$1.object({ intro: z$1.object({
|
|
|
18711
18764
|
paragraphSize: bodyTextIntroTypographySizeSchema.optional(),
|
|
18712
18765
|
headingSize: bodyTextIntroTypographySizeSchema.optional()
|
|
18713
18766
|
}).optional() }).optional();
|
|
18714
|
-
const gradientsSchema = z$1.object({
|
|
18715
|
-
button: z$1.string().min(1).nullable().optional(),
|
|
18716
|
-
hero: z$1.string().min(1).nullable().optional(),
|
|
18717
|
-
background: z$1.string().min(1).nullable().optional()
|
|
18718
|
-
});
|
|
18719
18767
|
/**
|
|
18720
18768
|
* Testimonial quote-mark treatment, owned by the site style (analogous to the
|
|
18721
18769
|
* accordion icon). `style` is the coarse selector; the optional fields fine-tune
|
|
@@ -21759,12 +21807,12 @@ function allGroupChildPlans(plan) {
|
|
|
21759
21807
|
}
|
|
21760
21808
|
function selectedGroupChildPlans(plan, value) {
|
|
21761
21809
|
if (plan.childSelection.kind === "all") return plan.childSelection.fields;
|
|
21762
|
-
if (!isPlainObject$
|
|
21810
|
+
if (!isPlainObject$7(value)) return [];
|
|
21763
21811
|
const selectedType = value[plan.childSelection.activeTabField];
|
|
21764
21812
|
return plan.childSelection.tabs.find((tab) => tab.typeId === selectedType)?.fields ?? [];
|
|
21765
21813
|
}
|
|
21766
21814
|
function canonicalizeObjectForWrite(plans, value) {
|
|
21767
|
-
if (!isPlainObject$
|
|
21815
|
+
if (!isPlainObject$7(value)) return value;
|
|
21768
21816
|
const canonical = { ...value };
|
|
21769
21817
|
for (const plan of plans) {
|
|
21770
21818
|
if (!(plan.fieldId in canonical)) continue;
|
|
@@ -21788,7 +21836,7 @@ function canonicalizeFieldValue(plan, value) {
|
|
|
21788
21836
|
}
|
|
21789
21837
|
}
|
|
21790
21838
|
function canonicalizeMedia(value) {
|
|
21791
|
-
if (!isPlainObject$
|
|
21839
|
+
if (!isPlainObject$7(value)) return value;
|
|
21792
21840
|
const canonical = { ...value };
|
|
21793
21841
|
if (mediaIdentifierSchema.safeParse(canonical.identifier).success) deleteEnvSpecificKeys(canonical);
|
|
21794
21842
|
return canonical;
|
|
@@ -21796,13 +21844,13 @@ function canonicalizeMedia(value) {
|
|
|
21796
21844
|
function canonicalizeRepeater(plan, value) {
|
|
21797
21845
|
if (!isUnknownArray$3(value)) return value;
|
|
21798
21846
|
return value.map((item) => {
|
|
21799
|
-
if (!isPlainObject$
|
|
21847
|
+
if (!isPlainObject$7(item)) return item;
|
|
21800
21848
|
const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
|
|
21801
21849
|
return childPlans ? canonicalizeObjectForWrite(childPlans, item) : item;
|
|
21802
21850
|
});
|
|
21803
21851
|
}
|
|
21804
21852
|
function canonicalizeDiscriminatedGroup(plan, value) {
|
|
21805
|
-
if (!isPlainObject$
|
|
21853
|
+
if (!isPlainObject$7(value) || plan.childSelection.kind !== "discriminatedTabs") return value;
|
|
21806
21854
|
const selection = plan.childSelection;
|
|
21807
21855
|
const selectedPlans = selectedGroupChildPlans(plan, value);
|
|
21808
21856
|
const selectedIds = rootFieldIds(selectedPlans);
|
|
@@ -21817,10 +21865,10 @@ function rootFieldIds(plans) {
|
|
|
21817
21865
|
}
|
|
21818
21866
|
function omitUndefined(value) {
|
|
21819
21867
|
if (isUnknownArray$3(value)) return value.map(omitUndefined);
|
|
21820
|
-
if (!isPlainObject$
|
|
21868
|
+
if (!isPlainObject$7(value)) return value;
|
|
21821
21869
|
return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0).map(([key, field]) => [key, omitUndefined(field)]));
|
|
21822
21870
|
}
|
|
21823
|
-
function isPlainObject$
|
|
21871
|
+
function isPlainObject$7(value) {
|
|
21824
21872
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21825
21873
|
}
|
|
21826
21874
|
function isUnknownArray$3(value) {
|
|
@@ -22216,7 +22264,7 @@ function validateNormalizedFieldValue(plan, value, ctx) {
|
|
|
22216
22264
|
case "select": return validateSelectPlan(plan, value);
|
|
22217
22265
|
case "richText": return isValidRichTextDoc(value) ? [] : [issue(plan, "invalidRichText")];
|
|
22218
22266
|
case "inlineText": return validateInlineTextPlan(plan, value);
|
|
22219
|
-
case "media": return isPlainObject$
|
|
22267
|
+
case "media": return isPlainObject$6(value) && mediaIdentifierSchema.safeParse(value.identifier).success && (value.type === void 0 || typeof value.type === "string" && plan.allowedMediaTypes.includes(value.type)) ? [] : [issue(plan, "invalidMedia")];
|
|
22220
22268
|
case "link": return linkSchema.safeParse(value).success ? [] : [issue(plan, "invalidLink")];
|
|
22221
22269
|
case "repeater": return validateRepeaterPlan(plan, value, ctx);
|
|
22222
22270
|
case "group": return validateGroupPlan(plan, value, ctx);
|
|
@@ -22309,7 +22357,7 @@ function validateRepeaterPlan(plan, value, ctx) {
|
|
|
22309
22357
|
}), ...value.flatMap((item, index) => validateRepeaterItem(plan, item, index, ctx))];
|
|
22310
22358
|
}
|
|
22311
22359
|
function validateRepeaterItem(plan, item, index, ctx) {
|
|
22312
|
-
if (!isPlainObject$
|
|
22360
|
+
if (!isPlainObject$6(item)) return [{
|
|
22313
22361
|
kind: "wrongType",
|
|
22314
22362
|
path: [...plan.path, index],
|
|
22315
22363
|
fieldId: plan.fieldId,
|
|
@@ -22335,7 +22383,7 @@ function validateRepeaterItem(plan, item, index, ctx) {
|
|
|
22335
22383
|
});
|
|
22336
22384
|
}
|
|
22337
22385
|
function validateGroupPlan(plan, value, ctx) {
|
|
22338
|
-
if (!isPlainObject$
|
|
22386
|
+
if (!isPlainObject$6(value)) return [issue(plan, "wrongType", { expected: "an object" })];
|
|
22339
22387
|
if (plan.childSelection.kind === "discriminatedTabs") {
|
|
22340
22388
|
const selectedType = value[plan.childSelection.activeTabField];
|
|
22341
22389
|
if (selectedType !== void 0 && selectedType !== null && !plan.childSelection.tabs.some((tab) => tab.typeId === selectedType)) return [{
|
|
@@ -22638,7 +22686,7 @@ function isEmptyValue$1(value) {
|
|
|
22638
22686
|
if (isEmptyRichTextDoc(value)) return true;
|
|
22639
22687
|
return false;
|
|
22640
22688
|
}
|
|
22641
|
-
function isPlainObject$
|
|
22689
|
+
function isPlainObject$6(value) {
|
|
22642
22690
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
22643
22691
|
}
|
|
22644
22692
|
function isUnknownArray$2(value) {
|
|
@@ -22648,7 +22696,7 @@ function isValidRichTextDoc(value) {
|
|
|
22648
22696
|
return isRichTextDoc(value);
|
|
22649
22697
|
}
|
|
22650
22698
|
function isEmptyRichTextDoc(value) {
|
|
22651
|
-
if (!isPlainObject$
|
|
22699
|
+
if (!isPlainObject$6(value)) return false;
|
|
22652
22700
|
if (value.type !== "doc") return false;
|
|
22653
22701
|
const content = value.content;
|
|
22654
22702
|
if (!Array.isArray(content) || content.length === 0) return true;
|
|
@@ -22700,26 +22748,6 @@ function compileManifestContentContract(manifestOrFields) {
|
|
|
22700
22748
|
findNonPortableMediaPaths: (content) => findNonPortableMediaPaths(plans, content)
|
|
22701
22749
|
}) });
|
|
22702
22750
|
}
|
|
22703
|
-
/** Strictly admits already-persisted content without repairing it. */
|
|
22704
|
-
function admitStoredManifestContent(contract, content) {
|
|
22705
|
-
const state = contract[compiledManifestContentContract];
|
|
22706
|
-
const nonPortablePaths = state.findNonPortableMediaPaths(content);
|
|
22707
|
-
if (nonPortablePaths.length > 0) return {
|
|
22708
|
-
ok: false,
|
|
22709
|
-
error: {
|
|
22710
|
-
kind: "non_portable_media",
|
|
22711
|
-
paths: nonPortablePaths
|
|
22712
|
-
}
|
|
22713
|
-
};
|
|
22714
|
-
const parsed = state.strictStoredSchema.safeParse(content);
|
|
22715
|
-
return parsed.success ? {
|
|
22716
|
-
ok: true,
|
|
22717
|
-
value: content
|
|
22718
|
-
} : {
|
|
22719
|
-
ok: false,
|
|
22720
|
-
error: zodAdmissionError(parsed.error)
|
|
22721
|
-
};
|
|
22722
|
-
}
|
|
22723
22751
|
/** Validates raw editor state without granting a stored-content proof. */
|
|
22724
22752
|
function validateManifestDraft(contract, data) {
|
|
22725
22753
|
const parsed = contract[compiledManifestContentContract].draftSchema.safeParse(data);
|
|
@@ -22737,21 +22765,6 @@ function validateManifestDraft(contract, data) {
|
|
|
22737
22765
|
};
|
|
22738
22766
|
}
|
|
22739
22767
|
/**
|
|
22740
|
-
* Applies a pure runtime projection to admitted stored content and validates
|
|
22741
|
-
* the resulting render shape before granting the renderable proof.
|
|
22742
|
-
*/
|
|
22743
|
-
function deriveRenderableManifestContent(contract, stored, project) {
|
|
22744
|
-
const projected = project(stored);
|
|
22745
|
-
const parsed = contract[compiledManifestContentContract].renderSchema.safeParse(projected);
|
|
22746
|
-
return parsed.success ? {
|
|
22747
|
-
ok: true,
|
|
22748
|
-
value: projected
|
|
22749
|
-
} : {
|
|
22750
|
-
ok: false,
|
|
22751
|
-
error: zodAdmissionError(parsed.error)
|
|
22752
|
-
};
|
|
22753
|
-
}
|
|
22754
|
-
/**
|
|
22755
22768
|
* Re-admits renderable content after a transport boundary has erased its
|
|
22756
22769
|
* compile-time proof. Unlike write admission, this never canonicalizes or
|
|
22757
22770
|
* grants a stored proof, so hydrated media cannot flow back to persistence.
|
|
@@ -22907,6 +22920,23 @@ function defineLookOptionGroup(group) {
|
|
|
22907
22920
|
defaultOption(group);
|
|
22908
22921
|
return group;
|
|
22909
22922
|
}
|
|
22923
|
+
function resolveLookOption(group, requestedOptionId) {
|
|
22924
|
+
const fallback = defaultOption(group);
|
|
22925
|
+
if (requestedOptionId === null || requestedOptionId === void 0) return {
|
|
22926
|
+
kind: "defaulted",
|
|
22927
|
+
option: fallback
|
|
22928
|
+
};
|
|
22929
|
+
const selected = group.options.find((option) => option.id === requestedOptionId);
|
|
22930
|
+
return selected ? {
|
|
22931
|
+
kind: "selected",
|
|
22932
|
+
option: selected
|
|
22933
|
+
} : {
|
|
22934
|
+
kind: "repaired",
|
|
22935
|
+
requestedOptionId,
|
|
22936
|
+
option: fallback,
|
|
22937
|
+
reason: "unsupported-option"
|
|
22938
|
+
};
|
|
22939
|
+
}
|
|
22910
22940
|
function defaultOption(group) {
|
|
22911
22941
|
const option = group.options.find((candidate) => candidate.id === group.defaultOptionId);
|
|
22912
22942
|
if (option) return option;
|
|
@@ -23797,6 +23827,12 @@ function cardRoleFromLegacyVocabulary(value) {
|
|
|
23797
23827
|
*/
|
|
23798
23828
|
const cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
|
|
23799
23829
|
//#endregion
|
|
23830
|
+
//#region ../theme-core/src/site-styles/compilationPlan.ts
|
|
23831
|
+
const defaultHeaderBrandPreferences = {
|
|
23832
|
+
brandDisplay: "auto",
|
|
23833
|
+
logoSize: "auto"
|
|
23834
|
+
};
|
|
23835
|
+
//#endregion
|
|
23800
23836
|
//#region ../theme-core/src/site-styles/sectionTransitions.ts
|
|
23801
23837
|
function boundaryRuntimeMetrics(family, size, heightExpression) {
|
|
23802
23838
|
return {
|
|
@@ -26178,6 +26214,12 @@ const overlayLogoAccommodations = [
|
|
|
26178
26214
|
inlineMarkWithTitleLogoAccommodation,
|
|
26179
26215
|
unbackedOverlayLogoAccommodation
|
|
26180
26216
|
];
|
|
26217
|
+
const allCuratedLogoAccommodations = [
|
|
26218
|
+
...inlineClassicLogoAccommodations,
|
|
26219
|
+
...centeredLogoAccommodations,
|
|
26220
|
+
...overlayLogoAccommodations
|
|
26221
|
+
];
|
|
26222
|
+
new Set(allCuratedLogoAccommodations.filter((option) => option.providesBacking === true).map((option) => option.pattern));
|
|
26181
26223
|
const headerLayoutCatalog = defineExactCatalog()([
|
|
26182
26224
|
{
|
|
26183
26225
|
id: asHeaderLayoutId("classic"),
|
|
@@ -26327,9 +26369,50 @@ defineLookOptionGroup({
|
|
|
26327
26369
|
{
|
|
26328
26370
|
id: "secondary",
|
|
26329
26371
|
label: "Secondary"
|
|
26372
|
+
},
|
|
26373
|
+
{
|
|
26374
|
+
id: headerInteractionQuietPalette,
|
|
26375
|
+
label: "Text only"
|
|
26330
26376
|
}
|
|
26331
26377
|
]
|
|
26332
26378
|
});
|
|
26379
|
+
/**
|
|
26380
|
+
* Key-sorted serialisation, because one side of the comparison is built here
|
|
26381
|
+
* and the other comes back out of `jsonb` in whatever order it was stored.
|
|
26382
|
+
* `JSON.stringify` alone reported two identical effect lists as different,
|
|
26383
|
+
* which is the same key-order trap the `logoText` derivation hit in Phase 4a.
|
|
26384
|
+
*/
|
|
26385
|
+
function canonicalJson(value) {
|
|
26386
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
26387
|
+
if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, member]) => member !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`).join(",")}}`;
|
|
26388
|
+
return value === void 0 ? "undefined" : JSON.stringify(value);
|
|
26389
|
+
}
|
|
26390
|
+
//#endregion
|
|
26391
|
+
//#region ../theme-core/src/site-styles/headerNavStyles.ts
|
|
26392
|
+
function applyNavTypography(style, typography) {
|
|
26393
|
+
switch (typography) {
|
|
26394
|
+
case "plain": return style;
|
|
26395
|
+
case "heading": return {
|
|
26396
|
+
...style,
|
|
26397
|
+
typography: "heading",
|
|
26398
|
+
fontWeight: "medium",
|
|
26399
|
+
letterSpacing: "wide"
|
|
26400
|
+
};
|
|
26401
|
+
case "quiet-caps": return {
|
|
26402
|
+
...style,
|
|
26403
|
+
textTransform: "uppercase",
|
|
26404
|
+
textSize: "sm",
|
|
26405
|
+
letterSpacing: "wide"
|
|
26406
|
+
};
|
|
26407
|
+
case "brand-caps": return {
|
|
26408
|
+
...style,
|
|
26409
|
+
textTransform: "uppercase",
|
|
26410
|
+
textSize: "sm",
|
|
26411
|
+
letterSpacing: "wider"
|
|
26412
|
+
};
|
|
26413
|
+
default: return assertNever$19(typography, "Unhandled header nav typography");
|
|
26414
|
+
}
|
|
26415
|
+
}
|
|
26333
26416
|
function asFooterLookId(value) {
|
|
26334
26417
|
return value;
|
|
26335
26418
|
}
|
|
@@ -27038,6 +27121,37 @@ function uniformSectionForeground(token) {
|
|
|
27038
27121
|
supportingColor: token
|
|
27039
27122
|
};
|
|
27040
27123
|
}
|
|
27124
|
+
function compileTransitionRuntime(input) {
|
|
27125
|
+
return {
|
|
27126
|
+
treatment: input.treatment,
|
|
27127
|
+
from: input.from,
|
|
27128
|
+
to: input.to,
|
|
27129
|
+
cssVars: {
|
|
27130
|
+
from: transitionEndpointCssValue(input.from),
|
|
27131
|
+
to: transitionEndpointCssValue(input.to)
|
|
27132
|
+
}
|
|
27133
|
+
};
|
|
27134
|
+
}
|
|
27135
|
+
function transitionEndpointCssValue(endpoint) {
|
|
27136
|
+
switch (endpoint.kind) {
|
|
27137
|
+
case "color-token": return themeColorTokenCssValue(endpoint.token);
|
|
27138
|
+
case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
|
|
27139
|
+
default: return assertNever$19(endpoint);
|
|
27140
|
+
}
|
|
27141
|
+
}
|
|
27142
|
+
function tokenForTransitionSurface(surface) {
|
|
27143
|
+
switch (surface) {
|
|
27144
|
+
case "base":
|
|
27145
|
+
case "hero-image": return "background";
|
|
27146
|
+
case "muted-band": return "muted";
|
|
27147
|
+
case "accent-band": return "accent";
|
|
27148
|
+
case "brand-depth-band": return "primary-800";
|
|
27149
|
+
default: return assertNever$19(surface);
|
|
27150
|
+
}
|
|
27151
|
+
}
|
|
27152
|
+
function themeColorTokenCssValue(token) {
|
|
27153
|
+
return `rgb(var(--tb-${token}))`;
|
|
27154
|
+
}
|
|
27041
27155
|
//#endregion
|
|
27042
27156
|
//#region ../theme-core/src/site-styles/curatedSiteStyles.ts
|
|
27043
27157
|
const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
|
|
@@ -28745,6 +28859,142 @@ const blockDesignFallback = {
|
|
|
28745
28859
|
};
|
|
28746
28860
|
blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
|
|
28747
28861
|
//#endregion
|
|
28862
|
+
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
28863
|
+
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
28864
|
+
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
28865
|
+
siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
|
|
28866
|
+
function parseSiteChromeBackgroundColorToken(value) {
|
|
28867
|
+
const token = parseBackgroundAppearanceColorToken(value);
|
|
28868
|
+
return token ? token : null;
|
|
28869
|
+
}
|
|
28870
|
+
function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
28871
|
+
return tokenNames.flatMap((name) => {
|
|
28872
|
+
const token = parseSiteChromeBackgroundColorToken(name);
|
|
28873
|
+
return token ? [token] : [];
|
|
28874
|
+
});
|
|
28875
|
+
}
|
|
28876
|
+
//#endregion
|
|
28877
|
+
//#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
|
|
28878
|
+
const headerNavTypographyPreferenceValues = [
|
|
28879
|
+
"inherit",
|
|
28880
|
+
"body",
|
|
28881
|
+
"heading"
|
|
28882
|
+
];
|
|
28883
|
+
const headerNavFontSizePreferenceValues = [
|
|
28884
|
+
"inherit",
|
|
28885
|
+
"sm",
|
|
28886
|
+
"base",
|
|
28887
|
+
"lg",
|
|
28888
|
+
"xl"
|
|
28889
|
+
];
|
|
28890
|
+
const defaultHeaderNavPreferences = {
|
|
28891
|
+
typography: "inherit",
|
|
28892
|
+
fontSize: "inherit",
|
|
28893
|
+
interactionPalette: "recommended"
|
|
28894
|
+
};
|
|
28895
|
+
/**
|
|
28896
|
+
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
28897
|
+
* nav link style. This is where the precedence contract lands:
|
|
28898
|
+
*
|
|
28899
|
+
* - The nav typography TREATMENT (case/tracking identity: plain / heading /
|
|
28900
|
+
* quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
|
|
28901
|
+
* baked into `style` before this call (see `applyNavTypography` in
|
|
28902
|
+
* headerLayoutStyles.ts).
|
|
28903
|
+
* - Each treatment resolves through a DEFAULT font role: `heading` resolves
|
|
28904
|
+
* through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
|
|
28905
|
+
* through the body role.
|
|
28906
|
+
* - The user's `navFontRole` (surfaced here as `preferences.typography`) only
|
|
28907
|
+
* overrides which font ROLE the treatment resolves through. It never changes
|
|
28908
|
+
* the treatment. `'inherit'` keeps the treatment's default role.
|
|
28909
|
+
*/
|
|
28910
|
+
function mergeHeaderNavTypographyPreference(style, preferences) {
|
|
28911
|
+
return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
|
|
28912
|
+
...style,
|
|
28913
|
+
...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
|
|
28914
|
+
...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
|
|
28915
|
+
};
|
|
28916
|
+
}
|
|
28917
|
+
function readHeaderNavFontSizePreference(value) {
|
|
28918
|
+
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
28919
|
+
}
|
|
28920
|
+
//#endregion
|
|
28921
|
+
//#region ../theme-core/src/site-styles/headerBrandPresentation.ts
|
|
28922
|
+
/**
|
|
28923
|
+
* Whether the header wears a brand presentation at all (#1318 Phase 4c).
|
|
28924
|
+
*
|
|
28925
|
+
* `applyHeaderBrandPresentation` is unconditional today: given a logo context
|
|
28926
|
+
* it always writes a `logo` object carrying a resolved display, size and
|
|
28927
|
+
* accommodation. That makes "this header has no brand presentation block" —
|
|
28928
|
+
* which is what several live sites persist, including one whose site *does*
|
|
28929
|
+
* have a logo asset — a state the preference record could not express, so a
|
|
28930
|
+
* recompile would silently give the site a logo treatment it has never
|
|
28931
|
+
* rendered.
|
|
28932
|
+
*
|
|
28933
|
+
* This is the third field in this phase with that shape, after the interaction
|
|
28934
|
+
* palette and the nav text size: the persisted bytes carry intent the reader
|
|
28935
|
+
* cannot say, so the recompile "corrects" it. The fix is the same one — widen
|
|
28936
|
+
* the closed vocabulary with a named member and derive it by baseline
|
|
28937
|
+
* comparison — and it is a *widening*, not a restyle: `'recommended'` is the
|
|
28938
|
+
* default and reproduces today's behaviour exactly, so no row whose bytes carry
|
|
28939
|
+
* a presentation compiles differently for `'plain'` existing.
|
|
28940
|
+
*
|
|
28941
|
+
* `'plain'` is deliberately not "preserve whatever bytes are there". It is a
|
|
28942
|
+
* constructive instruction — *write no presentation block* — so Phase 4e's
|
|
28943
|
+
* compiler, which builds the header from nothing, can produce it.
|
|
28944
|
+
*/
|
|
28945
|
+
const headerBrandPresentationValues = ["recommended", "plain"];
|
|
28946
|
+
/**
|
|
28947
|
+
* Key-order-safe structural equality over the persisted `logo` object.
|
|
28948
|
+
*
|
|
28949
|
+
* The persisted value comes back from `jsonb`, whose key order is not the order
|
|
28950
|
+
* the applier builds; a `JSON.stringify` comparison would read two equal logo
|
|
28951
|
+
* blocks as different and quietly classify a `'recommended'` row as
|
|
28952
|
+
* unrecognised — the same trap the interaction-palette transcription test
|
|
28953
|
+
* exists to catch.
|
|
28954
|
+
*/
|
|
28955
|
+
function jsonEquals(left, right) {
|
|
28956
|
+
if (left === right) return true;
|
|
28957
|
+
if (left === null || right === null) return false;
|
|
28958
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
28959
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false;
|
|
28960
|
+
if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((value, index) => jsonEquals(value, right[index]));
|
|
28961
|
+
const leftRecord = left;
|
|
28962
|
+
const rightRecord = right;
|
|
28963
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(leftRecord), ...Object.keys(rightRecord)]);
|
|
28964
|
+
for (const key of keys) if (!jsonEquals(leftRecord[key], rightRecord[key])) return false;
|
|
28965
|
+
return true;
|
|
28966
|
+
}
|
|
28967
|
+
defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
|
|
28968
|
+
function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
|
|
28969
|
+
const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
|
|
28970
|
+
const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
|
|
28971
|
+
if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
|
|
28972
|
+
const transition = compileTransitionRuntime({
|
|
28973
|
+
treatment: boundary.treatment,
|
|
28974
|
+
from: fromSurface,
|
|
28975
|
+
to: toSurface
|
|
28976
|
+
});
|
|
28977
|
+
return {
|
|
28978
|
+
...boundary,
|
|
28979
|
+
from: transition.from,
|
|
28980
|
+
to: transition.to,
|
|
28981
|
+
cssVars: transition.cssVars
|
|
28982
|
+
};
|
|
28983
|
+
}
|
|
28984
|
+
function contentOwnedBackgroundEndpoint(content) {
|
|
28985
|
+
const background = readRecord(readRecord(content?._sectionStyles)?.background);
|
|
28986
|
+
if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
|
|
28987
|
+
const token = parseThemeColorToken(background.color);
|
|
28988
|
+
if (!token) return null;
|
|
28989
|
+
return {
|
|
28990
|
+
kind: "color-token",
|
|
28991
|
+
token
|
|
28992
|
+
};
|
|
28993
|
+
}
|
|
28994
|
+
function readRecord(value) {
|
|
28995
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
28996
|
+
}
|
|
28997
|
+
//#endregion
|
|
28748
28998
|
//#region ../theme-core/src/site-styles/generatedSchemas.ts
|
|
28749
28999
|
function uniqueNonEmpty(values) {
|
|
28750
29000
|
const uniqueValues = [...new Set(values)];
|
|
@@ -28813,6 +29063,76 @@ z$1.object({
|
|
|
28813
29063
|
blocks: z$1.array(generatedPageDesignBlockIntentSchema)
|
|
28814
29064
|
}).strict();
|
|
28815
29065
|
//#endregion
|
|
29066
|
+
//#region ../theme-core/src/site-styles/headerPreferencesPersistence.ts
|
|
29067
|
+
const backgroundColorTokenSchema = z$1.unknown().transform((value, ctx) => {
|
|
29068
|
+
const token = parseSiteChromeBackgroundColorToken(value);
|
|
29069
|
+
if (token === null) {
|
|
29070
|
+
ctx.addIssue({
|
|
29071
|
+
code: "custom",
|
|
29072
|
+
message: "Unknown site chrome background colour token"
|
|
29073
|
+
});
|
|
29074
|
+
return z$1.NEVER;
|
|
29075
|
+
}
|
|
29076
|
+
return token;
|
|
29077
|
+
});
|
|
29078
|
+
const backgroundOverrideSchema = z$1.discriminatedUnion("kind", [z$1.object({ kind: z$1.literal("default") }).strict(), z$1.object({
|
|
29079
|
+
kind: z$1.literal("color"),
|
|
29080
|
+
token: backgroundColorTokenSchema
|
|
29081
|
+
}).strict()]);
|
|
29082
|
+
const logoTextIntentSchema = z$1.object({
|
|
29083
|
+
mobileMaxWidth: z$1.enum([
|
|
29084
|
+
"xs",
|
|
29085
|
+
"sm",
|
|
29086
|
+
"md"
|
|
29087
|
+
]).optional(),
|
|
29088
|
+
mobileWrapLines: z$1.union([z$1.literal(1), z$1.literal(2)]).optional(),
|
|
29089
|
+
hideOnShrink: z$1.boolean().optional()
|
|
29090
|
+
}).strict();
|
|
29091
|
+
const structureSchema = z$1.object({
|
|
29092
|
+
positioning: z$1.union([z$1.literal("inherit"), z$1.enum(headerPositioningValues)]),
|
|
29093
|
+
shrinkOnScroll: z$1.union([z$1.literal("inherit"), z$1.boolean()]),
|
|
29094
|
+
logoText: z$1.union([
|
|
29095
|
+
z$1.literal("inherit"),
|
|
29096
|
+
z$1.literal("none"),
|
|
29097
|
+
logoTextIntentSchema
|
|
29098
|
+
])
|
|
29099
|
+
}).strict();
|
|
29100
|
+
const headerPreferencesShape = {
|
|
29101
|
+
brandPresentation: z$1.enum(headerBrandPresentationValues),
|
|
29102
|
+
brandDisplay: z$1.enum(brandDisplayModeValues),
|
|
29103
|
+
logoSize: z$1.enum(logoSizePolicyValues),
|
|
29104
|
+
navFontRole: z$1.enum(headerNavTypographyPreferenceValues),
|
|
29105
|
+
navTextSize: z$1.enum(headerNavFontSizePreferenceValues),
|
|
29106
|
+
interactionPalette: z$1.union([z$1.literal("recommended"), z$1.enum(headerInteractionPaletteValues)]),
|
|
29107
|
+
background: backgroundOverrideSchema,
|
|
29108
|
+
structure: structureSchema
|
|
29109
|
+
};
|
|
29110
|
+
z$1.object(headerPreferencesShape).strict();
|
|
29111
|
+
z$1.object(headerPreferencesShape).omit({
|
|
29112
|
+
brandPresentation: true,
|
|
29113
|
+
structure: true
|
|
29114
|
+
}).strict();
|
|
29115
|
+
//#endregion
|
|
29116
|
+
//#region ../theme-core/src/site-styles/headerBytesComparison.ts
|
|
29117
|
+
function isPlainObject$5(value) {
|
|
29118
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29119
|
+
}
|
|
29120
|
+
/**
|
|
29121
|
+
* Structural diff by JSON path. Values are compared through
|
|
29122
|
+
* `JSON.stringify`, so a key present with the value `undefined` and an absent
|
|
29123
|
+
* key read as the same thing — which is what `jsonb` does to them anyway
|
|
29124
|
+
* (#1318 Correction 5).
|
|
29125
|
+
*/
|
|
29126
|
+
function diffJsonPaths(left, right, path = "") {
|
|
29127
|
+
if (isPlainObject$5(left) && isPlainObject$5(right)) return [.../* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])].sort().flatMap((key) => diffJsonPaths(left[key], right[key], path ? `${path}.${key}` : key));
|
|
29128
|
+
if (JSON.stringify(left) === JSON.stringify(right)) return [];
|
|
29129
|
+
return [{
|
|
29130
|
+
path,
|
|
29131
|
+
left,
|
|
29132
|
+
right
|
|
29133
|
+
}];
|
|
29134
|
+
}
|
|
29135
|
+
//#endregion
|
|
28816
29136
|
//#region ../theme-core/src/site-styles/pageDesignAuthority.ts
|
|
28817
29137
|
const nonEmptyStringSchema = z$1.string().min(1);
|
|
28818
29138
|
const blockContentVersionIdSchema = nonEmptyStringSchema.transform(asBlockContentVersionId);
|
|
@@ -28914,46 +29234,6 @@ function collectBlockSequenceIssues(snapshots) {
|
|
|
28914
29234
|
return issues;
|
|
28915
29235
|
}
|
|
28916
29236
|
//#endregion
|
|
28917
|
-
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
28918
|
-
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
28919
|
-
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
28920
|
-
siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
|
|
28921
|
-
function parseSiteChromeBackgroundColorToken(value) {
|
|
28922
|
-
const token = parseBackgroundAppearanceColorToken(value);
|
|
28923
|
-
return token ? token : null;
|
|
28924
|
-
}
|
|
28925
|
-
function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
28926
|
-
return tokenNames.flatMap((name) => {
|
|
28927
|
-
const token = parseSiteChromeBackgroundColorToken(name);
|
|
28928
|
-
return token ? [token] : [];
|
|
28929
|
-
});
|
|
28930
|
-
}
|
|
28931
|
-
/**
|
|
28932
|
-
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
28933
|
-
* nav link style. This is where the precedence contract lands:
|
|
28934
|
-
*
|
|
28935
|
-
* - The nav typography TREATMENT (case/tracking identity: plain / heading /
|
|
28936
|
-
* quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
|
|
28937
|
-
* baked into `style` before this call (see `applyNavTypography` in
|
|
28938
|
-
* headerLayoutStyles.ts).
|
|
28939
|
-
* - Each treatment resolves through a DEFAULT font role: `heading` resolves
|
|
28940
|
-
* through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
|
|
28941
|
-
* through the body role.
|
|
28942
|
-
* - The user's `navFontRole` (surfaced here as `preferences.typography`) only
|
|
28943
|
-
* overrides which font ROLE the treatment resolves through. It never changes
|
|
28944
|
-
* the treatment. `'inherit'` keeps the treatment's default role.
|
|
28945
|
-
*/
|
|
28946
|
-
function mergeHeaderNavTypographyPreference(style, preferences) {
|
|
28947
|
-
return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
|
|
28948
|
-
...style,
|
|
28949
|
-
...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
|
|
28950
|
-
...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
|
|
28951
|
-
};
|
|
28952
|
-
}
|
|
28953
|
-
function readHeaderNavFontSizePreference(value) {
|
|
28954
|
-
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
28955
|
-
}
|
|
28956
|
-
//#endregion
|
|
28957
29237
|
//#region ../theme-core/src/site-styles/tokenRecipes.ts
|
|
28958
29238
|
const themeTokenRecipeOptions = {
|
|
28959
29239
|
palette: [
|
|
@@ -40775,7 +41055,7 @@ const eventDisplayFields = [
|
|
|
40775
41055
|
{
|
|
40776
41056
|
id: "showCapacity",
|
|
40777
41057
|
type: "boolean",
|
|
40778
|
-
label: "Show available
|
|
41058
|
+
label: "Show available places",
|
|
40779
41059
|
description: "Display remaining capacity",
|
|
40780
41060
|
required: false,
|
|
40781
41061
|
defaultValue: true,
|
|
@@ -41100,7 +41380,272 @@ function planEventRegistrationScope(input) {
|
|
|
41100
41380
|
};
|
|
41101
41381
|
}
|
|
41102
41382
|
//#endregion
|
|
41103
|
-
//#region ../blocks/src/system/runtime/nodes/events/
|
|
41383
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
|
|
41384
|
+
/**
|
|
41385
|
+
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
41386
|
+
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
41387
|
+
* for uncapped events so call sites can render no capacity indicator at all.
|
|
41388
|
+
*
|
|
41389
|
+
* The single source of truth for capacity math across event cards, agenda
|
|
41390
|
+
* rows, and timetable cells — keep wording variations in the call sites.
|
|
41391
|
+
*/
|
|
41392
|
+
function getEventAvailability(event) {
|
|
41393
|
+
if (event.capacity == null) return {
|
|
41394
|
+
available: null,
|
|
41395
|
+
isFull: false
|
|
41396
|
+
};
|
|
41397
|
+
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
41398
|
+
if (available <= 0) return {
|
|
41399
|
+
available: 0,
|
|
41400
|
+
isFull: true
|
|
41401
|
+
};
|
|
41402
|
+
return {
|
|
41403
|
+
available,
|
|
41404
|
+
isFull: false
|
|
41405
|
+
};
|
|
41406
|
+
}
|
|
41407
|
+
/**
|
|
41408
|
+
* Pick the capacity row a booking decision should read: the selected
|
|
41409
|
+
* attendance mode's availability when the visitor has chosen one and the
|
|
41410
|
+
* event publishes it, otherwise the event-level aggregate.
|
|
41411
|
+
*
|
|
41412
|
+
* The mode row is used wholesale. A mode with `capacity: null` on a capped
|
|
41413
|
+
* event is uncapped *by design*: the registration write path applies the
|
|
41414
|
+
* same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
|
|
41415
|
+
* back to the event's `availableSpots` here would block bookings the server
|
|
41416
|
+
* accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
|
|
41417
|
+
*/
|
|
41418
|
+
function selectEventCapacitySource(event, attendanceMode) {
|
|
41419
|
+
if (!attendanceMode) return event;
|
|
41420
|
+
return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
|
|
41421
|
+
}
|
|
41422
|
+
/**
|
|
41423
|
+
* Whether the visitor's next step is the waitlist: the source is full *and*
|
|
41424
|
+
* a waitlist is open. The one place this is decided — cards, rows, the
|
|
41425
|
+
* wizard's steps and its action bar all route through here so they agree,
|
|
41426
|
+
* including for producers that omit `availableSpots` and rely on the
|
|
41427
|
+
* `capacity - registeredCount` fallback.
|
|
41428
|
+
*/
|
|
41429
|
+
function canJoinEventWaitlist(event, options) {
|
|
41430
|
+
const source = selectEventCapacitySource(event, options?.attendanceMode);
|
|
41431
|
+
return getEventAvailability(source).isFull && source.waitlistEnabled === true;
|
|
41432
|
+
}
|
|
41433
|
+
/**
|
|
41434
|
+
* Remaining places at or below which an event reads as scarce and the exact
|
|
41435
|
+
* count is worth showing. Above it, no number is shown at all: "147 places
|
|
41436
|
+
* left" is not something a visitor acts on, and it is the figure most likely
|
|
41437
|
+
* to be stale on a cached page.
|
|
41438
|
+
*
|
|
41439
|
+
* max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
|
|
41440
|
+
*
|
|
41441
|
+
* The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
|
|
41442
|
+
* 5% floor stops large events staying silent until three remain.
|
|
41443
|
+
* capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
|
|
41444
|
+
*/
|
|
41445
|
+
function lowCapacityThreshold(capacity) {
|
|
41446
|
+
return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
|
|
41447
|
+
}
|
|
41448
|
+
function getRemainingPlaces(source) {
|
|
41449
|
+
const { available, isFull } = getEventAvailability(source);
|
|
41450
|
+
if (available === null || source.capacity === null) return { kind: "unlimited" };
|
|
41451
|
+
if (isFull) return {
|
|
41452
|
+
kind: "full",
|
|
41453
|
+
waitlist: canJoinEventWaitlist(source)
|
|
41454
|
+
};
|
|
41455
|
+
if (available <= lowCapacityThreshold(source.capacity)) return {
|
|
41456
|
+
kind: "low",
|
|
41457
|
+
remaining: available
|
|
41458
|
+
};
|
|
41459
|
+
return { kind: "open" };
|
|
41460
|
+
}
|
|
41461
|
+
/**
|
|
41462
|
+
* The one place a remaining-places count becomes text. Returns `null` when
|
|
41463
|
+
* nothing should be shown: uncapped events, and capped events above the
|
|
41464
|
+
* scarcity threshold. Surfaces that need their own full-state wording pass
|
|
41465
|
+
* it in rather than re-deriving the count.
|
|
41466
|
+
*/
|
|
41467
|
+
function formatRemainingPlaces(source, wording = {}) {
|
|
41468
|
+
const remaining = getRemainingPlaces(source);
|
|
41469
|
+
switch (remaining.kind) {
|
|
41470
|
+
case "unlimited":
|
|
41471
|
+
case "open": return null;
|
|
41472
|
+
case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
|
|
41473
|
+
case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
|
|
41474
|
+
}
|
|
41475
|
+
}
|
|
41476
|
+
function formatPlacesLeft(count, compact) {
|
|
41477
|
+
if (compact) return `${count} left`;
|
|
41478
|
+
return count === 1 ? "1 place left" : `${count} places left`;
|
|
41479
|
+
}
|
|
41480
|
+
/** Total capacity as copy ("12 places"), for detail surfaces that state the size of an event. */
|
|
41481
|
+
function formatTotalCapacity(capacity) {
|
|
41482
|
+
if (capacity === null) return null;
|
|
41483
|
+
return capacity === 1 ? "1 place" : `${capacity} places`;
|
|
41484
|
+
}
|
|
41485
|
+
function getEventAvailabilityStatus(event) {
|
|
41486
|
+
const source = selectMostOpenCapacitySource(event);
|
|
41487
|
+
const remaining = getRemainingPlaces(source);
|
|
41488
|
+
const label = formatEventRemainingPlaces(event, { compact: true });
|
|
41489
|
+
switch (remaining.kind) {
|
|
41490
|
+
case "unlimited": return {
|
|
41491
|
+
status: "available",
|
|
41492
|
+
available: null,
|
|
41493
|
+
isFull: false,
|
|
41494
|
+
label
|
|
41495
|
+
};
|
|
41496
|
+
case "open": return {
|
|
41497
|
+
status: "available",
|
|
41498
|
+
available: getEventAvailability(source).available,
|
|
41499
|
+
isFull: false,
|
|
41500
|
+
label
|
|
41501
|
+
};
|
|
41502
|
+
case "low": return {
|
|
41503
|
+
status: "filling",
|
|
41504
|
+
available: remaining.remaining,
|
|
41505
|
+
isFull: false,
|
|
41506
|
+
label
|
|
41507
|
+
};
|
|
41508
|
+
case "full": return {
|
|
41509
|
+
status: "waitlist",
|
|
41510
|
+
available: 0,
|
|
41511
|
+
isFull: true,
|
|
41512
|
+
label
|
|
41513
|
+
};
|
|
41514
|
+
}
|
|
41515
|
+
}
|
|
41516
|
+
/**
|
|
41517
|
+
* Which capacity figure wins when they disagree.
|
|
41518
|
+
*
|
|
41519
|
+
* For a hybrid event that publishes both attendance-mode rows, **the mode
|
|
41520
|
+
* rows are authoritative and the event-level aggregate is only a summary**.
|
|
41521
|
+
* The registration write path admits or refuses per mode
|
|
41522
|
+
* (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
|
|
41523
|
+
* aggregate can read full while a mode still has room, and can read open
|
|
41524
|
+
* while the mode the visitor wants is full. Believing the aggregate on a
|
|
41525
|
+
* card is how a "places available" event turns into a dead end at step two.
|
|
41526
|
+
*
|
|
41527
|
+
* Everything else — single-mode events, events that publish no rows, and the
|
|
41528
|
+
* aggregate-only helpers {@link getEventAvailability} /
|
|
41529
|
+
* {@link getRemainingPlaces} that existing callers rely on — keeps reading
|
|
41530
|
+
* the aggregate, unchanged.
|
|
41531
|
+
*/
|
|
41532
|
+
function findHybridModeRows(event) {
|
|
41533
|
+
if (event.deliveryMode !== "hybrid") return null;
|
|
41534
|
+
const rows = event.attendanceModes ?? [];
|
|
41535
|
+
const inPerson = rows.find((row) => row.mode === "in_person");
|
|
41536
|
+
const online = rows.find((row) => row.mode === "online");
|
|
41537
|
+
return inPerson && online ? {
|
|
41538
|
+
inPerson,
|
|
41539
|
+
online
|
|
41540
|
+
} : null;
|
|
41541
|
+
}
|
|
41542
|
+
/** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
|
|
41543
|
+
function remainingPlacesRank(remaining) {
|
|
41544
|
+
switch (remaining.kind) {
|
|
41545
|
+
case "unlimited": return 4;
|
|
41546
|
+
case "open": return 3;
|
|
41547
|
+
case "low": return 2;
|
|
41548
|
+
case "full": return remaining.waitlist ? 1 : 0;
|
|
41549
|
+
}
|
|
41550
|
+
}
|
|
41551
|
+
function isMoreOpen(candidate, than) {
|
|
41552
|
+
const rank = remainingPlacesRank(candidate);
|
|
41553
|
+
const otherRank = remainingPlacesRank(than);
|
|
41554
|
+
if (rank !== otherRank) return rank > otherRank;
|
|
41555
|
+
return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
|
|
41556
|
+
}
|
|
41557
|
+
/** True when `candidate` has less room left than `than`. */
|
|
41558
|
+
function isScarcer(candidate, than) {
|
|
41559
|
+
return isMoreOpen(than, candidate);
|
|
41560
|
+
}
|
|
41561
|
+
/**
|
|
41562
|
+
* The capacity row a whole-event decision should read: the most open
|
|
41563
|
+
* attendance mode of a hybrid event, otherwise the aggregate. A hybrid event
|
|
41564
|
+
* is sold out only when both of its modes are, and its waitlist is reachable
|
|
41565
|
+
* when either full mode has one open.
|
|
41566
|
+
*/
|
|
41567
|
+
function selectMostOpenCapacitySource(event) {
|
|
41568
|
+
const rows = findHybridModeRows(event);
|
|
41569
|
+
if (!rows) return event;
|
|
41570
|
+
return isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? rows.online : rows.inPerson;
|
|
41571
|
+
}
|
|
41572
|
+
/** True when no attendance mode has room. Hybrid-aware; use this for sold-out decisions. */
|
|
41573
|
+
function isEventFull(event) {
|
|
41574
|
+
return getEventAvailability(selectMostOpenCapacitySource(event)).isFull;
|
|
41575
|
+
}
|
|
41576
|
+
/**
|
|
41577
|
+
* True when the event is full on every mode and at least one of those full
|
|
41578
|
+
* modes has a waitlist open. An event with a mode still bookable is not a
|
|
41579
|
+
* waitlist prospect: it should send the visitor to that mode instead.
|
|
41580
|
+
*/
|
|
41581
|
+
function canJoinEventWaitlistOnAnyMode(event) {
|
|
41582
|
+
return canJoinEventWaitlist(selectMostOpenCapacitySource(event));
|
|
41583
|
+
}
|
|
41584
|
+
const ATTENDANCE_MODE_LABEL = {
|
|
41585
|
+
in_person: "In person",
|
|
41586
|
+
online: "Online"
|
|
41587
|
+
};
|
|
41588
|
+
/** Wording for a mode with room to spare, where the count is deliberately withheld. */
|
|
41589
|
+
const PLACES_AVAILABLE = "Places available";
|
|
41590
|
+
/**
|
|
41591
|
+
* The browse-surface label for an event's remaining places. Single-mode
|
|
41592
|
+
* events get exactly the aggregate label {@link formatRemainingPlaces}
|
|
41593
|
+
* produces, and so does a hybrid event whose two modes tell the same story.
|
|
41594
|
+
* When the modes differ, both sides are named: "available" on a card is a
|
|
41595
|
+
* promise the registration wizard would otherwise have to break.
|
|
41596
|
+
*
|
|
41597
|
+
* Hybrid examples, in person full with 3 online places left:
|
|
41598
|
+
* default -> "Online: 3 places left · In person: Full"
|
|
41599
|
+
* { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
|
|
41600
|
+
* { compact: true } -> "Online: 3 left"
|
|
41601
|
+
*/
|
|
41602
|
+
function formatEventRemainingPlaces(event, wording = {}) {
|
|
41603
|
+
const rows = findHybridModeRows(event);
|
|
41604
|
+
if (!rows) return formatRemainingPlaces(event, wording);
|
|
41605
|
+
const inPerson = formatRemainingPlaces(rows.inPerson, wording);
|
|
41606
|
+
const online = formatRemainingPlaces(rows.online, wording);
|
|
41607
|
+
if (inPerson === online) return inPerson;
|
|
41608
|
+
if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
|
|
41609
|
+
return formatBothHybridSides(rows, {
|
|
41610
|
+
inPerson,
|
|
41611
|
+
online
|
|
41612
|
+
});
|
|
41613
|
+
}
|
|
41614
|
+
/**
|
|
41615
|
+
* Both sides, the one with room first: that is the half a visitor can act on,
|
|
41616
|
+
* and it should not arrive after a full stop's worth of bad news.
|
|
41617
|
+
*/
|
|
41618
|
+
function formatBothHybridSides(rows, labels) {
|
|
41619
|
+
return (isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? [["online", labels.online], ["in_person", labels.inPerson]] : [["in_person", labels.inPerson], ["online", labels.online]]).map(([mode, label]) => namedModePlaces(mode, label)).join(" · ");
|
|
41620
|
+
}
|
|
41621
|
+
function namedModePlaces(mode, label) {
|
|
41622
|
+
return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
|
|
41623
|
+
}
|
|
41624
|
+
/**
|
|
41625
|
+
* Pills and timetable cells have room for one short phrase, so they name the
|
|
41626
|
+
* single mode that carries news rather than listing both: the scarce side
|
|
41627
|
+
* when there is a count to show, and otherwise the side that has closed —
|
|
41628
|
+
* "In person: Full", never "Online only", which reads as a statement about
|
|
41629
|
+
* how the event is delivered rather than about its remaining places.
|
|
41630
|
+
*/
|
|
41631
|
+
function formatCompactHybridPlaces(rows, wording) {
|
|
41632
|
+
const inPerson = getRemainingPlaces(rows.inPerson);
|
|
41633
|
+
const online = getRemainingPlaces(rows.online);
|
|
41634
|
+
if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
|
|
41635
|
+
if (inPerson.kind === "full" || online.kind === "full") {
|
|
41636
|
+
const fullSide = inPerson.kind === "full" ? "in_person" : "online";
|
|
41637
|
+
const openSide = inPerson.kind === "full" ? "online" : "in_person";
|
|
41638
|
+
const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
|
|
41639
|
+
return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
|
|
41640
|
+
}
|
|
41641
|
+
const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
|
|
41642
|
+
return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
|
|
41643
|
+
}
|
|
41644
|
+
function rowFor(rows, mode) {
|
|
41645
|
+
return mode === "online" ? rows.online : rows.inPerson;
|
|
41646
|
+
}
|
|
41647
|
+
//#endregion
|
|
41648
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
|
|
41104
41649
|
function isRecord$23(value) {
|
|
41105
41650
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41106
41651
|
}
|
|
@@ -41118,7 +41663,701 @@ function resolveRuntimeEventDestination(event) {
|
|
|
41118
41663
|
};
|
|
41119
41664
|
}
|
|
41120
41665
|
//#endregion
|
|
41121
|
-
//#region ../blocks/src/system/runtime/nodes/events/
|
|
41666
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/cta.ts
|
|
41667
|
+
/**
|
|
41668
|
+
* Booking call-to-action resolution for events: where an event links to and
|
|
41669
|
+
* what the button says. Sold-out and waitlist decisions come from capacity.ts
|
|
41670
|
+
* so cards, rows and the wizard never disagree.
|
|
41671
|
+
*/
|
|
41672
|
+
function closedEventBookingLabel(reason) {
|
|
41673
|
+
switch (reason) {
|
|
41674
|
+
case "occurrence_cancelled":
|
|
41675
|
+
case "series_cancelled": return "Cancelled";
|
|
41676
|
+
case "occurrence_completed":
|
|
41677
|
+
case "series_completed": return "Completed";
|
|
41678
|
+
case "series_draft":
|
|
41679
|
+
case "series_archived":
|
|
41680
|
+
case "parent_archived":
|
|
41681
|
+
case "parent_inconsistent":
|
|
41682
|
+
case "not_public":
|
|
41683
|
+
case "already_started":
|
|
41684
|
+
case "enrollment_closed": return "Registration closed";
|
|
41685
|
+
default: return assertNever$24(reason);
|
|
41686
|
+
}
|
|
41687
|
+
}
|
|
41688
|
+
function resolveEventHref(event) {
|
|
41689
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
41690
|
+
return destination.kind === "external" ? destination.url : destination.path;
|
|
41691
|
+
}
|
|
41692
|
+
function resolveEventLinkProps(event) {
|
|
41693
|
+
const href = resolveEventHref(event);
|
|
41694
|
+
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
41695
|
+
href,
|
|
41696
|
+
target: "_blank",
|
|
41697
|
+
rel: "noopener noreferrer"
|
|
41698
|
+
} : { href };
|
|
41699
|
+
}
|
|
41700
|
+
/**
|
|
41701
|
+
* Resolve the CTA state for an event.
|
|
41702
|
+
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
41703
|
+
* and course-specific defaults consistent.
|
|
41704
|
+
*/
|
|
41705
|
+
function resolveEventCta(event, buttonText) {
|
|
41706
|
+
const isSoldOut = isEventFull(event);
|
|
41707
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
41708
|
+
const isExternal = destination.kind === "external";
|
|
41709
|
+
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
41710
|
+
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
41711
|
+
const href = resolveEventHref(event);
|
|
41712
|
+
if (event.bookingAvailability.kind === "closed") {
|
|
41713
|
+
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
41714
|
+
return {
|
|
41715
|
+
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
41716
|
+
href,
|
|
41717
|
+
external: isExternal,
|
|
41718
|
+
hidden: !linksToClosedWholeSeriesDetails
|
|
41719
|
+
};
|
|
41720
|
+
}
|
|
41721
|
+
if (!isExternal && isSoldOut) {
|
|
41722
|
+
if (canJoinEventWaitlistOnAnyMode(event)) return {
|
|
41723
|
+
label: "Join waitlist",
|
|
41724
|
+
href,
|
|
41725
|
+
external: false,
|
|
41726
|
+
hidden: false
|
|
41727
|
+
};
|
|
41728
|
+
return {
|
|
41729
|
+
label: "Sold out",
|
|
41730
|
+
href,
|
|
41731
|
+
external: false,
|
|
41732
|
+
hidden: true
|
|
41733
|
+
};
|
|
41734
|
+
}
|
|
41735
|
+
return {
|
|
41736
|
+
label: effectiveButtonText,
|
|
41737
|
+
href,
|
|
41738
|
+
external: isExternal,
|
|
41739
|
+
hidden: false
|
|
41740
|
+
};
|
|
41741
|
+
}
|
|
41742
|
+
//#endregion
|
|
41743
|
+
//#region ../calendar/src/grid.ts
|
|
41744
|
+
const DAY_NAMES = [
|
|
41745
|
+
"Sun",
|
|
41746
|
+
"Mon",
|
|
41747
|
+
"Tue",
|
|
41748
|
+
"Wed",
|
|
41749
|
+
"Thu",
|
|
41750
|
+
"Fri",
|
|
41751
|
+
"Sat"
|
|
41752
|
+
];
|
|
41753
|
+
const MONTH_NAMES = [
|
|
41754
|
+
"January",
|
|
41755
|
+
"February",
|
|
41756
|
+
"March",
|
|
41757
|
+
"April",
|
|
41758
|
+
"May",
|
|
41759
|
+
"June",
|
|
41760
|
+
"July",
|
|
41761
|
+
"August",
|
|
41762
|
+
"September",
|
|
41763
|
+
"October",
|
|
41764
|
+
"November",
|
|
41765
|
+
"December"
|
|
41766
|
+
];
|
|
41767
|
+
function formatDateKey(date) {
|
|
41768
|
+
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
41769
|
+
}
|
|
41770
|
+
function isToday(date, today) {
|
|
41771
|
+
return isSameCalendarDay(date, today);
|
|
41772
|
+
}
|
|
41773
|
+
function isSameCalendarDay(left, right) {
|
|
41774
|
+
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
41775
|
+
}
|
|
41776
|
+
function addDays(date, days) {
|
|
41777
|
+
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
41778
|
+
return {
|
|
41779
|
+
year: result.getUTCFullYear(),
|
|
41780
|
+
month: result.getUTCMonth() + 1,
|
|
41781
|
+
day: result.getUTCDate()
|
|
41782
|
+
};
|
|
41783
|
+
}
|
|
41784
|
+
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
41785
|
+
const firstDay = {
|
|
41786
|
+
year,
|
|
41787
|
+
month,
|
|
41788
|
+
day: 1
|
|
41789
|
+
};
|
|
41790
|
+
const dayOfWeek = getDayOfWeek(firstDay);
|
|
41791
|
+
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
41792
|
+
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
41793
|
+
}
|
|
41794
|
+
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
41795
|
+
const dayOfWeek = getDayOfWeek(baseDate);
|
|
41796
|
+
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
41797
|
+
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
41798
|
+
}
|
|
41799
|
+
function getDayOfWeek(date) {
|
|
41800
|
+
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
41801
|
+
}
|
|
41802
|
+
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
41803
|
+
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
41804
|
+
assertWeekCalendarDays(days);
|
|
41805
|
+
return days;
|
|
41806
|
+
}
|
|
41807
|
+
function assertWeekCalendarDays(days) {
|
|
41808
|
+
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
41809
|
+
}
|
|
41810
|
+
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
41811
|
+
if (startOfWeek === "monday") {
|
|
41812
|
+
const sunday = dayNames[0] ?? "Sun";
|
|
41813
|
+
return [...dayNames.slice(1), sunday];
|
|
41814
|
+
}
|
|
41815
|
+
return [...dayNames];
|
|
41816
|
+
}
|
|
41817
|
+
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
41818
|
+
if (view === "week") {
|
|
41819
|
+
const weekStart = days[0];
|
|
41820
|
+
const weekEnd = days[6];
|
|
41821
|
+
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
41822
|
+
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
41823
|
+
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
41824
|
+
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
41825
|
+
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
41826
|
+
}
|
|
41827
|
+
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
41828
|
+
}
|
|
41829
|
+
function buildCalendarViewport(input) {
|
|
41830
|
+
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
41831
|
+
if (input.view === "week") {
|
|
41832
|
+
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
41833
|
+
return {
|
|
41834
|
+
kind: "week",
|
|
41835
|
+
anchorDate: input.anchorDate,
|
|
41836
|
+
days,
|
|
41837
|
+
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
41838
|
+
};
|
|
41839
|
+
}
|
|
41840
|
+
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
41841
|
+
return {
|
|
41842
|
+
kind: "month",
|
|
41843
|
+
anchorDate: input.anchorDate,
|
|
41844
|
+
days,
|
|
41845
|
+
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
41846
|
+
};
|
|
41847
|
+
}
|
|
41848
|
+
//#endregion
|
|
41849
|
+
//#region ../calendar/src/lane-packing.ts
|
|
41850
|
+
/**
|
|
41851
|
+
* Assign lanes to a list of intervals.
|
|
41852
|
+
*
|
|
41853
|
+
* Returns one result per input in the same order as the input. Internally
|
|
41854
|
+
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
41855
|
+
* permutes results back to input order.
|
|
41856
|
+
*/
|
|
41857
|
+
function assignLanes(intervals) {
|
|
41858
|
+
if (intervals.length === 0) return [];
|
|
41859
|
+
const indexed = intervals.map((interval, index) => ({
|
|
41860
|
+
interval,
|
|
41861
|
+
index
|
|
41862
|
+
}));
|
|
41863
|
+
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
41864
|
+
const results = new Array(intervals.length);
|
|
41865
|
+
let cluster = [];
|
|
41866
|
+
let activeLaneEnds = [];
|
|
41867
|
+
let clusterMaxEnd = -Infinity;
|
|
41868
|
+
const closeCluster = () => {
|
|
41869
|
+
if (cluster.length === 0) return;
|
|
41870
|
+
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
41871
|
+
for (const entry of cluster) results[entry.index] = {
|
|
41872
|
+
laneIndex: entry.laneIndex,
|
|
41873
|
+
laneCount
|
|
41874
|
+
};
|
|
41875
|
+
cluster = [];
|
|
41876
|
+
activeLaneEnds = [];
|
|
41877
|
+
clusterMaxEnd = -Infinity;
|
|
41878
|
+
};
|
|
41879
|
+
for (const { interval, index } of indexed) {
|
|
41880
|
+
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
41881
|
+
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
41882
|
+
activeLaneEnds[laneIndex] = interval.end;
|
|
41883
|
+
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
41884
|
+
cluster.push({
|
|
41885
|
+
index,
|
|
41886
|
+
laneIndex
|
|
41887
|
+
});
|
|
41888
|
+
}
|
|
41889
|
+
closeCluster();
|
|
41890
|
+
return results;
|
|
41891
|
+
}
|
|
41892
|
+
function firstAvailableLane(activeLaneEnds, start) {
|
|
41893
|
+
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
41894
|
+
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
41895
|
+
}
|
|
41896
|
+
//#endregion
|
|
41897
|
+
//#region ../calendar/src/timezone.ts
|
|
41898
|
+
/**
|
|
41899
|
+
* Timezone boundary helpers — internal.
|
|
41900
|
+
*
|
|
41901
|
+
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
41902
|
+
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
41903
|
+
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
41904
|
+
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
41905
|
+
* public surface.
|
|
41906
|
+
*/
|
|
41907
|
+
const MINUTES_PER_HOUR = 60;
|
|
41908
|
+
/**
|
|
41909
|
+
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
41910
|
+
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
41911
|
+
*/
|
|
41912
|
+
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
41913
|
+
const date = new Date(isoTimestamp);
|
|
41914
|
+
if (Number.isNaN(date.getTime())) return NaN;
|
|
41915
|
+
if (!isValidTimeZone(timeZone)) return NaN;
|
|
41916
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
41917
|
+
timeZone,
|
|
41918
|
+
hour: "2-digit",
|
|
41919
|
+
minute: "2-digit",
|
|
41920
|
+
hourCycle: "h23"
|
|
41921
|
+
}).formatToParts(date);
|
|
41922
|
+
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
41923
|
+
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
41924
|
+
const hour = hourPart === 24 ? 0 : hourPart;
|
|
41925
|
+
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
41926
|
+
return hour * MINUTES_PER_HOUR + minute;
|
|
41927
|
+
}
|
|
41928
|
+
/**
|
|
41929
|
+
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
41930
|
+
* Returns null if the inputs cannot be parsed.
|
|
41931
|
+
*/
|
|
41932
|
+
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
41933
|
+
const date = new Date(isoTimestamp);
|
|
41934
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
41935
|
+
if (!isValidTimeZone(timeZone)) return null;
|
|
41936
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
41937
|
+
timeZone,
|
|
41938
|
+
year: "numeric",
|
|
41939
|
+
month: "2-digit",
|
|
41940
|
+
day: "2-digit"
|
|
41941
|
+
}).formatToParts(date);
|
|
41942
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
41943
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
41944
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
41945
|
+
if (year === void 0 || year.length === 0) return null;
|
|
41946
|
+
if (month === void 0 || month.length === 0) return null;
|
|
41947
|
+
if (day === void 0 || day.length === 0) return null;
|
|
41948
|
+
return `${year}-${month}-${day}`;
|
|
41949
|
+
}
|
|
41950
|
+
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
41951
|
+
function isValidTimeZone(timeZone) {
|
|
41952
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
41953
|
+
const cached = validTimeZoneCache.get(timeZone);
|
|
41954
|
+
if (cached !== void 0) return cached;
|
|
41955
|
+
try {
|
|
41956
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
41957
|
+
validTimeZoneCache.set(timeZone, true);
|
|
41958
|
+
return true;
|
|
41959
|
+
} catch {
|
|
41960
|
+
validTimeZoneCache.set(timeZone, false);
|
|
41961
|
+
return false;
|
|
41962
|
+
}
|
|
41963
|
+
}
|
|
41964
|
+
//#endregion
|
|
41965
|
+
//#region ../calendar/src/timetable.ts
|
|
41966
|
+
/**
|
|
41967
|
+
* Timetable model — the package's core surface.
|
|
41968
|
+
*
|
|
41969
|
+
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
41970
|
+
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
41971
|
+
* plus a diagnostics list for items dropped during processing.
|
|
41972
|
+
*
|
|
41973
|
+
* The function is pure: given identical input (including `now`), it returns
|
|
41974
|
+
* the same output. The model has no opinion on what an item is — pass
|
|
41975
|
+
* accessors.
|
|
41976
|
+
*/
|
|
41977
|
+
const DEFAULT_BOUNDS = {
|
|
41978
|
+
minStartHour: 5,
|
|
41979
|
+
maxEndHour: 23,
|
|
41980
|
+
defaultStartHour: 6,
|
|
41981
|
+
defaultEndHour: 21,
|
|
41982
|
+
minHeightPercent: 2.5
|
|
41983
|
+
};
|
|
41984
|
+
function buildTimetableModel$1(input) {
|
|
41985
|
+
const bounds = resolveBounds(input.bounds);
|
|
41986
|
+
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
41987
|
+
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
41988
|
+
const parsed = parseAllItems(input.items, input);
|
|
41989
|
+
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
41990
|
+
const hourRange = resolveHourRange(inViewport, bounds);
|
|
41991
|
+
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
41992
|
+
return {
|
|
41993
|
+
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
41994
|
+
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
41995
|
+
startHour: hourRange.startHour,
|
|
41996
|
+
endHour: hourRange.endHour,
|
|
41997
|
+
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
41998
|
+
hasItems: inViewport.length > 0,
|
|
41999
|
+
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
42000
|
+
};
|
|
42001
|
+
}
|
|
42002
|
+
function parseAllItems(items, accessors) {
|
|
42003
|
+
const drafts = [];
|
|
42004
|
+
const diagnostics = [];
|
|
42005
|
+
for (const item of items) {
|
|
42006
|
+
const result = parseDraft(item, accessors);
|
|
42007
|
+
if (result.ok) drafts.push(result.draft);
|
|
42008
|
+
else diagnostics.push({
|
|
42009
|
+
item,
|
|
42010
|
+
reason: result.reason
|
|
42011
|
+
});
|
|
42012
|
+
}
|
|
42013
|
+
return {
|
|
42014
|
+
drafts,
|
|
42015
|
+
diagnostics
|
|
42016
|
+
};
|
|
42017
|
+
}
|
|
42018
|
+
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
42019
|
+
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
42020
|
+
const rangeStartMinutes = hourRange.startHour * 60;
|
|
42021
|
+
const rangeEndMinutes = hourRange.endHour * 60;
|
|
42022
|
+
const segmentsByDate = groupSegmentsByDate(segments);
|
|
42023
|
+
const cellsByDate = /* @__PURE__ */ new Map();
|
|
42024
|
+
const diagnostics = [];
|
|
42025
|
+
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
42026
|
+
const clipped = [];
|
|
42027
|
+
for (const segment of daySegments) {
|
|
42028
|
+
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
42029
|
+
if (result.ok) clipped.push(result.clipped);
|
|
42030
|
+
else diagnostics.push({
|
|
42031
|
+
item: segment.draft.item,
|
|
42032
|
+
reason: result.reason
|
|
42033
|
+
});
|
|
42034
|
+
}
|
|
42035
|
+
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
42036
|
+
start: entry.visibleStart,
|
|
42037
|
+
end: entry.visibleEnd,
|
|
42038
|
+
orderKey: entry.segment.draft.orderKey
|
|
42039
|
+
})));
|
|
42040
|
+
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
42041
|
+
laneIndex: 0,
|
|
42042
|
+
laneCount: 1
|
|
42043
|
+
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
42044
|
+
}
|
|
42045
|
+
return {
|
|
42046
|
+
cellsByDate,
|
|
42047
|
+
diagnostics
|
|
42048
|
+
};
|
|
42049
|
+
}
|
|
42050
|
+
function assembleDays(viewportDays, cellsByDate, today) {
|
|
42051
|
+
return viewportDays.map((date) => {
|
|
42052
|
+
const dateKey = formatDateKey(date);
|
|
42053
|
+
return {
|
|
42054
|
+
date,
|
|
42055
|
+
dateKey,
|
|
42056
|
+
isToday: isSameCalendarDay(date, today),
|
|
42057
|
+
cells: cellsByDate.get(dateKey) ?? []
|
|
42058
|
+
};
|
|
42059
|
+
});
|
|
42060
|
+
}
|
|
42061
|
+
function parseDraft(item, accessors) {
|
|
42062
|
+
const startsAtIso = accessors.getStartsAt(item);
|
|
42063
|
+
const endsAtIso = accessors.getEndsAt(item);
|
|
42064
|
+
const timeZone = accessors.getTimeZone(item);
|
|
42065
|
+
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
42066
|
+
if (Number.isNaN(startsAtMinutes)) return {
|
|
42067
|
+
ok: false,
|
|
42068
|
+
reason: "invalid-start"
|
|
42069
|
+
};
|
|
42070
|
+
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
42071
|
+
if (startDateKey == null) return {
|
|
42072
|
+
ok: false,
|
|
42073
|
+
reason: "invalid-timezone"
|
|
42074
|
+
};
|
|
42075
|
+
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
42076
|
+
if (endDateKey == null) return {
|
|
42077
|
+
ok: false,
|
|
42078
|
+
reason: "invalid-end"
|
|
42079
|
+
};
|
|
42080
|
+
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
42081
|
+
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
42082
|
+
ok: false,
|
|
42083
|
+
reason: "invalid-end"
|
|
42084
|
+
};
|
|
42085
|
+
const spansMultipleDays = startDateKey < endDateKey;
|
|
42086
|
+
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
42087
|
+
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
42088
|
+
ok: false,
|
|
42089
|
+
reason: "non-positive-duration"
|
|
42090
|
+
};
|
|
42091
|
+
return {
|
|
42092
|
+
ok: true,
|
|
42093
|
+
draft: {
|
|
42094
|
+
item,
|
|
42095
|
+
startDateKey,
|
|
42096
|
+
endDateKey,
|
|
42097
|
+
startsAtMinutes,
|
|
42098
|
+
endsAtMinutes,
|
|
42099
|
+
color: accessors.getColor?.(item) ?? null,
|
|
42100
|
+
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
42101
|
+
}
|
|
42102
|
+
};
|
|
42103
|
+
}
|
|
42104
|
+
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
42105
|
+
const segments = [];
|
|
42106
|
+
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
42107
|
+
if (!viewportKeys.has(dateKey)) continue;
|
|
42108
|
+
const isStartDay = dateKey === draft.startDateKey;
|
|
42109
|
+
const isEndDay = dateKey === draft.endDateKey;
|
|
42110
|
+
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
42111
|
+
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
42112
|
+
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
42113
|
+
segments.push({
|
|
42114
|
+
draft,
|
|
42115
|
+
dateKey,
|
|
42116
|
+
startsAtMinutes,
|
|
42117
|
+
endsAtMinutes,
|
|
42118
|
+
continuesBefore: !isStartDay,
|
|
42119
|
+
continuesAfter: !isEndDay
|
|
42120
|
+
});
|
|
42121
|
+
}
|
|
42122
|
+
return segments;
|
|
42123
|
+
}
|
|
42124
|
+
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
42125
|
+
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
42126
|
+
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
42127
|
+
if (visibleEnd <= visibleStart) return {
|
|
42128
|
+
ok: false,
|
|
42129
|
+
reason: "out-of-visible-hours"
|
|
42130
|
+
};
|
|
42131
|
+
return {
|
|
42132
|
+
ok: true,
|
|
42133
|
+
clipped: {
|
|
42134
|
+
segment,
|
|
42135
|
+
visibleStart,
|
|
42136
|
+
visibleEnd
|
|
42137
|
+
}
|
|
42138
|
+
};
|
|
42139
|
+
}
|
|
42140
|
+
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
42141
|
+
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
42142
|
+
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
42143
|
+
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
42144
|
+
return {
|
|
42145
|
+
item: entry.segment.draft.item,
|
|
42146
|
+
dateKey: entry.segment.dateKey,
|
|
42147
|
+
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
42148
|
+
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
42149
|
+
visibleStartsAtMinutes: entry.visibleStart,
|
|
42150
|
+
visibleEndsAtMinutes: entry.visibleEnd,
|
|
42151
|
+
topPercent,
|
|
42152
|
+
heightPercent,
|
|
42153
|
+
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
42154
|
+
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
42155
|
+
laneIndex: lane.laneIndex,
|
|
42156
|
+
laneCount: lane.laneCount,
|
|
42157
|
+
color: entry.segment.draft.color
|
|
42158
|
+
};
|
|
42159
|
+
}
|
|
42160
|
+
function resolveBounds(bounds) {
|
|
42161
|
+
return {
|
|
42162
|
+
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
42163
|
+
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
42164
|
+
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
42165
|
+
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
42166
|
+
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
42167
|
+
};
|
|
42168
|
+
}
|
|
42169
|
+
function resolveHourRange(segments, bounds) {
|
|
42170
|
+
if (segments.length === 0) return {
|
|
42171
|
+
startHour: bounds.defaultStartHour,
|
|
42172
|
+
endHour: bounds.defaultEndHour
|
|
42173
|
+
};
|
|
42174
|
+
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
42175
|
+
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
42176
|
+
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
42177
|
+
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
42178
|
+
return {
|
|
42179
|
+
startHour,
|
|
42180
|
+
endHour: Math.max(startHour + 1, endHour)
|
|
42181
|
+
};
|
|
42182
|
+
}
|
|
42183
|
+
function buildHourTicks(startHour, endHour) {
|
|
42184
|
+
const ticks = [];
|
|
42185
|
+
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
42186
|
+
return ticks;
|
|
42187
|
+
}
|
|
42188
|
+
function groupSegmentsByDate(segments) {
|
|
42189
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
42190
|
+
for (const segment of segments) {
|
|
42191
|
+
const existing = grouped.get(segment.dateKey) ?? [];
|
|
42192
|
+
existing.push(segment);
|
|
42193
|
+
grouped.set(segment.dateKey, existing);
|
|
42194
|
+
}
|
|
42195
|
+
return grouped;
|
|
42196
|
+
}
|
|
42197
|
+
function toRangePercent(minutes, rangeMinutes) {
|
|
42198
|
+
if (rangeMinutes <= 0) return 0;
|
|
42199
|
+
return minutes / rangeMinutes * 100;
|
|
42200
|
+
}
|
|
42201
|
+
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
42202
|
+
if (view === "day") return [viewDate];
|
|
42203
|
+
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
42204
|
+
}
|
|
42205
|
+
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
42206
|
+
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
42207
|
+
const start = parseDateKey(startDateKey);
|
|
42208
|
+
parseDateKey(endDateKey);
|
|
42209
|
+
const keys = [];
|
|
42210
|
+
let cursor = start;
|
|
42211
|
+
while (formatDateKey(cursor) <= endDateKey) {
|
|
42212
|
+
keys.push(formatDateKey(cursor));
|
|
42213
|
+
cursor = addDays(cursor, 1);
|
|
42214
|
+
}
|
|
42215
|
+
return keys;
|
|
42216
|
+
}
|
|
42217
|
+
function parseDateKey(dateKey) {
|
|
42218
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
42219
|
+
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
42220
|
+
const [, year, month, day] = match;
|
|
42221
|
+
return {
|
|
42222
|
+
year: Number(year),
|
|
42223
|
+
month: Number(month),
|
|
42224
|
+
day: Number(day)
|
|
42225
|
+
};
|
|
42226
|
+
}
|
|
42227
|
+
//#endregion
|
|
42228
|
+
//#region ../calendar/src/group.ts
|
|
42229
|
+
/**
|
|
42230
|
+
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
42231
|
+
*
|
|
42232
|
+
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
42233
|
+
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
42234
|
+
*
|
|
42235
|
+
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
42236
|
+
* relative order of items within each date matches input order.
|
|
42237
|
+
*/
|
|
42238
|
+
function groupItemsByDate(items, accessors) {
|
|
42239
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
42240
|
+
for (const item of items) {
|
|
42241
|
+
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
42242
|
+
if (dateKey == null) continue;
|
|
42243
|
+
const existing = grouped.get(dateKey);
|
|
42244
|
+
if (existing !== void 0) existing.push(item);
|
|
42245
|
+
else grouped.set(dateKey, [item]);
|
|
42246
|
+
}
|
|
42247
|
+
return grouped;
|
|
42248
|
+
}
|
|
42249
|
+
//#endregion
|
|
42250
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
|
|
42251
|
+
/**
|
|
42252
|
+
* Date and time presentation for events: the single place an event's
|
|
42253
|
+
* timestamps become strings or date buckets. Calendar-grid primitives are
|
|
42254
|
+
* re-exported from @riverbankcms/calendar so block code has one import.
|
|
42255
|
+
*/
|
|
42256
|
+
/**
|
|
42257
|
+
* Format date for display.
|
|
42258
|
+
* Returns: "Monday, January 15, 2025"
|
|
42259
|
+
*/
|
|
42260
|
+
function formatDate$2(isoTimestamp, options) {
|
|
42261
|
+
return formatBookingDate(toScheduledBookingDateTime({
|
|
42262
|
+
startsAt: isoTimestamp,
|
|
42263
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
42264
|
+
locale: options?.locale ?? "en-US"
|
|
42265
|
+
}), {
|
|
42266
|
+
weekday: "long",
|
|
42267
|
+
year: "numeric",
|
|
42268
|
+
month: "long",
|
|
42269
|
+
day: "numeric"
|
|
42270
|
+
});
|
|
42271
|
+
}
|
|
42272
|
+
/**
|
|
42273
|
+
* Format time for display.
|
|
42274
|
+
* Returns: "10:00 AM"
|
|
42275
|
+
*/
|
|
42276
|
+
function formatTime(isoTimestamp, options) {
|
|
42277
|
+
return formatBookingTime(toScheduledBookingDateTime({
|
|
42278
|
+
startsAt: isoTimestamp,
|
|
42279
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
42280
|
+
locale: options?.locale ?? "en-US"
|
|
42281
|
+
}), {
|
|
42282
|
+
hour: "numeric",
|
|
42283
|
+
minute: "2-digit",
|
|
42284
|
+
hour12: true
|
|
42285
|
+
});
|
|
42286
|
+
}
|
|
42287
|
+
function formatEventDateParts(args) {
|
|
42288
|
+
const scheduledRange = toScheduledBookingDateRange({
|
|
42289
|
+
startsAt: args.startsAt,
|
|
42290
|
+
endsAt: args.endsAt,
|
|
42291
|
+
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
42292
|
+
locale: args.locale ?? "en-US"
|
|
42293
|
+
});
|
|
42294
|
+
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
42295
|
+
weekday: "long",
|
|
42296
|
+
year: "numeric",
|
|
42297
|
+
month: "long",
|
|
42298
|
+
day: "numeric"
|
|
42299
|
+
} });
|
|
42300
|
+
return {
|
|
42301
|
+
date,
|
|
42302
|
+
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
42303
|
+
startsAt: args.endsAt,
|
|
42304
|
+
timeZone: scheduledRange.timeZone,
|
|
42305
|
+
locale: args.locale ?? "en-US"
|
|
42306
|
+
}))}`
|
|
42307
|
+
};
|
|
42308
|
+
}
|
|
42309
|
+
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
42310
|
+
const dateTime = toScheduledBookingDateTime({
|
|
42311
|
+
startsAt: isoTimestamp,
|
|
42312
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
42313
|
+
locale: options?.locale ?? "en-US"
|
|
42314
|
+
});
|
|
42315
|
+
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
42316
|
+
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
42317
|
+
return {
|
|
42318
|
+
weekday,
|
|
42319
|
+
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
42320
|
+
month,
|
|
42321
|
+
label: formatBookingDate(dateTime, {
|
|
42322
|
+
weekday: "long",
|
|
42323
|
+
year: "numeric",
|
|
42324
|
+
month: "long",
|
|
42325
|
+
day: "numeric"
|
|
42326
|
+
})
|
|
42327
|
+
};
|
|
42328
|
+
}
|
|
42329
|
+
/**
|
|
42330
|
+
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
42331
|
+
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
42332
|
+
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
42333
|
+
*/
|
|
42334
|
+
function groupEventsByDate(events, calendarTimeZone) {
|
|
42335
|
+
const grouped = groupItemsByDate(events, {
|
|
42336
|
+
getStartsAt: (event) => event.startsAt,
|
|
42337
|
+
getTimeZone: () => calendarTimeZone
|
|
42338
|
+
});
|
|
42339
|
+
const map = /* @__PURE__ */ new Map();
|
|
42340
|
+
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
42341
|
+
return map;
|
|
42342
|
+
}
|
|
42343
|
+
//#endregion
|
|
42344
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/people.ts
|
|
42345
|
+
function formatTeamMemberNames(names) {
|
|
42346
|
+
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
42347
|
+
if (normalized.length === 0) return null;
|
|
42348
|
+
const [firstName, secondName] = normalized;
|
|
42349
|
+
if (!firstName) return null;
|
|
42350
|
+
if (normalized.length === 1) return firstName;
|
|
42351
|
+
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
42352
|
+
const finalName = normalized[normalized.length - 1] ?? "";
|
|
42353
|
+
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
42354
|
+
}
|
|
42355
|
+
function formatEventTeacherLine(event) {
|
|
42356
|
+
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
42357
|
+
return names ? `with ${names}` : null;
|
|
42358
|
+
}
|
|
42359
|
+
//#endregion
|
|
42360
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
|
|
41122
42361
|
const DEFAULT_EVENT_CURRENCY = "USD";
|
|
41123
42362
|
function resolveRuntimeEventPricing(event) {
|
|
41124
42363
|
if (isPublicEventPricing(event.pricing)) return event.pricing;
|
|
@@ -41285,6 +42524,17 @@ function readCoursePriceDisplay(value, currency) {
|
|
|
41285
42524
|
function readAmount(value) {
|
|
41286
42525
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
41287
42526
|
}
|
|
42527
|
+
/**
|
|
42528
|
+
* Format event price for display.
|
|
42529
|
+
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
42530
|
+
*
|
|
42531
|
+
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
42532
|
+
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
42533
|
+
* surfaces stay consistent.
|
|
42534
|
+
*/
|
|
42535
|
+
function formatEventPrice(event) {
|
|
42536
|
+
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
42537
|
+
}
|
|
41288
42538
|
const eventCalendarDisplayModeValues = [
|
|
41289
42539
|
"combined",
|
|
41290
42540
|
"calendar",
|
|
@@ -41809,7 +43059,7 @@ const eventRegistrationBlockDefinition = {
|
|
|
41809
43059
|
{
|
|
41810
43060
|
id: "showCapacity",
|
|
41811
43061
|
type: "boolean",
|
|
41812
|
-
label: "Show available
|
|
43062
|
+
label: "Show available places",
|
|
41813
43063
|
description: "Display remaining capacity",
|
|
41814
43064
|
required: false,
|
|
41815
43065
|
defaultValue: true,
|
|
@@ -42346,599 +43596,34 @@ const eventSpotlightBlockDefinition = {
|
|
|
42346
43596
|
showCapacity: { $bind: { from: "content.showCapacity" } },
|
|
42347
43597
|
emptyMessage: { $bind: { from: "content.emptyMessage" } }
|
|
42348
43598
|
})], { gap: "lg" }),
|
|
42349
|
-
baseClass: "",
|
|
42350
|
-
spacing: "comfortable"
|
|
42351
|
-
})]
|
|
42352
|
-
}),
|
|
42353
|
-
dataSchemas: { events: publicEventsArraySchema.optional() },
|
|
42354
|
-
dataLoaders: { events: {
|
|
42355
|
-
endpoint: "listPublicEvents",
|
|
42356
|
-
params: {
|
|
42357
|
-
siteId: { $bind: { from: "$root.siteId" } },
|
|
42358
|
-
limit: { $bind: {
|
|
42359
|
-
from: "content.maxEvents",
|
|
42360
|
-
fallback: "3"
|
|
42361
|
-
} },
|
|
42362
|
-
stage: { $bind: {
|
|
42363
|
-
from: "$root.previewStage",
|
|
42364
|
-
fallback: "published"
|
|
42365
|
-
} },
|
|
42366
|
-
detailLevel: "listing",
|
|
42367
|
-
readProfile: "spotlight"
|
|
42368
|
-
},
|
|
42369
|
-
mode: "server"
|
|
42370
|
-
} }
|
|
42371
|
-
};
|
|
42372
|
-
//#endregion
|
|
42373
|
-
//#region ../calendar/src/grid.ts
|
|
42374
|
-
const DAY_NAMES = [
|
|
42375
|
-
"Sun",
|
|
42376
|
-
"Mon",
|
|
42377
|
-
"Tue",
|
|
42378
|
-
"Wed",
|
|
42379
|
-
"Thu",
|
|
42380
|
-
"Fri",
|
|
42381
|
-
"Sat"
|
|
42382
|
-
];
|
|
42383
|
-
const MONTH_NAMES = [
|
|
42384
|
-
"January",
|
|
42385
|
-
"February",
|
|
42386
|
-
"March",
|
|
42387
|
-
"April",
|
|
42388
|
-
"May",
|
|
42389
|
-
"June",
|
|
42390
|
-
"July",
|
|
42391
|
-
"August",
|
|
42392
|
-
"September",
|
|
42393
|
-
"October",
|
|
42394
|
-
"November",
|
|
42395
|
-
"December"
|
|
42396
|
-
];
|
|
42397
|
-
function formatDateKey(date) {
|
|
42398
|
-
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
42399
|
-
}
|
|
42400
|
-
function isToday(date, today) {
|
|
42401
|
-
return isSameCalendarDay(date, today);
|
|
42402
|
-
}
|
|
42403
|
-
function isSameCalendarDay(left, right) {
|
|
42404
|
-
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
42405
|
-
}
|
|
42406
|
-
function addDays(date, days) {
|
|
42407
|
-
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
42408
|
-
return {
|
|
42409
|
-
year: result.getUTCFullYear(),
|
|
42410
|
-
month: result.getUTCMonth() + 1,
|
|
42411
|
-
day: result.getUTCDate()
|
|
42412
|
-
};
|
|
42413
|
-
}
|
|
42414
|
-
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
42415
|
-
const firstDay = {
|
|
42416
|
-
year,
|
|
42417
|
-
month,
|
|
42418
|
-
day: 1
|
|
42419
|
-
};
|
|
42420
|
-
const dayOfWeek = getDayOfWeek(firstDay);
|
|
42421
|
-
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
42422
|
-
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
42423
|
-
}
|
|
42424
|
-
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
42425
|
-
const dayOfWeek = getDayOfWeek(baseDate);
|
|
42426
|
-
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
42427
|
-
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
42428
|
-
}
|
|
42429
|
-
function getDayOfWeek(date) {
|
|
42430
|
-
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
42431
|
-
}
|
|
42432
|
-
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
42433
|
-
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
42434
|
-
assertWeekCalendarDays(days);
|
|
42435
|
-
return days;
|
|
42436
|
-
}
|
|
42437
|
-
function assertWeekCalendarDays(days) {
|
|
42438
|
-
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
42439
|
-
}
|
|
42440
|
-
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
42441
|
-
if (startOfWeek === "monday") {
|
|
42442
|
-
const sunday = dayNames[0] ?? "Sun";
|
|
42443
|
-
return [...dayNames.slice(1), sunday];
|
|
42444
|
-
}
|
|
42445
|
-
return [...dayNames];
|
|
42446
|
-
}
|
|
42447
|
-
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
42448
|
-
if (view === "week") {
|
|
42449
|
-
const weekStart = days[0];
|
|
42450
|
-
const weekEnd = days[6];
|
|
42451
|
-
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
42452
|
-
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
42453
|
-
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
42454
|
-
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
42455
|
-
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
42456
|
-
}
|
|
42457
|
-
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
42458
|
-
}
|
|
42459
|
-
function buildCalendarViewport(input) {
|
|
42460
|
-
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
42461
|
-
if (input.view === "week") {
|
|
42462
|
-
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
42463
|
-
return {
|
|
42464
|
-
kind: "week",
|
|
42465
|
-
anchorDate: input.anchorDate,
|
|
42466
|
-
days,
|
|
42467
|
-
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
42468
|
-
};
|
|
42469
|
-
}
|
|
42470
|
-
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
42471
|
-
return {
|
|
42472
|
-
kind: "month",
|
|
42473
|
-
anchorDate: input.anchorDate,
|
|
42474
|
-
days,
|
|
42475
|
-
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
42476
|
-
};
|
|
42477
|
-
}
|
|
42478
|
-
//#endregion
|
|
42479
|
-
//#region ../calendar/src/lane-packing.ts
|
|
42480
|
-
/**
|
|
42481
|
-
* Assign lanes to a list of intervals.
|
|
42482
|
-
*
|
|
42483
|
-
* Returns one result per input in the same order as the input. Internally
|
|
42484
|
-
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
42485
|
-
* permutes results back to input order.
|
|
42486
|
-
*/
|
|
42487
|
-
function assignLanes(intervals) {
|
|
42488
|
-
if (intervals.length === 0) return [];
|
|
42489
|
-
const indexed = intervals.map((interval, index) => ({
|
|
42490
|
-
interval,
|
|
42491
|
-
index
|
|
42492
|
-
}));
|
|
42493
|
-
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
42494
|
-
const results = new Array(intervals.length);
|
|
42495
|
-
let cluster = [];
|
|
42496
|
-
let activeLaneEnds = [];
|
|
42497
|
-
let clusterMaxEnd = -Infinity;
|
|
42498
|
-
const closeCluster = () => {
|
|
42499
|
-
if (cluster.length === 0) return;
|
|
42500
|
-
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
42501
|
-
for (const entry of cluster) results[entry.index] = {
|
|
42502
|
-
laneIndex: entry.laneIndex,
|
|
42503
|
-
laneCount
|
|
42504
|
-
};
|
|
42505
|
-
cluster = [];
|
|
42506
|
-
activeLaneEnds = [];
|
|
42507
|
-
clusterMaxEnd = -Infinity;
|
|
42508
|
-
};
|
|
42509
|
-
for (const { interval, index } of indexed) {
|
|
42510
|
-
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
42511
|
-
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
42512
|
-
activeLaneEnds[laneIndex] = interval.end;
|
|
42513
|
-
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
42514
|
-
cluster.push({
|
|
42515
|
-
index,
|
|
42516
|
-
laneIndex
|
|
42517
|
-
});
|
|
42518
|
-
}
|
|
42519
|
-
closeCluster();
|
|
42520
|
-
return results;
|
|
42521
|
-
}
|
|
42522
|
-
function firstAvailableLane(activeLaneEnds, start) {
|
|
42523
|
-
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
42524
|
-
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
42525
|
-
}
|
|
42526
|
-
//#endregion
|
|
42527
|
-
//#region ../calendar/src/timezone.ts
|
|
42528
|
-
/**
|
|
42529
|
-
* Timezone boundary helpers — internal.
|
|
42530
|
-
*
|
|
42531
|
-
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
42532
|
-
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
42533
|
-
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
42534
|
-
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
42535
|
-
* public surface.
|
|
42536
|
-
*/
|
|
42537
|
-
const MINUTES_PER_HOUR = 60;
|
|
42538
|
-
/**
|
|
42539
|
-
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
42540
|
-
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
42541
|
-
*/
|
|
42542
|
-
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
42543
|
-
const date = new Date(isoTimestamp);
|
|
42544
|
-
if (Number.isNaN(date.getTime())) return NaN;
|
|
42545
|
-
if (!isValidTimeZone(timeZone)) return NaN;
|
|
42546
|
-
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
42547
|
-
timeZone,
|
|
42548
|
-
hour: "2-digit",
|
|
42549
|
-
minute: "2-digit",
|
|
42550
|
-
hourCycle: "h23"
|
|
42551
|
-
}).formatToParts(date);
|
|
42552
|
-
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
42553
|
-
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
42554
|
-
const hour = hourPart === 24 ? 0 : hourPart;
|
|
42555
|
-
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
42556
|
-
return hour * MINUTES_PER_HOUR + minute;
|
|
42557
|
-
}
|
|
42558
|
-
/**
|
|
42559
|
-
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
42560
|
-
* Returns null if the inputs cannot be parsed.
|
|
42561
|
-
*/
|
|
42562
|
-
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
42563
|
-
const date = new Date(isoTimestamp);
|
|
42564
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
42565
|
-
if (!isValidTimeZone(timeZone)) return null;
|
|
42566
|
-
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
42567
|
-
timeZone,
|
|
42568
|
-
year: "numeric",
|
|
42569
|
-
month: "2-digit",
|
|
42570
|
-
day: "2-digit"
|
|
42571
|
-
}).formatToParts(date);
|
|
42572
|
-
const year = parts.find((part) => part.type === "year")?.value;
|
|
42573
|
-
const month = parts.find((part) => part.type === "month")?.value;
|
|
42574
|
-
const day = parts.find((part) => part.type === "day")?.value;
|
|
42575
|
-
if (year === void 0 || year.length === 0) return null;
|
|
42576
|
-
if (month === void 0 || month.length === 0) return null;
|
|
42577
|
-
if (day === void 0 || day.length === 0) return null;
|
|
42578
|
-
return `${year}-${month}-${day}`;
|
|
42579
|
-
}
|
|
42580
|
-
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
42581
|
-
function isValidTimeZone(timeZone) {
|
|
42582
|
-
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
42583
|
-
const cached = validTimeZoneCache.get(timeZone);
|
|
42584
|
-
if (cached !== void 0) return cached;
|
|
42585
|
-
try {
|
|
42586
|
-
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
42587
|
-
validTimeZoneCache.set(timeZone, true);
|
|
42588
|
-
return true;
|
|
42589
|
-
} catch {
|
|
42590
|
-
validTimeZoneCache.set(timeZone, false);
|
|
42591
|
-
return false;
|
|
42592
|
-
}
|
|
42593
|
-
}
|
|
42594
|
-
//#endregion
|
|
42595
|
-
//#region ../calendar/src/timetable.ts
|
|
42596
|
-
/**
|
|
42597
|
-
* Timetable model — the package's core surface.
|
|
42598
|
-
*
|
|
42599
|
-
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
42600
|
-
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
42601
|
-
* plus a diagnostics list for items dropped during processing.
|
|
42602
|
-
*
|
|
42603
|
-
* The function is pure: given identical input (including `now`), it returns
|
|
42604
|
-
* the same output. The model has no opinion on what an item is — pass
|
|
42605
|
-
* accessors.
|
|
42606
|
-
*/
|
|
42607
|
-
const DEFAULT_BOUNDS = {
|
|
42608
|
-
minStartHour: 5,
|
|
42609
|
-
maxEndHour: 23,
|
|
42610
|
-
defaultStartHour: 6,
|
|
42611
|
-
defaultEndHour: 21,
|
|
42612
|
-
minHeightPercent: 2.5
|
|
43599
|
+
baseClass: "",
|
|
43600
|
+
spacing: "comfortable"
|
|
43601
|
+
})]
|
|
43602
|
+
}),
|
|
43603
|
+
dataSchemas: { events: publicEventsArraySchema.optional() },
|
|
43604
|
+
dataLoaders: { events: {
|
|
43605
|
+
endpoint: "listPublicEvents",
|
|
43606
|
+
params: {
|
|
43607
|
+
siteId: { $bind: { from: "$root.siteId" } },
|
|
43608
|
+
limit: { $bind: {
|
|
43609
|
+
from: "content.maxEvents",
|
|
43610
|
+
fallback: "3"
|
|
43611
|
+
} },
|
|
43612
|
+
stage: { $bind: {
|
|
43613
|
+
from: "$root.previewStage",
|
|
43614
|
+
fallback: "published"
|
|
43615
|
+
} },
|
|
43616
|
+
detailLevel: "listing",
|
|
43617
|
+
readProfile: "spotlight"
|
|
43618
|
+
},
|
|
43619
|
+
mode: "server"
|
|
43620
|
+
} }
|
|
42613
43621
|
};
|
|
42614
|
-
function buildTimetableModel$1(input) {
|
|
42615
|
-
const bounds = resolveBounds(input.bounds);
|
|
42616
|
-
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
42617
|
-
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
42618
|
-
const parsed = parseAllItems(input.items, input);
|
|
42619
|
-
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
42620
|
-
const hourRange = resolveHourRange(inViewport, bounds);
|
|
42621
|
-
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
42622
|
-
return {
|
|
42623
|
-
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
42624
|
-
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
42625
|
-
startHour: hourRange.startHour,
|
|
42626
|
-
endHour: hourRange.endHour,
|
|
42627
|
-
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
42628
|
-
hasItems: inViewport.length > 0,
|
|
42629
|
-
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
42630
|
-
};
|
|
42631
|
-
}
|
|
42632
|
-
function parseAllItems(items, accessors) {
|
|
42633
|
-
const drafts = [];
|
|
42634
|
-
const diagnostics = [];
|
|
42635
|
-
for (const item of items) {
|
|
42636
|
-
const result = parseDraft(item, accessors);
|
|
42637
|
-
if (result.ok) drafts.push(result.draft);
|
|
42638
|
-
else diagnostics.push({
|
|
42639
|
-
item,
|
|
42640
|
-
reason: result.reason
|
|
42641
|
-
});
|
|
42642
|
-
}
|
|
42643
|
-
return {
|
|
42644
|
-
drafts,
|
|
42645
|
-
diagnostics
|
|
42646
|
-
};
|
|
42647
|
-
}
|
|
42648
|
-
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
42649
|
-
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
42650
|
-
const rangeStartMinutes = hourRange.startHour * 60;
|
|
42651
|
-
const rangeEndMinutes = hourRange.endHour * 60;
|
|
42652
|
-
const segmentsByDate = groupSegmentsByDate(segments);
|
|
42653
|
-
const cellsByDate = /* @__PURE__ */ new Map();
|
|
42654
|
-
const diagnostics = [];
|
|
42655
|
-
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
42656
|
-
const clipped = [];
|
|
42657
|
-
for (const segment of daySegments) {
|
|
42658
|
-
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
42659
|
-
if (result.ok) clipped.push(result.clipped);
|
|
42660
|
-
else diagnostics.push({
|
|
42661
|
-
item: segment.draft.item,
|
|
42662
|
-
reason: result.reason
|
|
42663
|
-
});
|
|
42664
|
-
}
|
|
42665
|
-
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
42666
|
-
start: entry.visibleStart,
|
|
42667
|
-
end: entry.visibleEnd,
|
|
42668
|
-
orderKey: entry.segment.draft.orderKey
|
|
42669
|
-
})));
|
|
42670
|
-
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
42671
|
-
laneIndex: 0,
|
|
42672
|
-
laneCount: 1
|
|
42673
|
-
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
42674
|
-
}
|
|
42675
|
-
return {
|
|
42676
|
-
cellsByDate,
|
|
42677
|
-
diagnostics
|
|
42678
|
-
};
|
|
42679
|
-
}
|
|
42680
|
-
function assembleDays(viewportDays, cellsByDate, today) {
|
|
42681
|
-
return viewportDays.map((date) => {
|
|
42682
|
-
const dateKey = formatDateKey(date);
|
|
42683
|
-
return {
|
|
42684
|
-
date,
|
|
42685
|
-
dateKey,
|
|
42686
|
-
isToday: isSameCalendarDay(date, today),
|
|
42687
|
-
cells: cellsByDate.get(dateKey) ?? []
|
|
42688
|
-
};
|
|
42689
|
-
});
|
|
42690
|
-
}
|
|
42691
|
-
function parseDraft(item, accessors) {
|
|
42692
|
-
const startsAtIso = accessors.getStartsAt(item);
|
|
42693
|
-
const endsAtIso = accessors.getEndsAt(item);
|
|
42694
|
-
const timeZone = accessors.getTimeZone(item);
|
|
42695
|
-
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
42696
|
-
if (Number.isNaN(startsAtMinutes)) return {
|
|
42697
|
-
ok: false,
|
|
42698
|
-
reason: "invalid-start"
|
|
42699
|
-
};
|
|
42700
|
-
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
42701
|
-
if (startDateKey == null) return {
|
|
42702
|
-
ok: false,
|
|
42703
|
-
reason: "invalid-timezone"
|
|
42704
|
-
};
|
|
42705
|
-
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
42706
|
-
if (endDateKey == null) return {
|
|
42707
|
-
ok: false,
|
|
42708
|
-
reason: "invalid-end"
|
|
42709
|
-
};
|
|
42710
|
-
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
42711
|
-
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
42712
|
-
ok: false,
|
|
42713
|
-
reason: "invalid-end"
|
|
42714
|
-
};
|
|
42715
|
-
const spansMultipleDays = startDateKey < endDateKey;
|
|
42716
|
-
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
42717
|
-
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
42718
|
-
ok: false,
|
|
42719
|
-
reason: "non-positive-duration"
|
|
42720
|
-
};
|
|
42721
|
-
return {
|
|
42722
|
-
ok: true,
|
|
42723
|
-
draft: {
|
|
42724
|
-
item,
|
|
42725
|
-
startDateKey,
|
|
42726
|
-
endDateKey,
|
|
42727
|
-
startsAtMinutes,
|
|
42728
|
-
endsAtMinutes,
|
|
42729
|
-
color: accessors.getColor?.(item) ?? null,
|
|
42730
|
-
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
42731
|
-
}
|
|
42732
|
-
};
|
|
42733
|
-
}
|
|
42734
|
-
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
42735
|
-
const segments = [];
|
|
42736
|
-
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
42737
|
-
if (!viewportKeys.has(dateKey)) continue;
|
|
42738
|
-
const isStartDay = dateKey === draft.startDateKey;
|
|
42739
|
-
const isEndDay = dateKey === draft.endDateKey;
|
|
42740
|
-
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
42741
|
-
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
42742
|
-
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
42743
|
-
segments.push({
|
|
42744
|
-
draft,
|
|
42745
|
-
dateKey,
|
|
42746
|
-
startsAtMinutes,
|
|
42747
|
-
endsAtMinutes,
|
|
42748
|
-
continuesBefore: !isStartDay,
|
|
42749
|
-
continuesAfter: !isEndDay
|
|
42750
|
-
});
|
|
42751
|
-
}
|
|
42752
|
-
return segments;
|
|
42753
|
-
}
|
|
42754
|
-
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
42755
|
-
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
42756
|
-
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
42757
|
-
if (visibleEnd <= visibleStart) return {
|
|
42758
|
-
ok: false,
|
|
42759
|
-
reason: "out-of-visible-hours"
|
|
42760
|
-
};
|
|
42761
|
-
return {
|
|
42762
|
-
ok: true,
|
|
42763
|
-
clipped: {
|
|
42764
|
-
segment,
|
|
42765
|
-
visibleStart,
|
|
42766
|
-
visibleEnd
|
|
42767
|
-
}
|
|
42768
|
-
};
|
|
42769
|
-
}
|
|
42770
|
-
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
42771
|
-
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
42772
|
-
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
42773
|
-
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
42774
|
-
return {
|
|
42775
|
-
item: entry.segment.draft.item,
|
|
42776
|
-
dateKey: entry.segment.dateKey,
|
|
42777
|
-
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
42778
|
-
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
42779
|
-
visibleStartsAtMinutes: entry.visibleStart,
|
|
42780
|
-
visibleEndsAtMinutes: entry.visibleEnd,
|
|
42781
|
-
topPercent,
|
|
42782
|
-
heightPercent,
|
|
42783
|
-
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
42784
|
-
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
42785
|
-
laneIndex: lane.laneIndex,
|
|
42786
|
-
laneCount: lane.laneCount,
|
|
42787
|
-
color: entry.segment.draft.color
|
|
42788
|
-
};
|
|
42789
|
-
}
|
|
42790
|
-
function resolveBounds(bounds) {
|
|
42791
|
-
return {
|
|
42792
|
-
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
42793
|
-
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
42794
|
-
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
42795
|
-
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
42796
|
-
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
42797
|
-
};
|
|
42798
|
-
}
|
|
42799
|
-
function resolveHourRange(segments, bounds) {
|
|
42800
|
-
if (segments.length === 0) return {
|
|
42801
|
-
startHour: bounds.defaultStartHour,
|
|
42802
|
-
endHour: bounds.defaultEndHour
|
|
42803
|
-
};
|
|
42804
|
-
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
42805
|
-
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
42806
|
-
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
42807
|
-
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
42808
|
-
return {
|
|
42809
|
-
startHour,
|
|
42810
|
-
endHour: Math.max(startHour + 1, endHour)
|
|
42811
|
-
};
|
|
42812
|
-
}
|
|
42813
|
-
function buildHourTicks(startHour, endHour) {
|
|
42814
|
-
const ticks = [];
|
|
42815
|
-
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
42816
|
-
return ticks;
|
|
42817
|
-
}
|
|
42818
|
-
function groupSegmentsByDate(segments) {
|
|
42819
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
42820
|
-
for (const segment of segments) {
|
|
42821
|
-
const existing = grouped.get(segment.dateKey) ?? [];
|
|
42822
|
-
existing.push(segment);
|
|
42823
|
-
grouped.set(segment.dateKey, existing);
|
|
42824
|
-
}
|
|
42825
|
-
return grouped;
|
|
42826
|
-
}
|
|
42827
|
-
function toRangePercent(minutes, rangeMinutes) {
|
|
42828
|
-
if (rangeMinutes <= 0) return 0;
|
|
42829
|
-
return minutes / rangeMinutes * 100;
|
|
42830
|
-
}
|
|
42831
|
-
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
42832
|
-
if (view === "day") return [viewDate];
|
|
42833
|
-
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
42834
|
-
}
|
|
42835
|
-
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
42836
|
-
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
42837
|
-
const start = parseDateKey(startDateKey);
|
|
42838
|
-
parseDateKey(endDateKey);
|
|
42839
|
-
const keys = [];
|
|
42840
|
-
let cursor = start;
|
|
42841
|
-
while (formatDateKey(cursor) <= endDateKey) {
|
|
42842
|
-
keys.push(formatDateKey(cursor));
|
|
42843
|
-
cursor = addDays(cursor, 1);
|
|
42844
|
-
}
|
|
42845
|
-
return keys;
|
|
42846
|
-
}
|
|
42847
|
-
function parseDateKey(dateKey) {
|
|
42848
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
42849
|
-
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
42850
|
-
const [, year, month, day] = match;
|
|
42851
|
-
return {
|
|
42852
|
-
year: Number(year),
|
|
42853
|
-
month: Number(month),
|
|
42854
|
-
day: Number(day)
|
|
42855
|
-
};
|
|
42856
|
-
}
|
|
42857
|
-
//#endregion
|
|
42858
|
-
//#region ../calendar/src/group.ts
|
|
42859
|
-
/**
|
|
42860
|
-
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
42861
|
-
*
|
|
42862
|
-
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
42863
|
-
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
42864
|
-
*
|
|
42865
|
-
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
42866
|
-
* relative order of items within each date matches input order.
|
|
42867
|
-
*/
|
|
42868
|
-
function groupItemsByDate(items, accessors) {
|
|
42869
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
42870
|
-
for (const item of items) {
|
|
42871
|
-
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
42872
|
-
if (dateKey == null) continue;
|
|
42873
|
-
const existing = grouped.get(dateKey);
|
|
42874
|
-
if (existing !== void 0) existing.push(item);
|
|
42875
|
-
else grouped.set(dateKey, [item]);
|
|
42876
|
-
}
|
|
42877
|
-
return grouped;
|
|
42878
|
-
}
|
|
42879
|
-
//#endregion
|
|
42880
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/eventCapacity.ts
|
|
42881
|
-
/**
|
|
42882
|
-
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
42883
|
-
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
42884
|
-
* for uncapped events so call sites can render no capacity indicator at all.
|
|
42885
|
-
*
|
|
42886
|
-
* The single source of truth for capacity math across event cards, agenda
|
|
42887
|
-
* rows, and timetable cells — keep wording variations in the call sites.
|
|
42888
|
-
*/
|
|
42889
|
-
function getEventAvailability(event) {
|
|
42890
|
-
if (event.capacity == null) return {
|
|
42891
|
-
available: null,
|
|
42892
|
-
isFull: false
|
|
42893
|
-
};
|
|
42894
|
-
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
42895
|
-
if (available <= 0) return {
|
|
42896
|
-
available: 0,
|
|
42897
|
-
isFull: true
|
|
42898
|
-
};
|
|
42899
|
-
return {
|
|
42900
|
-
available,
|
|
42901
|
-
isFull: false
|
|
42902
|
-
};
|
|
42903
|
-
}
|
|
42904
|
-
/**
|
|
42905
|
-
* Derive the three-state availability status (available / filling / waitlist)
|
|
42906
|
-
* plus a short pill label. Builds on {@link getEventAvailability} so the
|
|
42907
|
-
* capacity math (availableSpots with a `capacity - registeredCount` fallback)
|
|
42908
|
-
* stays consistent with the binary helper used elsewhere.
|
|
42909
|
-
*/
|
|
42910
|
-
function getEventAvailabilityStatus(event, options) {
|
|
42911
|
-
const threshold = options?.fillingThreshold ?? 2;
|
|
42912
|
-
const { available, isFull } = getEventAvailability(event);
|
|
42913
|
-
if (available == null) return {
|
|
42914
|
-
status: "available",
|
|
42915
|
-
available: null,
|
|
42916
|
-
isFull: false,
|
|
42917
|
-
label: null
|
|
42918
|
-
};
|
|
42919
|
-
if (isFull) return {
|
|
42920
|
-
status: "waitlist",
|
|
42921
|
-
available: 0,
|
|
42922
|
-
isFull: true,
|
|
42923
|
-
label: event.waitlistEnabled ? "Waitlist" : "Full"
|
|
42924
|
-
};
|
|
42925
|
-
if (available <= threshold) return {
|
|
42926
|
-
status: "filling",
|
|
42927
|
-
available,
|
|
42928
|
-
isFull: false,
|
|
42929
|
-
label: `${available} left`
|
|
42930
|
-
};
|
|
42931
|
-
return {
|
|
42932
|
-
status: "available",
|
|
42933
|
-
available,
|
|
42934
|
-
isFull: false,
|
|
42935
|
-
label: `${available} places`
|
|
42936
|
-
};
|
|
42937
|
-
}
|
|
42938
43622
|
//#endregion
|
|
42939
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/
|
|
43623
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/listingLayout.ts
|
|
42940
43624
|
/**
|
|
42941
|
-
*
|
|
43625
|
+
* Layout classes for event listing containers and cards. Presentation
|
|
43626
|
+
* plumbing rather than event domain logic, so it lives beside the components.
|
|
42942
43627
|
*/
|
|
42943
43628
|
/**
|
|
42944
43629
|
* Column class mappings for grid layouts, derived from the canonical
|
|
@@ -42973,180 +43658,6 @@ function getCardOrientation(layout) {
|
|
|
42973
43658
|
case "compact": return "vertical";
|
|
42974
43659
|
}
|
|
42975
43660
|
}
|
|
42976
|
-
/**
|
|
42977
|
-
* Format date for display.
|
|
42978
|
-
* Returns: "Monday, January 15, 2025"
|
|
42979
|
-
*/
|
|
42980
|
-
function formatDate$2(isoTimestamp, options) {
|
|
42981
|
-
return formatBookingDate(toScheduledBookingDateTime({
|
|
42982
|
-
startsAt: isoTimestamp,
|
|
42983
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
42984
|
-
locale: options?.locale ?? "en-US"
|
|
42985
|
-
}), {
|
|
42986
|
-
weekday: "long",
|
|
42987
|
-
year: "numeric",
|
|
42988
|
-
month: "long",
|
|
42989
|
-
day: "numeric"
|
|
42990
|
-
});
|
|
42991
|
-
}
|
|
42992
|
-
/**
|
|
42993
|
-
* Format time for display.
|
|
42994
|
-
* Returns: "10:00 AM"
|
|
42995
|
-
*/
|
|
42996
|
-
function formatTime(isoTimestamp, options) {
|
|
42997
|
-
return formatBookingTime(toScheduledBookingDateTime({
|
|
42998
|
-
startsAt: isoTimestamp,
|
|
42999
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
43000
|
-
locale: options?.locale ?? "en-US"
|
|
43001
|
-
}), {
|
|
43002
|
-
hour: "numeric",
|
|
43003
|
-
minute: "2-digit",
|
|
43004
|
-
hour12: true
|
|
43005
|
-
});
|
|
43006
|
-
}
|
|
43007
|
-
function formatEventDateParts(args) {
|
|
43008
|
-
const scheduledRange = toScheduledBookingDateRange({
|
|
43009
|
-
startsAt: args.startsAt,
|
|
43010
|
-
endsAt: args.endsAt,
|
|
43011
|
-
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
43012
|
-
locale: args.locale ?? "en-US"
|
|
43013
|
-
});
|
|
43014
|
-
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
43015
|
-
weekday: "long",
|
|
43016
|
-
year: "numeric",
|
|
43017
|
-
month: "long",
|
|
43018
|
-
day: "numeric"
|
|
43019
|
-
} });
|
|
43020
|
-
return {
|
|
43021
|
-
date,
|
|
43022
|
-
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
43023
|
-
startsAt: args.endsAt,
|
|
43024
|
-
timeZone: scheduledRange.timeZone,
|
|
43025
|
-
locale: args.locale ?? "en-US"
|
|
43026
|
-
}))}`
|
|
43027
|
-
};
|
|
43028
|
-
}
|
|
43029
|
-
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
43030
|
-
const dateTime = toScheduledBookingDateTime({
|
|
43031
|
-
startsAt: isoTimestamp,
|
|
43032
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
43033
|
-
locale: options?.locale ?? "en-US"
|
|
43034
|
-
});
|
|
43035
|
-
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
43036
|
-
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
43037
|
-
return {
|
|
43038
|
-
weekday,
|
|
43039
|
-
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
43040
|
-
month,
|
|
43041
|
-
label: formatBookingDate(dateTime, {
|
|
43042
|
-
weekday: "long",
|
|
43043
|
-
year: "numeric",
|
|
43044
|
-
month: "long",
|
|
43045
|
-
day: "numeric"
|
|
43046
|
-
})
|
|
43047
|
-
};
|
|
43048
|
-
}
|
|
43049
|
-
function formatTeamMemberNames(names) {
|
|
43050
|
-
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
43051
|
-
if (normalized.length === 0) return null;
|
|
43052
|
-
const [firstName, secondName] = normalized;
|
|
43053
|
-
if (!firstName) return null;
|
|
43054
|
-
if (normalized.length === 1) return firstName;
|
|
43055
|
-
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
43056
|
-
const finalName = normalized[normalized.length - 1] ?? "";
|
|
43057
|
-
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
43058
|
-
}
|
|
43059
|
-
function formatEventTeacherLine(event) {
|
|
43060
|
-
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
43061
|
-
return names ? `with ${names}` : null;
|
|
43062
|
-
}
|
|
43063
|
-
/**
|
|
43064
|
-
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
43065
|
-
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
43066
|
-
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
43067
|
-
*/
|
|
43068
|
-
function groupEventsByDate(events, calendarTimeZone) {
|
|
43069
|
-
const grouped = groupItemsByDate(events, {
|
|
43070
|
-
getStartsAt: (event) => event.startsAt,
|
|
43071
|
-
getTimeZone: () => calendarTimeZone
|
|
43072
|
-
});
|
|
43073
|
-
const map = /* @__PURE__ */ new Map();
|
|
43074
|
-
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
43075
|
-
return map;
|
|
43076
|
-
}
|
|
43077
|
-
/**
|
|
43078
|
-
* Format event price for display.
|
|
43079
|
-
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
43080
|
-
*
|
|
43081
|
-
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
43082
|
-
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
43083
|
-
* surfaces stay consistent.
|
|
43084
|
-
*/
|
|
43085
|
-
function formatEventPrice(event) {
|
|
43086
|
-
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
43087
|
-
}
|
|
43088
|
-
function closedEventBookingLabel(reason) {
|
|
43089
|
-
switch (reason) {
|
|
43090
|
-
case "occurrence_cancelled":
|
|
43091
|
-
case "series_cancelled": return "Cancelled";
|
|
43092
|
-
case "occurrence_completed":
|
|
43093
|
-
case "series_completed": return "Completed";
|
|
43094
|
-
case "series_draft":
|
|
43095
|
-
case "series_archived":
|
|
43096
|
-
case "parent_archived":
|
|
43097
|
-
case "parent_inconsistent":
|
|
43098
|
-
case "not_public":
|
|
43099
|
-
case "already_started":
|
|
43100
|
-
case "enrollment_closed": return "Registration closed";
|
|
43101
|
-
default: return assertNever$24(reason);
|
|
43102
|
-
}
|
|
43103
|
-
}
|
|
43104
|
-
function resolveEventHref(event) {
|
|
43105
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
43106
|
-
return destination.kind === "external" ? destination.url : destination.path;
|
|
43107
|
-
}
|
|
43108
|
-
function resolveEventLinkProps(event) {
|
|
43109
|
-
const href = resolveEventHref(event);
|
|
43110
|
-
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
43111
|
-
href,
|
|
43112
|
-
target: "_blank",
|
|
43113
|
-
rel: "noopener noreferrer"
|
|
43114
|
-
} : { href };
|
|
43115
|
-
}
|
|
43116
|
-
/**
|
|
43117
|
-
* Resolve the CTA state for an event.
|
|
43118
|
-
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
43119
|
-
* and course-specific defaults consistent.
|
|
43120
|
-
*/
|
|
43121
|
-
function resolveEventCta(event, buttonText) {
|
|
43122
|
-
const { isFull: isSoldOut } = getEventAvailability(event);
|
|
43123
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
43124
|
-
const isExternal = destination.kind === "external";
|
|
43125
|
-
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
43126
|
-
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
43127
|
-
const href = resolveEventHref(event);
|
|
43128
|
-
if (event.bookingAvailability.kind === "closed") {
|
|
43129
|
-
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
43130
|
-
return {
|
|
43131
|
-
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
43132
|
-
href,
|
|
43133
|
-
external: isExternal,
|
|
43134
|
-
hidden: !linksToClosedWholeSeriesDetails
|
|
43135
|
-
};
|
|
43136
|
-
}
|
|
43137
|
-
if (!isExternal && isSoldOut) return {
|
|
43138
|
-
label: "Sold out",
|
|
43139
|
-
href,
|
|
43140
|
-
external: false,
|
|
43141
|
-
hidden: true
|
|
43142
|
-
};
|
|
43143
|
-
return {
|
|
43144
|
-
label: effectiveButtonText,
|
|
43145
|
-
href,
|
|
43146
|
-
external: isExternal,
|
|
43147
|
-
hidden: false
|
|
43148
|
-
};
|
|
43149
|
-
}
|
|
43150
43661
|
//#endregion
|
|
43151
43662
|
//#region ../blocks/src/system/media/slotContracts.ts
|
|
43152
43663
|
const DEFAULT_MIN_SOURCE_WIDTH = 1200;
|
|
@@ -44417,7 +44928,6 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
44417
44928
|
const formattedDate = formatDate$2(event.startsAt, { timeZone });
|
|
44418
44929
|
const formattedTime = `${formatTime(event.startsAt, { timeZone })} - ${formatTime(event.endsAt, { timeZone })}`;
|
|
44419
44930
|
const cta = resolveEventCta(event, buttonText);
|
|
44420
|
-
const { available: spotsLeft, isFull: isSoldOut } = getEventAvailability(event);
|
|
44421
44931
|
const cardClass = boxStyleClassNames({ role: cardRole }, { baseClass: "event-card" });
|
|
44422
44932
|
const buttonClass = themeButtonClassName({
|
|
44423
44933
|
variant: buttonVariant,
|
|
@@ -44433,10 +44943,7 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
44433
44943
|
} : null;
|
|
44434
44944
|
const hasImage = Boolean(showImage && image);
|
|
44435
44945
|
const teacherLine = formatEventTeacherLine(event);
|
|
44436
|
-
const capacityText = showCapacity ?
|
|
44437
|
-
isSoldOut,
|
|
44438
|
-
spotsLeft
|
|
44439
|
-
}) : null;
|
|
44946
|
+
const capacityText = showCapacity ? formatEventRemainingPlaces(event, { full: "Sold out" }) : null;
|
|
44440
44947
|
return /* @__PURE__ */ jsxs("article", {
|
|
44441
44948
|
className: `${cardClass} group rb-flex rb-flex-col rb-h-full ${eventCardOrientationClass(orientation)}`,
|
|
44442
44949
|
"data-orientation": orientation,
|
|
@@ -44502,7 +45009,11 @@ function EventCardContent(props) {
|
|
|
44502
45009
|
}),
|
|
44503
45010
|
/* @__PURE__ */ jsx("h3", {
|
|
44504
45011
|
className: "card-title rb-mb-3",
|
|
44505
|
-
children:
|
|
45012
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
45013
|
+
className: "event-title-link",
|
|
45014
|
+
...resolveEventLinkProps(event),
|
|
45015
|
+
children: props.title
|
|
45016
|
+
})
|
|
44506
45017
|
}),
|
|
44507
45018
|
/* @__PURE__ */ jsx("div", {
|
|
44508
45019
|
className: "rb-mb-2 rb-caption",
|
|
@@ -44579,13 +45090,6 @@ function eventCardOrientationClass(orientation) {
|
|
|
44579
45090
|
case "vertical": return "event-card--vertical";
|
|
44580
45091
|
}
|
|
44581
45092
|
}
|
|
44582
|
-
function formatCapacityText(event, options) {
|
|
44583
|
-
if (event.capacity == null) return null;
|
|
44584
|
-
if (options.isSoldOut) return "Sold out";
|
|
44585
|
-
if (options.spotsLeft == null) return null;
|
|
44586
|
-
const count = options.spotsLeft;
|
|
44587
|
-
return `${count} ${count === 1 ? "spot" : "spots"} available`;
|
|
44588
|
-
}
|
|
44589
45093
|
function getCategoryBadgeStyle(background) {
|
|
44590
45094
|
const normalized = background?.trim() ?? "";
|
|
44591
45095
|
if (!normalized) return {
|
|
@@ -44654,7 +45158,11 @@ const EventCompactRow = ({ event, buttonVariant = "primary", buttonText }) => {
|
|
|
44654
45158
|
className: "event-compact-row-content",
|
|
44655
45159
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
44656
45160
|
className: "event-compact-row-title",
|
|
44657
|
-
children:
|
|
45161
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
45162
|
+
className: "event-title-link",
|
|
45163
|
+
...resolveEventLinkProps(event),
|
|
45164
|
+
children: event.title
|
|
45165
|
+
})
|
|
44658
45166
|
}), /* @__PURE__ */ jsxs("div", {
|
|
44659
45167
|
className: "event-compact-row-meta",
|
|
44660
45168
|
children: [
|
|
@@ -44743,6 +45251,23 @@ function DateTimeTile({ startsAt, endsAt, timeZone }) {
|
|
|
44743
45251
|
});
|
|
44744
45252
|
}
|
|
44745
45253
|
//#endregion
|
|
45254
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/OfferingTitle.tsx
|
|
45255
|
+
/**
|
|
45256
|
+
* Heading for an offering card or row. Links to the offering's page when one
|
|
45257
|
+
* exists so a sold-out or closed offering stays reachable (issue 1372); the
|
|
45258
|
+
* `.event-title-link` rule lives in theme-core's event-list.css.
|
|
45259
|
+
*/
|
|
45260
|
+
function OfferingTitle({ className, path, title }) {
|
|
45261
|
+
return /* @__PURE__ */ jsx("h3", {
|
|
45262
|
+
className,
|
|
45263
|
+
children: path ? /* @__PURE__ */ jsx("a", {
|
|
45264
|
+
className: "event-title-link",
|
|
45265
|
+
href: path,
|
|
45266
|
+
children: title
|
|
45267
|
+
}) : title
|
|
45268
|
+
});
|
|
45269
|
+
}
|
|
45270
|
+
//#endregion
|
|
44746
45271
|
//#region ../blocks/src/system/runtime/nodes/events/shared/listOptions.ts
|
|
44747
45272
|
const eventPageSizeValues = [
|
|
44748
45273
|
3,
|
|
@@ -46087,7 +46612,7 @@ const eventCalendarAdditionalFields = [
|
|
|
46087
46612
|
{
|
|
46088
46613
|
id: "showCapacity",
|
|
46089
46614
|
type: "boolean",
|
|
46090
|
-
label: "Show available
|
|
46615
|
+
label: "Show available places",
|
|
46091
46616
|
description: "Display remaining capacity on event cards and timetable cells",
|
|
46092
46617
|
required: false,
|
|
46093
46618
|
defaultValue: true,
|
|
@@ -55764,7 +56289,7 @@ function EventFacts({ selection, selected, venue, teamMembers, pricing, showCapa
|
|
|
55764
56289
|
children: [/* @__PURE__ */ jsx("div", {
|
|
55765
56290
|
className: "rb-label rb-mb-2",
|
|
55766
56291
|
children: "Places"
|
|
55767
|
-
}), /* @__PURE__ */
|
|
56292
|
+
}), /* @__PURE__ */ jsx("p", { children: formatTotalCapacity(selected.capacity) })]
|
|
55768
56293
|
}) : null,
|
|
55769
56294
|
pricing,
|
|
55770
56295
|
selected.bookingScope === "whole_series" ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -56608,9 +57133,10 @@ function CourseCompactRow({ course, path }) {
|
|
|
56608
57133
|
}),
|
|
56609
57134
|
/* @__PURE__ */ jsxs("div", {
|
|
56610
57135
|
className: "event-compact-row-content",
|
|
56611
|
-
children: [/* @__PURE__ */ jsx(
|
|
57136
|
+
children: [/* @__PURE__ */ jsx(OfferingTitle, {
|
|
56612
57137
|
className: "event-compact-row-title",
|
|
56613
|
-
|
|
57138
|
+
path,
|
|
57139
|
+
title: course.title
|
|
56614
57140
|
}), /* @__PURE__ */ jsxs("div", {
|
|
56615
57141
|
className: "event-compact-row-meta",
|
|
56616
57142
|
children: [
|
|
@@ -56667,9 +57193,10 @@ function CourseFeatureCard({ course, path, buttonVariant, buttonText }) {
|
|
|
56667
57193
|
children: /* @__PURE__ */ jsxs("div", {
|
|
56668
57194
|
className: "card-content rb-flex rb-flex-1 rb-flex-col",
|
|
56669
57195
|
children: [
|
|
56670
|
-
/* @__PURE__ */ jsx(
|
|
57196
|
+
/* @__PURE__ */ jsx(OfferingTitle, {
|
|
56671
57197
|
className: "card-title rb-mb-3",
|
|
56672
|
-
|
|
57198
|
+
path,
|
|
57199
|
+
title: course.title
|
|
56673
57200
|
}),
|
|
56674
57201
|
/* @__PURE__ */ jsxs("div", {
|
|
56675
57202
|
className: "rb-mb-2 rb-caption",
|
|
@@ -57525,8 +58052,8 @@ const AgendaEventCard = ({ event, showCapacity, onClick }) => {
|
|
|
57525
58052
|
const eventLinkProps = resolveEventLinkProps(event);
|
|
57526
58053
|
const categoryColor = event.eventCategory?.color || "rgb(var(--tb-accent))";
|
|
57527
58054
|
const price = formatEventPrice(event);
|
|
57528
|
-
const
|
|
57529
|
-
const
|
|
58055
|
+
const capacity = showCapacity ? formatEventRemainingPlaces(event) : null;
|
|
58056
|
+
const isFull = isEventFull(event);
|
|
57530
58057
|
const title = event.title;
|
|
57531
58058
|
const timeZone = event.timeZone ?? null;
|
|
57532
58059
|
const teacherLine = formatEventTeacherLine(event);
|
|
@@ -57968,7 +58495,7 @@ function buildTimetableModel(input) {
|
|
|
57968
58495
|
getEndsAt: (event) => event.endsAt,
|
|
57969
58496
|
getTimeZone: (event) => event.timeZone ?? "UTC",
|
|
57970
58497
|
getColor: (event) => event.eventCategory?.color ?? null,
|
|
57971
|
-
getOrderKey: (event) => `${event.title}
|
|
58498
|
+
getOrderKey: (event) => `${event.title}\u0000${event.id}`
|
|
57972
58499
|
});
|
|
57973
58500
|
return {
|
|
57974
58501
|
hourTicks: [...generic.hourTicks],
|
|
@@ -58005,18 +58532,9 @@ function toEventCell(cell) {
|
|
|
58005
58532
|
};
|
|
58006
58533
|
}
|
|
58007
58534
|
function formatTimetableCapacity(event) {
|
|
58008
|
-
const { available, isFull } = getEventAvailability(event);
|
|
58009
|
-
if (available == null) return {
|
|
58010
|
-
label: null,
|
|
58011
|
-
isFull: false
|
|
58012
|
-
};
|
|
58013
|
-
if (isFull) return {
|
|
58014
|
-
label: "Full",
|
|
58015
|
-
isFull: true
|
|
58016
|
-
};
|
|
58017
58535
|
return {
|
|
58018
|
-
label:
|
|
58019
|
-
isFull:
|
|
58536
|
+
label: formatEventRemainingPlaces(event, { compact: true }),
|
|
58537
|
+
isFull: isEventFull(event)
|
|
58020
58538
|
};
|
|
58021
58539
|
}
|
|
58022
58540
|
function formatEventTimeRange(event) {
|
|
@@ -58492,7 +59010,7 @@ function EventRegistrationSSR({ blockId, subject, children, ...sourceProps }) {
|
|
|
58492
59010
|
className: `event-registration ${className ?? ""}`.trim(),
|
|
58493
59011
|
"data-block": "event-registration",
|
|
58494
59012
|
"data-rb-island-required": "true",
|
|
58495
|
-
"data-rb-island-error-message": "
|
|
59013
|
+
"data-rb-island-error-message": "We couldn’t load the booking options for this event. Please refresh this page and try again. If this keeps happening, contact the organiser for help.",
|
|
58496
59014
|
"data-rb-island-error-class": "event-registration__hydration-error",
|
|
58497
59015
|
...getIslandRootProps("event-registration", blockId),
|
|
58498
59016
|
children: [
|
|
@@ -64363,6 +64881,32 @@ function isSiteStyleTransitionStyleGroup(value) {
|
|
|
64363
64881
|
function boundaryMapForAdjacentBlockIds(boundaries) {
|
|
64364
64882
|
return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
|
|
64365
64883
|
}
|
|
64884
|
+
/** Keep a surviving block's outgoing treatment when omitted blocks change its neighbor. */
|
|
64885
|
+
function boundariesForSurvivingBlocks(boundaries, blocks) {
|
|
64886
|
+
const outgoing = new Map(boundaries.map((boundary) => [boundary.previousBlockId, boundary]));
|
|
64887
|
+
const incoming = new Map(boundaries.map((boundary) => [boundary.nextBlockId, boundary]));
|
|
64888
|
+
return blocks.flatMap((block, index) => {
|
|
64889
|
+
const next = blocks[index + 1];
|
|
64890
|
+
if (!block.id || !next?.id) return [];
|
|
64891
|
+
const boundary = outgoing.get(block.id);
|
|
64892
|
+
if (!boundary) return [];
|
|
64893
|
+
if (boundary.nextBlockId === next.id) return [boundary];
|
|
64894
|
+
const destination = incoming.get(next.id);
|
|
64895
|
+
return [retargetCompiledBoundaryRuntimeForContentAdjacency({
|
|
64896
|
+
...boundary,
|
|
64897
|
+
boundaryId: `boundary:${block.id}:${next.id}`,
|
|
64898
|
+
nextBlockId: next.id,
|
|
64899
|
+
to: destination?.to ?? boundary.to,
|
|
64900
|
+
cssVars: {
|
|
64901
|
+
...boundary.cssVars,
|
|
64902
|
+
to: destination?.cssVars.to ?? boundary.cssVars.to
|
|
64903
|
+
}
|
|
64904
|
+
}, {
|
|
64905
|
+
currentContent: block.content,
|
|
64906
|
+
nextContent: next.content
|
|
64907
|
+
})];
|
|
64908
|
+
});
|
|
64909
|
+
}
|
|
64366
64910
|
function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
|
|
64367
64911
|
const previousBlockId = previousBlock?.id ?? null;
|
|
64368
64912
|
const nextBlockId = nextBlock?.id ?? null;
|
|
@@ -64454,41 +64998,179 @@ function toPresentationPage(page) {
|
|
|
64454
64998
|
};
|
|
64455
64999
|
}
|
|
64456
65000
|
//#endregion
|
|
65001
|
+
//#region ../blocks/src/pageRendererBlockSupport.tsx
|
|
65002
|
+
/**
|
|
65003
|
+
* Support for rendering one block section: what a block that cannot render
|
|
65004
|
+
* leaves behind, how an override is found, and the props both override paths
|
|
65005
|
+
* hand to a component. `PageRenderer` owns the page-level composition; this
|
|
65006
|
+
* module owns the pieces it reaches for per block.
|
|
65007
|
+
*/
|
|
65008
|
+
function fallback(message) {
|
|
65009
|
+
return /* @__PURE__ */ jsx("div", {
|
|
65010
|
+
className: "rb-p-6 rb-caption",
|
|
65011
|
+
style: textColorStyle("mutedText"),
|
|
65012
|
+
children: message
|
|
65013
|
+
});
|
|
65014
|
+
}
|
|
65015
|
+
/**
|
|
65016
|
+
* A page with nothing to show. Authoring surfaces get the diagnostic; a public
|
|
65017
|
+
* page renders nothing at all rather than telling a visitor about the outline.
|
|
65018
|
+
*/
|
|
65019
|
+
function emptyPageNotice(audience) {
|
|
65020
|
+
if (audience === "public") return null;
|
|
65021
|
+
return /* @__PURE__ */ jsx("div", {
|
|
65022
|
+
className: "rb-p-6 rb-caption",
|
|
65023
|
+
style: textColorStyle("mutedText"),
|
|
65024
|
+
children: "No blocks found."
|
|
65025
|
+
});
|
|
65026
|
+
}
|
|
65027
|
+
/**
|
|
65028
|
+
* A public page dropped a block, so nothing in the rendered HTML says the
|
|
65029
|
+
* content is missing. Say it in the logs instead.
|
|
65030
|
+
*/
|
|
65031
|
+
function warnOmittedFromPublicPage(input) {
|
|
65032
|
+
try {
|
|
65033
|
+
console.warn("[blocks:PageRenderer] omitted a non-renderable block from a public page", {
|
|
65034
|
+
blockId: input.block.id ?? null,
|
|
65035
|
+
blockKind: input.block.kind,
|
|
65036
|
+
outcome: input.outcome,
|
|
65037
|
+
...input.admissionError ? { error: input.admissionError } : {}
|
|
65038
|
+
});
|
|
65039
|
+
} catch {}
|
|
65040
|
+
}
|
|
65041
|
+
function shouldDebugCustomBlocks() {
|
|
65042
|
+
try {
|
|
65043
|
+
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
65044
|
+
} catch {
|
|
65045
|
+
return false;
|
|
65046
|
+
}
|
|
65047
|
+
}
|
|
65048
|
+
/**
|
|
65049
|
+
* Look up a block override component by block kind.
|
|
65050
|
+
* Supports both full ("block.hero") and short ("hero") keys.
|
|
65051
|
+
*/
|
|
65052
|
+
function getBlockOverride(blockKind, overrides) {
|
|
65053
|
+
if (!overrides) return void 0;
|
|
65054
|
+
if (overrides[blockKind]) return overrides[blockKind];
|
|
65055
|
+
const shortKind = blockKind.replace(/^block\./, "");
|
|
65056
|
+
if (overrides[shortKind]) return overrides[shortKind];
|
|
65057
|
+
}
|
|
65058
|
+
/**
|
|
65059
|
+
* Resolves block content based on stage priority.
|
|
65060
|
+
* For draft/preview: draftContent takes priority over content.
|
|
65061
|
+
* For published: content is the source of truth.
|
|
65062
|
+
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
65063
|
+
*/
|
|
65064
|
+
function getRenderableContent(block, previewStage = "published") {
|
|
65065
|
+
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
65066
|
+
if (!isObjectRecord$1(source)) return {};
|
|
65067
|
+
return source;
|
|
65068
|
+
}
|
|
65069
|
+
function resolvePageRendererImageResolveContext(dataContext) {
|
|
65070
|
+
const input = dataContext?.imageResolveContext;
|
|
65071
|
+
return buildResolveImageUrlContext({
|
|
65072
|
+
siteId: input?.siteId ?? dataContext?.siteId,
|
|
65073
|
+
supabaseUrl: input?.supabaseUrl ?? dataContext?.supabaseUrl,
|
|
65074
|
+
mediaAssetIndex: input?.mediaAssetIndex,
|
|
65075
|
+
mediaAssets: input?.mediaAssets ?? dataContext?.mediaAssets
|
|
65076
|
+
});
|
|
65077
|
+
}
|
|
65078
|
+
/**
|
|
65079
|
+
* The RSC-safe props every override component receives, whether the block
|
|
65080
|
+
* resolved a manifest or is being rendered from an override alone.
|
|
65081
|
+
*/
|
|
65082
|
+
function buildBlockOverrideProps(input) {
|
|
65083
|
+
return {
|
|
65084
|
+
content: input.content,
|
|
65085
|
+
...overrideThemeProps(input.themeResources),
|
|
65086
|
+
...blockOverrideContextProps(input.dataContext),
|
|
65087
|
+
data: input.data,
|
|
65088
|
+
imageResolveContext: input.imageResolveContext,
|
|
65089
|
+
blockId: input.block.id ?? null,
|
|
65090
|
+
blockKind: input.block.kind
|
|
65091
|
+
};
|
|
65092
|
+
}
|
|
65093
|
+
/** The render-context half of the override props, absent context included. */
|
|
65094
|
+
function blockOverrideContextProps(dataContext) {
|
|
65095
|
+
const context = dataContext ?? {};
|
|
65096
|
+
return {
|
|
65097
|
+
siteId: context.siteId,
|
|
65098
|
+
apiBaseUrl: context.apiBaseUrl,
|
|
65099
|
+
bookingTimeZone: context.bookingTimeZone,
|
|
65100
|
+
portalToken: context.portalToken ?? null,
|
|
65101
|
+
previewToken: context.previewToken ?? null,
|
|
65102
|
+
stage: context.previewStage,
|
|
65103
|
+
occurrenceContext: context.occurrenceContext ?? null,
|
|
65104
|
+
entrySubrouteContext: context.entrySubrouteContext ?? null,
|
|
65105
|
+
contentEntry: context.contentEntry ?? null,
|
|
65106
|
+
pageSurface: context.pageSurface ?? null,
|
|
65107
|
+
subject: context.subject ?? null,
|
|
65108
|
+
supabaseUrl: context.supabaseUrl
|
|
65109
|
+
};
|
|
65110
|
+
}
|
|
65111
|
+
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
65112
|
+
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
65113
|
+
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
65114
|
+
function getPerfSlowBlockRenderThresholdMs() {
|
|
65115
|
+
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
65116
|
+
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
65117
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
65118
|
+
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
65119
|
+
return cachedPerfSlowBlockRenderThresholdMs;
|
|
65120
|
+
}
|
|
65121
|
+
function getCachedBlockComponent(manifest) {
|
|
65122
|
+
const cached = blockComponentCache.get(manifest);
|
|
65123
|
+
if (cached) return cached;
|
|
65124
|
+
const created = makeDefaultBlockComponent({ manifest });
|
|
65125
|
+
blockComponentCache.set(manifest, created);
|
|
65126
|
+
return created;
|
|
65127
|
+
}
|
|
65128
|
+
//#endregion
|
|
64457
65129
|
//#region ../blocks/src/PageRenderer.tsx
|
|
64458
65130
|
function PageRenderer$1(props) {
|
|
64459
65131
|
const page = React$1.useMemo(() => toPresentationPage(props.page), [props.page]);
|
|
64460
65132
|
return /* @__PURE__ */ jsx(PageRendererCore, {
|
|
64461
65133
|
...props,
|
|
64462
|
-
page
|
|
65134
|
+
page,
|
|
65135
|
+
audience: "public"
|
|
64463
65136
|
});
|
|
64464
65137
|
}
|
|
64465
65138
|
function PageRendererCore(props) {
|
|
64466
|
-
const { page, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
65139
|
+
const { page, audience, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
64467
65140
|
const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
|
|
64468
65141
|
dataContext?.imageResolveContext,
|
|
64469
65142
|
dataContext?.mediaAssets,
|
|
64470
65143
|
dataContext?.siteId,
|
|
64471
65144
|
dataContext?.supabaseUrl
|
|
64472
65145
|
]);
|
|
64473
|
-
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(runtimeBoundaries ?? []), [runtimeBoundaries]);
|
|
64474
65146
|
const usesRuntimeBoundaries = runtimeBoundaries !== void 0;
|
|
64475
65147
|
const themeResources = React$1.useMemo(() => resolvePageThemeResources(props), [
|
|
64476
65148
|
props.rendering,
|
|
64477
65149
|
props.theme,
|
|
64478
65150
|
props.themeTokens
|
|
64479
65151
|
]);
|
|
64480
|
-
if (!page || page.blocks.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
64481
|
-
className: "rb-p-6 rb-caption",
|
|
64482
|
-
style: textColorStyle("mutedText"),
|
|
64483
|
-
children: "No blocks found."
|
|
64484
|
-
});
|
|
64485
65152
|
const resolvedRegistry = registry ?? getDefaultComponentRegistry();
|
|
64486
65153
|
setContextResolveImageUrlContext(imageResolveContext);
|
|
64487
|
-
|
|
64488
|
-
|
|
64489
|
-
|
|
64490
|
-
|
|
64491
|
-
|
|
65154
|
+
const sections = usePageSections(props, {
|
|
65155
|
+
page,
|
|
65156
|
+
themeResources,
|
|
65157
|
+
imageResolveContext,
|
|
65158
|
+
registry: resolvedRegistry
|
|
65159
|
+
});
|
|
65160
|
+
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(boundariesForSurvivingBlocks(runtimeBoundaries ?? [], sections.map(({ block }) => ({
|
|
65161
|
+
...block,
|
|
65162
|
+
content: getRenderableContent(block, dataContext?.previewStage)
|
|
65163
|
+
})))), [
|
|
65164
|
+
runtimeBoundaries,
|
|
65165
|
+
sections,
|
|
65166
|
+
dataContext?.previewStage
|
|
65167
|
+
]);
|
|
65168
|
+
if (sections.length === 0) return emptyPageNotice(audience);
|
|
65169
|
+
return /* @__PURE__ */ jsx("div", { children: sections.map((section, index) => {
|
|
65170
|
+
const { block, blockKey, node } = section;
|
|
65171
|
+
const blockPosition = pageBlockPosition(index, sections.length);
|
|
65172
|
+
const previousBlock = sections[index - 1]?.block ?? null;
|
|
65173
|
+
const nextBlock = sections[index + 1]?.block ?? null;
|
|
64492
65174
|
const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
|
|
64493
65175
|
const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
|
|
64494
65176
|
const boundaryElement = renderBoundaryAfterBlock({
|
|
@@ -64503,19 +65185,7 @@ function PageRendererCore(props) {
|
|
|
64503
65185
|
const managesOwnWrapper = definition?.manifest.behaviours?.managesOwnWrapper;
|
|
64504
65186
|
const renderableContent = getRenderableContent(block, dataContext?.previewStage);
|
|
64505
65187
|
const variantKey = definition?.manifest ? resolveVariantKey(definition.manifest, renderableContent) : null;
|
|
64506
|
-
|
|
64507
|
-
const blockSectionProps = buildBlockSectionProps({
|
|
64508
|
-
props,
|
|
64509
|
-
themeResources,
|
|
64510
|
-
block,
|
|
64511
|
-
blockKey,
|
|
64512
|
-
imageResolveContext,
|
|
64513
|
-
registry: resolvedRegistry
|
|
64514
|
-
});
|
|
64515
|
-
if (!shouldUseWrapper) {
|
|
64516
|
-
const rendered = /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
64517
|
-
return boundaryElement ? /* @__PURE__ */ jsxs(React$1.Fragment, { children: [rendered, boundaryElement] }, blockKey) : rendered;
|
|
64518
|
-
}
|
|
65188
|
+
if (!(!managesOwnWrapper || Boolean(wrapBlock))) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [node, boundaryElement] }, blockKey);
|
|
64519
65189
|
const wrapperClassName = [`block-${cleanKind}`, wrapBlock ? "group relative" : null].filter(Boolean).join(" ");
|
|
64520
65190
|
const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
|
|
64521
65191
|
const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
|
|
@@ -64540,7 +65210,7 @@ function PageRendererCore(props) {
|
|
|
64540
65210
|
"data-site-style-boundary-after": boundaryAfter?.metrics.size,
|
|
64541
65211
|
"data-style-group": styleGroup,
|
|
64542
65212
|
style: blockStyle,
|
|
64543
|
-
children:
|
|
65213
|
+
children: node
|
|
64544
65214
|
}, blockKey);
|
|
64545
65215
|
if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
|
|
64546
65216
|
if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
|
|
@@ -64553,18 +65223,69 @@ function PageRendererCore(props) {
|
|
|
64553
65223
|
})] }, blockKey);
|
|
64554
65224
|
}) });
|
|
64555
65225
|
}
|
|
64556
|
-
|
|
64557
|
-
|
|
64558
|
-
|
|
64559
|
-
|
|
64560
|
-
|
|
64561
|
-
|
|
64562
|
-
|
|
65226
|
+
/**
|
|
65227
|
+
* Memoised so that a re-render with unchanged inputs reuses the same section
|
|
65228
|
+
* elements and React can skip the block subtrees, exactly as the per-block memo
|
|
65229
|
+
* did before public pages started resolving their bodies up front.
|
|
65230
|
+
*/
|
|
65231
|
+
function usePageSections(props, input) {
|
|
65232
|
+
const { page, themeResources, imageResolveContext, registry } = input;
|
|
65233
|
+
return React$1.useMemo(() => admitPageSections({
|
|
65234
|
+
props,
|
|
65235
|
+
blocks: page?.blocks ?? [],
|
|
65236
|
+
audience: props.audience,
|
|
65237
|
+
themeResources,
|
|
65238
|
+
imageResolveContext,
|
|
65239
|
+
registry
|
|
65240
|
+
}), [
|
|
65241
|
+
page,
|
|
65242
|
+
props.audience,
|
|
65243
|
+
themeResources,
|
|
65244
|
+
imageResolveContext,
|
|
65245
|
+
registry,
|
|
65246
|
+
props.dataContext,
|
|
65247
|
+
props.routeMap,
|
|
65248
|
+
props.wrapBlock,
|
|
65249
|
+
props.blockOverrides,
|
|
65250
|
+
props.sdkConfig,
|
|
65251
|
+
props.customBlocks,
|
|
65252
|
+
props.previewAuthoring
|
|
65253
|
+
]);
|
|
65254
|
+
}
|
|
65255
|
+
/**
|
|
65256
|
+
* Resolves each block's rendered body before the page decides what to wrap.
|
|
65257
|
+
*
|
|
65258
|
+
* On a public page the body is resolved eagerly so a block that cannot render
|
|
65259
|
+
* is dropped along with its wrapper, its data attributes, its boundary reserve
|
|
65260
|
+
* space and its style-group sibling — the section body is the single source of
|
|
65261
|
+
* truth for whether the block is on the page (issue 1378). Authoring surfaces
|
|
65262
|
+
* keep the memoised component so the editor's render-loop guards still hold.
|
|
65263
|
+
*/
|
|
65264
|
+
function admitPageSections(input) {
|
|
65265
|
+
const sections = [];
|
|
65266
|
+
input.blocks.forEach((block, index) => {
|
|
65267
|
+
const blockKey = block.id ?? `${block.kind}-${index}`;
|
|
65268
|
+
const blockSectionProps = buildBlockSectionProps({
|
|
65269
|
+
props: input.props,
|
|
65270
|
+
themeResources: input.themeResources,
|
|
65271
|
+
block,
|
|
65272
|
+
blockKey,
|
|
65273
|
+
imageResolveContext: input.imageResolveContext,
|
|
65274
|
+
registry: input.registry
|
|
65275
|
+
});
|
|
65276
|
+
const node = input.audience === "public" ? renderBlockSection(blockSectionProps) : /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
65277
|
+
if (node === null || node === void 0) return;
|
|
65278
|
+
sections.push({
|
|
65279
|
+
block,
|
|
65280
|
+
blockKey,
|
|
65281
|
+
node
|
|
65282
|
+
});
|
|
64563
65283
|
});
|
|
65284
|
+
return sections;
|
|
64564
65285
|
}
|
|
64565
|
-
const resolveBookingTimeZone = (dataContext) => dataContext?.bookingTimeZone;
|
|
64566
65286
|
function buildBlockSectionProps(input) {
|
|
64567
65287
|
return {
|
|
65288
|
+
audience: input.props.audience,
|
|
64568
65289
|
themeResources: input.themeResources,
|
|
64569
65290
|
block: input.block,
|
|
64570
65291
|
dataContext: input.props.dataContext,
|
|
@@ -64580,40 +65301,24 @@ function buildBlockSectionProps(input) {
|
|
|
64580
65301
|
};
|
|
64581
65302
|
}
|
|
64582
65303
|
/**
|
|
64583
|
-
* Look up a block override component by block kind.
|
|
64584
|
-
* Supports both full ("block.hero") and short ("hero") keys.
|
|
64585
|
-
*/
|
|
64586
|
-
function getBlockOverride(blockKind, overrides) {
|
|
64587
|
-
if (!overrides) return void 0;
|
|
64588
|
-
if (overrides[blockKind]) return overrides[blockKind];
|
|
64589
|
-
const shortKind = blockKind.replace(/^block\./, "");
|
|
64590
|
-
if (overrides[shortKind]) return overrides[shortKind];
|
|
64591
|
-
}
|
|
64592
|
-
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
64593
|
-
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
64594
|
-
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
64595
|
-
function getPerfSlowBlockRenderThresholdMs() {
|
|
64596
|
-
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
64597
|
-
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
64598
|
-
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
64599
|
-
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
64600
|
-
return cachedPerfSlowBlockRenderThresholdMs;
|
|
64601
|
-
}
|
|
64602
|
-
function getCachedBlockComponent(manifest) {
|
|
64603
|
-
const cached = blockComponentCache.get(manifest);
|
|
64604
|
-
if (cached) return cached;
|
|
64605
|
-
const created = makeDefaultBlockComponent({ manifest });
|
|
64606
|
-
blockComponentCache.set(manifest, created);
|
|
64607
|
-
return created;
|
|
64608
|
-
}
|
|
64609
|
-
/**
|
|
64610
65304
|
* Memoized block section component.
|
|
64611
65305
|
*
|
|
64612
65306
|
* Custom memoization prevents render loops during editing by explicitly checking
|
|
64613
65307
|
* referential equality of all props that affect rendering. Standard React.memo
|
|
64614
65308
|
* wouldn't catch all the render triggers from the editor store.
|
|
64615
65309
|
*/
|
|
64616
|
-
const MemoBlockSection = React$1.memo(function MemoBlockSection(
|
|
65310
|
+
const MemoBlockSection = React$1.memo(function MemoBlockSection(props) {
|
|
65311
|
+
return renderBlockSection(props);
|
|
65312
|
+
}, (prev, next) => {
|
|
65313
|
+
return prev.audience === next.audience && prev.block === next.block && prev.registry === next.registry && prev.dataContext === next.dataContext && prev.themeResources === next.themeResources && prev.routeMap === next.routeMap && prev.block.presentationAdmission === next.block.presentationAdmission && prev.wrapBlock === next.wrapBlock && prev.blockOverrides === next.blockOverrides && prev.sdkConfig === next.sdkConfig && prev.customBlocks === next.customBlocks && prev.imageResolveContext === next.imageResolveContext && prev.previewAuthoring === next.previewAuthoring;
|
|
65314
|
+
});
|
|
65315
|
+
/**
|
|
65316
|
+
* Renders a single block body.
|
|
65317
|
+
*
|
|
65318
|
+
* Returns `null` when the block cannot render and the audience is `public`;
|
|
65319
|
+
* the caller treats that as "this block is not on the page".
|
|
65320
|
+
*/
|
|
65321
|
+
function renderBlockSection({ audience, themeResources, block, dataContext, registry, routeMap, wrapBlock, blockOverrides, sdkConfig, customBlocks, imageResolveContext, previewAuthoring }) {
|
|
64617
65322
|
const perfStart = globalThis.process?.env?.DEBUG_PERF === "1" && typeof window === "undefined" ? Date.now() : 0;
|
|
64618
65323
|
const fallbackImageResolveContext = getCurrentResolveImageUrlContext();
|
|
64619
65324
|
const resolvedImageResolveContext = imageResolveContext ?? fallbackImageResolveContext;
|
|
@@ -64633,19 +65338,30 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
64633
65338
|
} catch {}
|
|
64634
65339
|
return node;
|
|
64635
65340
|
}
|
|
64636
|
-
|
|
64637
|
-
|
|
64638
|
-
|
|
64639
|
-
|
|
64640
|
-
|
|
64641
|
-
|
|
64642
|
-
|
|
64643
|
-
|
|
64644
|
-
|
|
64645
|
-
|
|
64646
|
-
override: false
|
|
65341
|
+
/**
|
|
65342
|
+
* The one place that decides what a block that cannot render leaves behind.
|
|
65343
|
+
* A public page gets nothing (and a log line, since dropping content on a
|
|
65344
|
+
* 200 is otherwise invisible); authoring surfaces get the diagnostic.
|
|
65345
|
+
*/
|
|
65346
|
+
function diagnostic(message, detail) {
|
|
65347
|
+
if (audience !== "public") return fallback(message);
|
|
65348
|
+
warnOmittedFromPublicPage({
|
|
65349
|
+
block,
|
|
65350
|
+
...detail
|
|
64647
65351
|
});
|
|
65352
|
+
return null;
|
|
64648
65353
|
}
|
|
65354
|
+
function withBlockWrapper(node) {
|
|
65355
|
+
if (node === null) return null;
|
|
65356
|
+
return wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, node) }) : /* @__PURE__ */ jsx(Fragment, { children: node });
|
|
65357
|
+
}
|
|
65358
|
+
if (block.presentationAdmission.kind === "invalid") return finish(diagnostic(`Invalid content for block kind "${block.kind}".`, {
|
|
65359
|
+
outcome: "invalidContent",
|
|
65360
|
+
admissionError: block.presentationAdmission.error
|
|
65361
|
+
}), {
|
|
65362
|
+
outcome: "invalidContent",
|
|
65363
|
+
override: false
|
|
65364
|
+
});
|
|
64649
65365
|
const OverrideComponent = getBlockOverride(block.kind, blockOverrides);
|
|
64650
65366
|
requireBlockOverride(themeResources, OverrideComponent, block.kind);
|
|
64651
65367
|
let definition = getBlockDefinition(block.kind);
|
|
@@ -64658,25 +65374,14 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
64658
65374
|
}).findManifest(block.kind)?.descriptor.definition;
|
|
64659
65375
|
if (!definition) {
|
|
64660
65376
|
if (OverrideComponent) {
|
|
64661
|
-
const rendered = /* @__PURE__ */ jsx(OverrideComponent, {
|
|
65377
|
+
const rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...buildBlockOverrideProps({
|
|
65378
|
+
block,
|
|
64662
65379
|
content: getRenderableContent(block, dataContext?.previewStage),
|
|
64663
|
-
...overrideThemeProps(themeResources),
|
|
64664
65380
|
data: void 0,
|
|
64665
|
-
|
|
64666
|
-
|
|
64667
|
-
|
|
64668
|
-
|
|
64669
|
-
stage: dataContext?.previewStage,
|
|
64670
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
64671
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
64672
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
64673
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
64674
|
-
subject: dataContext?.subject ?? null,
|
|
64675
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
64676
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
64677
|
-
blockId: block.id ?? null,
|
|
64678
|
-
blockKind: block.kind
|
|
64679
|
-
});
|
|
65381
|
+
themeResources,
|
|
65382
|
+
dataContext,
|
|
65383
|
+
imageResolveContext: resolvedImageResolveContext
|
|
65384
|
+
}) });
|
|
64680
65385
|
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
64681
65386
|
outcome: "overrideWithoutDefinition",
|
|
64682
65387
|
override: true,
|
|
@@ -64692,8 +65397,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
64692
65397
|
dbCustomBlocksCount: customBlocks?.length ?? 0
|
|
64693
65398
|
});
|
|
64694
65399
|
} catch {}
|
|
64695
|
-
|
|
64696
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
65400
|
+
return finish(withBlockWrapper(diagnostic(block.kind.startsWith("custom.") ? `No renderer available for block kind "${block.kind}". The custom block definition was not found (missing manifest).` : `No renderer available for block kind "${block.kind}".`, { outcome: "missingDefinition" })), {
|
|
64697
65401
|
outcome: "missingDefinition",
|
|
64698
65402
|
override: false
|
|
64699
65403
|
});
|
|
@@ -64708,8 +65412,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
64708
65412
|
manifestKeys: Object.keys(definition.manifest ?? {}).sort()
|
|
64709
65413
|
});
|
|
64710
65414
|
} catch {}
|
|
64711
|
-
|
|
64712
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
65415
|
+
return finish(withBlockWrapper(diagnostic(block.kind.startsWith("custom.") ? `No renderer available for block kind "${block.kind}". This custom block has no layout/variants, so it must be rendered via blockOverrides["${block.kind}"].` : `No renderer available for block kind "${block.kind}".`, { outcome: "missingLayout" })), {
|
|
64713
65416
|
outcome: "missingLayout",
|
|
64714
65417
|
override: false
|
|
64715
65418
|
});
|
|
@@ -64719,39 +65422,24 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
64719
65422
|
if (block.presentationAdmission.kind === "editor") {
|
|
64720
65423
|
const draft = validateManifestDraft(compileManifestContentContract(definition.manifest), content);
|
|
64721
65424
|
if (draft.kind === "previewable") data = draft.data;
|
|
64722
|
-
else {
|
|
64723
|
-
|
|
64724
|
-
|
|
64725
|
-
|
|
64726
|
-
override: false
|
|
64727
|
-
});
|
|
64728
|
-
}
|
|
65425
|
+
else return finish(withBlockWrapper(diagnostic(`Unable to render block kind "${block.kind}".`, { outcome: "invalidPlaceholder" })), {
|
|
65426
|
+
outcome: "invalidPlaceholder",
|
|
65427
|
+
override: false
|
|
65428
|
+
});
|
|
64729
65429
|
}
|
|
64730
65430
|
try {
|
|
64731
65431
|
const routes = routeMap ?? dataContext?.routes;
|
|
64732
65432
|
const hydratedContent = routes ? hydrateManifestLinks(definition.manifest, data, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : data;
|
|
64733
65433
|
const resolved = resolveBlockData$1(block.id, definition.dataLoaders, dataContext, routes);
|
|
64734
65434
|
const hasResolvedData = !!resolved && typeof resolved === "object" && Object.keys(resolved).length > 0;
|
|
64735
|
-
const baseProps = {
|
|
65435
|
+
const baseProps = buildBlockOverrideProps({
|
|
65436
|
+
block,
|
|
64736
65437
|
content: hydratedContent,
|
|
64737
|
-
...overrideThemeProps(themeResources),
|
|
64738
65438
|
data: resolved,
|
|
64739
|
-
|
|
64740
|
-
|
|
64741
|
-
|
|
64742
|
-
|
|
64743
|
-
stage: dataContext?.previewStage,
|
|
64744
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
64745
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
64746
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
64747
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
64748
|
-
subject: dataContext?.subject ?? null,
|
|
64749
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
64750
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
64751
|
-
blockId: block.id ?? null,
|
|
64752
|
-
blockKind: block.kind
|
|
64753
|
-
};
|
|
64754
|
-
baseProps.bookingTimeZone = resolveBookingTimeZone(dataContext);
|
|
65439
|
+
themeResources,
|
|
65440
|
+
dataContext,
|
|
65441
|
+
imageResolveContext: resolvedImageResolveContext
|
|
65442
|
+
});
|
|
64755
65443
|
let rendered;
|
|
64756
65444
|
if (OverrideComponent) rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...baseProps });
|
|
64757
65445
|
else {
|
|
@@ -64798,39 +65486,11 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
64798
65486
|
hasResolvedData
|
|
64799
65487
|
});
|
|
64800
65488
|
} catch {
|
|
64801
|
-
|
|
64802
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
65489
|
+
return finish(withBlockWrapper(diagnostic(block.presentationAdmission.kind === "editor" ? `Unable to render block kind "${block.kind}".` : `Invalid content for block kind "${block.kind}".`, { outcome: "exception" })), {
|
|
64803
65490
|
outcome: "exception",
|
|
64804
65491
|
override: false
|
|
64805
65492
|
});
|
|
64806
65493
|
}
|
|
64807
|
-
}, (prev, next) => {
|
|
64808
|
-
return prev.block === next.block && prev.registry === next.registry && prev.dataContext === next.dataContext && prev.themeResources === next.themeResources && prev.routeMap === next.routeMap && prev.block.presentationAdmission === next.block.presentationAdmission && prev.wrapBlock === next.wrapBlock && prev.blockOverrides === next.blockOverrides && prev.sdkConfig === next.sdkConfig && prev.customBlocks === next.customBlocks && prev.imageResolveContext === next.imageResolveContext && prev.previewAuthoring === next.previewAuthoring;
|
|
64809
|
-
});
|
|
64810
|
-
function fallback(message) {
|
|
64811
|
-
return /* @__PURE__ */ jsx("div", {
|
|
64812
|
-
className: "rb-p-6 rb-caption",
|
|
64813
|
-
style: textColorStyle("mutedText"),
|
|
64814
|
-
children: message
|
|
64815
|
-
});
|
|
64816
|
-
}
|
|
64817
|
-
function shouldDebugCustomBlocks() {
|
|
64818
|
-
try {
|
|
64819
|
-
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
64820
|
-
} catch {
|
|
64821
|
-
return false;
|
|
64822
|
-
}
|
|
64823
|
-
}
|
|
64824
|
-
/**
|
|
64825
|
-
* Resolves block content based on stage priority.
|
|
64826
|
-
* For draft/preview: draftContent takes priority over content.
|
|
64827
|
-
* For published: content is the source of truth.
|
|
64828
|
-
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
64829
|
-
*/
|
|
64830
|
-
function getRenderableContent(block, previewStage = "published") {
|
|
64831
|
-
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
64832
|
-
if (!isObjectRecord$1(source)) return {};
|
|
64833
|
-
return source;
|
|
64834
65494
|
}
|
|
64835
65495
|
//#endregion
|
|
64836
65496
|
//#region ../blocks/src/structure/schema.ts
|
|
@@ -74804,6 +75464,21 @@ const containerResponsiveThemeCss = `/*
|
|
|
74804
75464
|
outline-offset: 2px;
|
|
74805
75465
|
}
|
|
74806
75466
|
|
|
75467
|
+
/* Event titles always link to the event page, so a card or row whose CTA is
|
|
75468
|
+
hidden (sold out, closed) is never a dead end (issue 1372). The link inherits
|
|
75469
|
+
the heading's colour and only reveals itself on hover or keyboard focus.
|
|
75470
|
+
Shared by EventCard, EventCompactRow and the offerings-preview course cards via \`.event-title-link\`. */
|
|
75471
|
+
:where(.theme-scope) .event-title-link {
|
|
75472
|
+
color: inherit;
|
|
75473
|
+
text-decoration: none;
|
|
75474
|
+
}
|
|
75475
|
+
|
|
75476
|
+
:where(.theme-scope) .event-title-link:hover,
|
|
75477
|
+
:where(.theme-scope) .event-title-link:focus-visible {
|
|
75478
|
+
text-decoration: underline;
|
|
75479
|
+
text-underline-offset: 0.12em;
|
|
75480
|
+
}
|
|
75481
|
+
|
|
74807
75482
|
/* =============================================================================
|
|
74808
75483
|
* Block: Carousel Navigation (Phase 3 client component migration)
|
|
74809
75484
|
* =============================================================================
|
|
@@ -80151,6 +80826,50 @@ z$1.object({
|
|
|
80151
80826
|
title: z$1.string(),
|
|
80152
80827
|
slug: z$1.string().min(1)
|
|
80153
80828
|
});
|
|
80829
|
+
z$1.object({
|
|
80830
|
+
signedUrl: z$1.string().url(),
|
|
80831
|
+
token: z$1.string().min(1),
|
|
80832
|
+
storagePath: z$1.string().min(1)
|
|
80833
|
+
}).strict();
|
|
80834
|
+
const backupManifestSchema = z$1.object({
|
|
80835
|
+
siteId: z$1.string().min(1),
|
|
80836
|
+
siteSlug: z$1.string().min(1),
|
|
80837
|
+
siteTitle: z$1.string(),
|
|
80838
|
+
exportedAt: z$1.string().min(1),
|
|
80839
|
+
tableCount: z$1.number().int().nonnegative(),
|
|
80840
|
+
totalRecords: z$1.number().int().nonnegative(),
|
|
80841
|
+
pageCount: z$1.number().int().nonnegative(),
|
|
80842
|
+
blockCount: z$1.number().int().nonnegative(),
|
|
80843
|
+
contentEntryCount: z$1.number().int().nonnegative(),
|
|
80844
|
+
formCount: z$1.number().int().nonnegative(),
|
|
80845
|
+
navigationMenuCount: z$1.number().int().nonnegative(),
|
|
80846
|
+
mediaCount: z$1.number().int().nonnegative(),
|
|
80847
|
+
mediaBytes: z$1.number().int().nonnegative()
|
|
80848
|
+
}).strict();
|
|
80849
|
+
z$1.object({
|
|
80850
|
+
ok: z$1.literal(true),
|
|
80851
|
+
manifest: backupManifestSchema,
|
|
80852
|
+
storagePath: z$1.string().min(1)
|
|
80853
|
+
}).strict();
|
|
80854
|
+
z$1.object({
|
|
80855
|
+
ok: z$1.literal(true),
|
|
80856
|
+
siteId: brandedIdSchema(parseSiteId, "site id"),
|
|
80857
|
+
slug: z$1.string().min(1),
|
|
80858
|
+
title: z$1.string(),
|
|
80859
|
+
counts: z$1.record(z$1.string(), z$1.object({
|
|
80860
|
+
created: z$1.number().int().nonnegative(),
|
|
80861
|
+
updated: z$1.number().int().nonnegative(),
|
|
80862
|
+
skipped: z$1.number().int().nonnegative(),
|
|
80863
|
+
failed: z$1.number().int().nonnegative()
|
|
80864
|
+
}).strict()),
|
|
80865
|
+
warnings: z$1.array(z$1.string()),
|
|
80866
|
+
errors: z$1.array(z$1.object({
|
|
80867
|
+
table: z$1.string(),
|
|
80868
|
+
id: z$1.string(),
|
|
80869
|
+
error: z$1.string()
|
|
80870
|
+
}).strict())
|
|
80871
|
+
}).strict();
|
|
80872
|
+
z$1.object({ deleted: z$1.boolean() }).strict();
|
|
80154
80873
|
const apiKeyIdSchema = brandedIdSchema(parseApiKeyId, "API key id");
|
|
80155
80874
|
const standardKeyIdsSchema = z$1.object({
|
|
80156
80875
|
live: apiKeyIdSchema.optional(),
|
|
@@ -83942,9 +84661,7 @@ function admitMaterializedSdkTemplateBlockContent(input) {
|
|
|
83942
84661
|
const baseContent = sanitizeSdkRuntimeBlockContent(input.block.blockKind, input.block.content ?? {});
|
|
83943
84662
|
const bindings = input.block.bindings ?? {};
|
|
83944
84663
|
const materialized = input.block.scope === "entry" && Object.keys(bindings).length > 0 ? applyBindingsToContent(baseContent, bindings, input.bindingContext) : baseContent;
|
|
83945
|
-
|
|
83946
|
-
const stored = admitStoredManifestContent(contract, materialized);
|
|
83947
|
-
return stored.ok ? deriveRenderableManifestContent(contract, stored.value, (content) => content) : stored;
|
|
84664
|
+
return admitTransportedRenderableManifestContent(compileManifestContentContract(input.manifestOrFields), materialized);
|
|
83948
84665
|
}
|
|
83949
84666
|
function createTemplateRenderTarget(source, templateId) {
|
|
83950
84667
|
switch (source.kind) {
|
|
@@ -84076,13 +84793,7 @@ function admitEntrySlotBlock(block, customBlocks) {
|
|
|
84076
84793
|
if (transportedAdmission) return invalidRenderableBlock(base, transportedAdmission.error);
|
|
84077
84794
|
const manifest = resolveSdkBlockManifest(block.kind, customBlocks);
|
|
84078
84795
|
if (manifest === null) return invalidRenderableBlock(base, { kind: "manifest_unavailable" });
|
|
84079
|
-
const
|
|
84080
|
-
const stored = admitStoredManifestContent(contract, block.content ?? {});
|
|
84081
|
-
if (!stored.ok) return invalidRenderableBlock(base, {
|
|
84082
|
-
kind: "invalid_manifest_content",
|
|
84083
|
-
error: stored.error
|
|
84084
|
-
});
|
|
84085
|
-
const renderable = deriveRenderableManifestContent(contract, stored.value, (content) => content);
|
|
84796
|
+
const renderable = admitTransportedRenderableManifestContent(compileManifestContentContract(manifest), block.content ?? {});
|
|
84086
84797
|
return renderable.ok ? {
|
|
84087
84798
|
...base,
|
|
84088
84799
|
admission: {
|
|
@@ -85783,7 +86494,7 @@ var CircuitOpenError = class extends Error {
|
|
|
85783
86494
|
* 1. This constant
|
|
85784
86495
|
* 2. The "version" field in package.json
|
|
85785
86496
|
*/
|
|
85786
|
-
const SDK_VERSION = "0.
|
|
86497
|
+
const SDK_VERSION = "0.137.0";
|
|
85787
86498
|
//#endregion
|
|
85788
86499
|
//#region src/next/content-load-failure.ts
|
|
85789
86500
|
const SdkContentLoadError = RiverbankContentLoadError;
|