@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
|
@@ -7664,6 +7664,11 @@ const gradientConfigSchema = z$1.object({
|
|
|
7664
7664
|
position: z$1.string().optional(),
|
|
7665
7665
|
stops: z$1.array(gradientStopSchema).min(2)
|
|
7666
7666
|
});
|
|
7667
|
+
const gradientsSchema = z$1.object({
|
|
7668
|
+
button: z$1.string().min(1).nullable().optional(),
|
|
7669
|
+
hero: z$1.string().min(1).nullable().optional(),
|
|
7670
|
+
background: z$1.string().min(1).nullable().optional()
|
|
7671
|
+
});
|
|
7667
7672
|
//#endregion
|
|
7668
7673
|
//#region ../theme-core/src/interactive/baseSchema.ts
|
|
7669
7674
|
/**
|
|
@@ -8664,11 +8669,30 @@ const headerContainerSchema = z$1.object({
|
|
|
8664
8669
|
tint: z$1.string().nullable().optional(),
|
|
8665
8670
|
opacity: z$1.number().min(0).max(1).default(.12)
|
|
8666
8671
|
}).optional();
|
|
8667
|
-
|
|
8672
|
+
//#endregion
|
|
8673
|
+
//#region ../theme-core/src/header/interactionPalette.ts
|
|
8674
|
+
/**
|
|
8675
|
+
* The palettes that fill the nav link with a colour on hover and focus. Each
|
|
8676
|
+
* one names a background/foreground token pair.
|
|
8677
|
+
*/
|
|
8678
|
+
const headerInteractionPillPaletteValues = [
|
|
8668
8679
|
"primary",
|
|
8669
8680
|
"secondary",
|
|
8670
8681
|
"accent"
|
|
8671
|
-
]
|
|
8682
|
+
];
|
|
8683
|
+
/**
|
|
8684
|
+
* "No pill": the nav link keeps whatever hover treatment its recipe compiled,
|
|
8685
|
+
* and the palette step writes nothing at all (#1318 Phase 4c decision 2).
|
|
8686
|
+
*
|
|
8687
|
+
* It exists because "a colour change on hover, no filled background" was not
|
|
8688
|
+
* expressible in the vocabulary, and it is what
|
|
8689
|
+
* `anna-raven-therapy-and-yoga` and `kirtan-lucy` have rendered since before
|
|
8690
|
+
* the palette existed. Without it those two rows can only be classified as a
|
|
8691
|
+
* conscious visual change.
|
|
8692
|
+
*/
|
|
8693
|
+
const headerInteractionQuietPalette = "quiet";
|
|
8694
|
+
const headerInteractionPaletteValues = [...headerInteractionPillPaletteValues, headerInteractionQuietPalette];
|
|
8695
|
+
const headerInteractionPaletteSchema = z$1.enum(headerInteractionPaletteValues);
|
|
8672
8696
|
//#endregion
|
|
8673
8697
|
//#region ../theme-core/src/boxBackgroundSchema.ts
|
|
8674
8698
|
const boxBackgroundOverlaySchema = z$1.preprocess(normalizeAuthoringOpacity, z$1.discriminatedUnion("type", [
|
|
@@ -9050,11 +9074,12 @@ const inputStyle = z$1.object({
|
|
|
9050
9074
|
]),
|
|
9051
9075
|
label: z$1.enum(["inside", "above"])
|
|
9052
9076
|
});
|
|
9053
|
-
const
|
|
9077
|
+
const headerPositioningValues = [
|
|
9054
9078
|
"static",
|
|
9055
9079
|
"sticky",
|
|
9056
9080
|
"fixed"
|
|
9057
|
-
]
|
|
9081
|
+
];
|
|
9082
|
+
const headerPositioning = z$1.enum(headerPositioningValues);
|
|
9058
9083
|
const headerNavStyle = z$1.enum([
|
|
9059
9084
|
"minimal",
|
|
9060
9085
|
"underline",
|
|
@@ -9189,7 +9214,7 @@ const headerSchema = z$1.object({
|
|
|
9189
9214
|
variant: headerVariantWithLegacyAliasSchema,
|
|
9190
9215
|
positioning: headerPositioning,
|
|
9191
9216
|
shrinkOnScroll: z$1.boolean(),
|
|
9192
|
-
maxWidth: headerMaxWidth,
|
|
9217
|
+
maxWidth: headerMaxWidth.default("container"),
|
|
9193
9218
|
scrollTreatment: headerScrollTreatmentSchema,
|
|
9194
9219
|
background: headerBackgroundSchema.default({
|
|
9195
9220
|
type: "color",
|
|
@@ -9388,11 +9413,6 @@ const bodyTextSchema = z$1.object({ intro: z$1.object({
|
|
|
9388
9413
|
paragraphSize: bodyTextIntroTypographySizeSchema.optional(),
|
|
9389
9414
|
headingSize: bodyTextIntroTypographySizeSchema.optional()
|
|
9390
9415
|
}).optional() }).optional();
|
|
9391
|
-
const gradientsSchema = z$1.object({
|
|
9392
|
-
button: z$1.string().min(1).nullable().optional(),
|
|
9393
|
-
hero: z$1.string().min(1).nullable().optional(),
|
|
9394
|
-
background: z$1.string().min(1).nullable().optional()
|
|
9395
|
-
});
|
|
9396
9416
|
/**
|
|
9397
9417
|
* Testimonial quote-mark treatment, owned by the site style (analogous to the
|
|
9398
9418
|
* accordion icon). `style` is the coarse selector; the optional fields fine-tune
|
|
@@ -13359,12 +13379,12 @@ function allGroupChildPlans(plan) {
|
|
|
13359
13379
|
}
|
|
13360
13380
|
function selectedGroupChildPlans(plan, value) {
|
|
13361
13381
|
if (plan.childSelection.kind === "all") return plan.childSelection.fields;
|
|
13362
|
-
if (!isPlainObject$
|
|
13382
|
+
if (!isPlainObject$7(value)) return [];
|
|
13363
13383
|
const selectedType = value[plan.childSelection.activeTabField];
|
|
13364
13384
|
return plan.childSelection.tabs.find((tab) => tab.typeId === selectedType)?.fields ?? [];
|
|
13365
13385
|
}
|
|
13366
13386
|
function canonicalizeObjectForWrite(plans, value) {
|
|
13367
|
-
if (!isPlainObject$
|
|
13387
|
+
if (!isPlainObject$7(value)) return value;
|
|
13368
13388
|
const canonical = { ...value };
|
|
13369
13389
|
for (const plan of plans) {
|
|
13370
13390
|
if (!(plan.fieldId in canonical)) continue;
|
|
@@ -13388,7 +13408,7 @@ function canonicalizeFieldValue(plan, value) {
|
|
|
13388
13408
|
}
|
|
13389
13409
|
}
|
|
13390
13410
|
function canonicalizeMedia(value) {
|
|
13391
|
-
if (!isPlainObject$
|
|
13411
|
+
if (!isPlainObject$7(value)) return value;
|
|
13392
13412
|
const canonical = { ...value };
|
|
13393
13413
|
if (mediaIdentifierSchema.safeParse(canonical.identifier).success) deleteEnvSpecificKeys(canonical);
|
|
13394
13414
|
return canonical;
|
|
@@ -13396,13 +13416,13 @@ function canonicalizeMedia(value) {
|
|
|
13396
13416
|
function canonicalizeRepeater(plan, value) {
|
|
13397
13417
|
if (!isUnknownArray$3(value)) return value;
|
|
13398
13418
|
return value.map((item) => {
|
|
13399
|
-
if (!isPlainObject$
|
|
13419
|
+
if (!isPlainObject$7(item)) return item;
|
|
13400
13420
|
const childPlans = plan.repeatedItemVariants ? plan.repeatedItemVariants.find((variant) => variant.typeId === item._type)?.fields : plan.repeatedItemPlan;
|
|
13401
13421
|
return childPlans ? canonicalizeObjectForWrite(childPlans, item) : item;
|
|
13402
13422
|
});
|
|
13403
13423
|
}
|
|
13404
13424
|
function canonicalizeDiscriminatedGroup(plan, value) {
|
|
13405
|
-
if (!isPlainObject$
|
|
13425
|
+
if (!isPlainObject$7(value) || plan.childSelection.kind !== "discriminatedTabs") return value;
|
|
13406
13426
|
const selection = plan.childSelection;
|
|
13407
13427
|
const selectedPlans = selectedGroupChildPlans(plan, value);
|
|
13408
13428
|
const selectedIds = rootFieldIds(selectedPlans);
|
|
@@ -13417,10 +13437,10 @@ function rootFieldIds(plans) {
|
|
|
13417
13437
|
}
|
|
13418
13438
|
function omitUndefined(value) {
|
|
13419
13439
|
if (isUnknownArray$3(value)) return value.map(omitUndefined);
|
|
13420
|
-
if (!isPlainObject$
|
|
13440
|
+
if (!isPlainObject$7(value)) return value;
|
|
13421
13441
|
return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0).map(([key, field]) => [key, omitUndefined(field)]));
|
|
13422
13442
|
}
|
|
13423
|
-
function isPlainObject$
|
|
13443
|
+
function isPlainObject$7(value) {
|
|
13424
13444
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13425
13445
|
}
|
|
13426
13446
|
function isUnknownArray$3(value) {
|
|
@@ -13816,7 +13836,7 @@ function validateNormalizedFieldValue(plan, value, ctx) {
|
|
|
13816
13836
|
case "select": return validateSelectPlan(plan, value);
|
|
13817
13837
|
case "richText": return isValidRichTextDoc(value) ? [] : [issue(plan, "invalidRichText")];
|
|
13818
13838
|
case "inlineText": return validateInlineTextPlan(plan, value);
|
|
13819
|
-
case "media": return isPlainObject$
|
|
13839
|
+
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")];
|
|
13820
13840
|
case "link": return linkSchema.safeParse(value).success ? [] : [issue(plan, "invalidLink")];
|
|
13821
13841
|
case "repeater": return validateRepeaterPlan(plan, value, ctx);
|
|
13822
13842
|
case "group": return validateGroupPlan(plan, value, ctx);
|
|
@@ -13909,7 +13929,7 @@ function validateRepeaterPlan(plan, value, ctx) {
|
|
|
13909
13929
|
}), ...value.flatMap((item, index) => validateRepeaterItem(plan, item, index, ctx))];
|
|
13910
13930
|
}
|
|
13911
13931
|
function validateRepeaterItem(plan, item, index, ctx) {
|
|
13912
|
-
if (!isPlainObject$
|
|
13932
|
+
if (!isPlainObject$6(item)) return [{
|
|
13913
13933
|
kind: "wrongType",
|
|
13914
13934
|
path: [...plan.path, index],
|
|
13915
13935
|
fieldId: plan.fieldId,
|
|
@@ -13935,7 +13955,7 @@ function validateRepeaterItem(plan, item, index, ctx) {
|
|
|
13935
13955
|
});
|
|
13936
13956
|
}
|
|
13937
13957
|
function validateGroupPlan(plan, value, ctx) {
|
|
13938
|
-
if (!isPlainObject$
|
|
13958
|
+
if (!isPlainObject$6(value)) return [issue(plan, "wrongType", { expected: "an object" })];
|
|
13939
13959
|
if (plan.childSelection.kind === "discriminatedTabs") {
|
|
13940
13960
|
const selectedType = value[plan.childSelection.activeTabField];
|
|
13941
13961
|
if (selectedType !== void 0 && selectedType !== null && !plan.childSelection.tabs.some((tab) => tab.typeId === selectedType)) return [{
|
|
@@ -14238,7 +14258,7 @@ function isEmptyValue$1(value) {
|
|
|
14238
14258
|
if (isEmptyRichTextDoc(value)) return true;
|
|
14239
14259
|
return false;
|
|
14240
14260
|
}
|
|
14241
|
-
function isPlainObject$
|
|
14261
|
+
function isPlainObject$6(value) {
|
|
14242
14262
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
14243
14263
|
}
|
|
14244
14264
|
function isUnknownArray$2(value) {
|
|
@@ -14248,7 +14268,7 @@ function isValidRichTextDoc(value) {
|
|
|
14248
14268
|
return isRichTextDoc(value);
|
|
14249
14269
|
}
|
|
14250
14270
|
function isEmptyRichTextDoc(value) {
|
|
14251
|
-
if (!isPlainObject$
|
|
14271
|
+
if (!isPlainObject$6(value)) return false;
|
|
14252
14272
|
if (value.type !== "doc") return false;
|
|
14253
14273
|
const content = value.content;
|
|
14254
14274
|
if (!Array.isArray(content) || content.length === 0) return true;
|
|
@@ -14472,6 +14492,23 @@ function defineLookOptionGroup(group) {
|
|
|
14472
14492
|
defaultOption(group);
|
|
14473
14493
|
return group;
|
|
14474
14494
|
}
|
|
14495
|
+
function resolveLookOption(group, requestedOptionId) {
|
|
14496
|
+
const fallback = defaultOption(group);
|
|
14497
|
+
if (requestedOptionId === null || requestedOptionId === void 0) return {
|
|
14498
|
+
kind: "defaulted",
|
|
14499
|
+
option: fallback
|
|
14500
|
+
};
|
|
14501
|
+
const selected = group.options.find((option) => option.id === requestedOptionId);
|
|
14502
|
+
return selected ? {
|
|
14503
|
+
kind: "selected",
|
|
14504
|
+
option: selected
|
|
14505
|
+
} : {
|
|
14506
|
+
kind: "repaired",
|
|
14507
|
+
requestedOptionId,
|
|
14508
|
+
option: fallback,
|
|
14509
|
+
reason: "unsupported-option"
|
|
14510
|
+
};
|
|
14511
|
+
}
|
|
14475
14512
|
function defaultOption(group) {
|
|
14476
14513
|
const option = group.options.find((candidate) => candidate.id === group.defaultOptionId);
|
|
14477
14514
|
if (option) return option;
|
|
@@ -15362,6 +15399,12 @@ function cardRoleFromLegacyVocabulary(value) {
|
|
|
15362
15399
|
*/
|
|
15363
15400
|
const cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
|
|
15364
15401
|
//#endregion
|
|
15402
|
+
//#region ../theme-core/src/site-styles/compilationPlan.ts
|
|
15403
|
+
const defaultHeaderBrandPreferences = {
|
|
15404
|
+
brandDisplay: "auto",
|
|
15405
|
+
logoSize: "auto"
|
|
15406
|
+
};
|
|
15407
|
+
//#endregion
|
|
15365
15408
|
//#region ../theme-core/src/site-styles/sectionTransitions.ts
|
|
15366
15409
|
function boundaryRuntimeMetrics(family, size, heightExpression) {
|
|
15367
15410
|
return {
|
|
@@ -17743,6 +17786,12 @@ const overlayLogoAccommodations = [
|
|
|
17743
17786
|
inlineMarkWithTitleLogoAccommodation,
|
|
17744
17787
|
unbackedOverlayLogoAccommodation
|
|
17745
17788
|
];
|
|
17789
|
+
const allCuratedLogoAccommodations = [
|
|
17790
|
+
...inlineClassicLogoAccommodations,
|
|
17791
|
+
...centeredLogoAccommodations,
|
|
17792
|
+
...overlayLogoAccommodations
|
|
17793
|
+
];
|
|
17794
|
+
new Set(allCuratedLogoAccommodations.filter((option) => option.providesBacking === true).map((option) => option.pattern));
|
|
17746
17795
|
const headerLayoutCatalog = defineExactCatalog()([
|
|
17747
17796
|
{
|
|
17748
17797
|
id: asHeaderLayoutId("classic"),
|
|
@@ -17892,9 +17941,50 @@ defineLookOptionGroup({
|
|
|
17892
17941
|
{
|
|
17893
17942
|
id: "secondary",
|
|
17894
17943
|
label: "Secondary"
|
|
17944
|
+
},
|
|
17945
|
+
{
|
|
17946
|
+
id: headerInteractionQuietPalette,
|
|
17947
|
+
label: "Text only"
|
|
17895
17948
|
}
|
|
17896
17949
|
]
|
|
17897
17950
|
});
|
|
17951
|
+
/**
|
|
17952
|
+
* Key-sorted serialisation, because one side of the comparison is built here
|
|
17953
|
+
* and the other comes back out of `jsonb` in whatever order it was stored.
|
|
17954
|
+
* `JSON.stringify` alone reported two identical effect lists as different,
|
|
17955
|
+
* which is the same key-order trap the `logoText` derivation hit in Phase 4a.
|
|
17956
|
+
*/
|
|
17957
|
+
function canonicalJson(value) {
|
|
17958
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
17959
|
+
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(",")}}`;
|
|
17960
|
+
return value === void 0 ? "undefined" : JSON.stringify(value);
|
|
17961
|
+
}
|
|
17962
|
+
//#endregion
|
|
17963
|
+
//#region ../theme-core/src/site-styles/headerNavStyles.ts
|
|
17964
|
+
function applyNavTypography(style, typography) {
|
|
17965
|
+
switch (typography) {
|
|
17966
|
+
case "plain": return style;
|
|
17967
|
+
case "heading": return {
|
|
17968
|
+
...style,
|
|
17969
|
+
typography: "heading",
|
|
17970
|
+
fontWeight: "medium",
|
|
17971
|
+
letterSpacing: "wide"
|
|
17972
|
+
};
|
|
17973
|
+
case "quiet-caps": return {
|
|
17974
|
+
...style,
|
|
17975
|
+
textTransform: "uppercase",
|
|
17976
|
+
textSize: "sm",
|
|
17977
|
+
letterSpacing: "wide"
|
|
17978
|
+
};
|
|
17979
|
+
case "brand-caps": return {
|
|
17980
|
+
...style,
|
|
17981
|
+
textTransform: "uppercase",
|
|
17982
|
+
textSize: "sm",
|
|
17983
|
+
letterSpacing: "wider"
|
|
17984
|
+
};
|
|
17985
|
+
default: return assertNever$24(typography, "Unhandled header nav typography");
|
|
17986
|
+
}
|
|
17987
|
+
}
|
|
17898
17988
|
function asFooterLookId(value) {
|
|
17899
17989
|
return value;
|
|
17900
17990
|
}
|
|
@@ -18603,6 +18693,37 @@ function uniformSectionForeground(token) {
|
|
|
18603
18693
|
supportingColor: token
|
|
18604
18694
|
};
|
|
18605
18695
|
}
|
|
18696
|
+
function compileTransitionRuntime(input) {
|
|
18697
|
+
return {
|
|
18698
|
+
treatment: input.treatment,
|
|
18699
|
+
from: input.from,
|
|
18700
|
+
to: input.to,
|
|
18701
|
+
cssVars: {
|
|
18702
|
+
from: transitionEndpointCssValue(input.from),
|
|
18703
|
+
to: transitionEndpointCssValue(input.to)
|
|
18704
|
+
}
|
|
18705
|
+
};
|
|
18706
|
+
}
|
|
18707
|
+
function transitionEndpointCssValue(endpoint) {
|
|
18708
|
+
switch (endpoint.kind) {
|
|
18709
|
+
case "color-token": return themeColorTokenCssValue(endpoint.token);
|
|
18710
|
+
case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
|
|
18711
|
+
default: return assertNever$24(endpoint);
|
|
18712
|
+
}
|
|
18713
|
+
}
|
|
18714
|
+
function tokenForTransitionSurface(surface) {
|
|
18715
|
+
switch (surface) {
|
|
18716
|
+
case "base":
|
|
18717
|
+
case "hero-image": return "background";
|
|
18718
|
+
case "muted-band": return "muted";
|
|
18719
|
+
case "accent-band": return "accent";
|
|
18720
|
+
case "brand-depth-band": return "primary-800";
|
|
18721
|
+
default: return assertNever$24(surface);
|
|
18722
|
+
}
|
|
18723
|
+
}
|
|
18724
|
+
function themeColorTokenCssValue(token) {
|
|
18725
|
+
return `rgb(var(--tb-${token}))`;
|
|
18726
|
+
}
|
|
18606
18727
|
//#endregion
|
|
18607
18728
|
//#region ../theme-core/src/site-styles/curatedSiteStyles.ts
|
|
18608
18729
|
const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
|
|
@@ -20310,6 +20431,142 @@ const blockDesignFallback = {
|
|
|
20310
20431
|
};
|
|
20311
20432
|
blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
|
|
20312
20433
|
//#endregion
|
|
20434
|
+
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
20435
|
+
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
20436
|
+
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
20437
|
+
siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
|
|
20438
|
+
function parseSiteChromeBackgroundColorToken(value) {
|
|
20439
|
+
const token = parseBackgroundAppearanceColorToken(value);
|
|
20440
|
+
return token ? token : null;
|
|
20441
|
+
}
|
|
20442
|
+
function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
20443
|
+
return tokenNames.flatMap((name) => {
|
|
20444
|
+
const token = parseSiteChromeBackgroundColorToken(name);
|
|
20445
|
+
return token ? [token] : [];
|
|
20446
|
+
});
|
|
20447
|
+
}
|
|
20448
|
+
//#endregion
|
|
20449
|
+
//#region ../theme-core/src/site-styles/siteChromeNavPreferences.ts
|
|
20450
|
+
const headerNavTypographyPreferenceValues = [
|
|
20451
|
+
"inherit",
|
|
20452
|
+
"body",
|
|
20453
|
+
"heading"
|
|
20454
|
+
];
|
|
20455
|
+
const headerNavFontSizePreferenceValues = [
|
|
20456
|
+
"inherit",
|
|
20457
|
+
"sm",
|
|
20458
|
+
"base",
|
|
20459
|
+
"lg",
|
|
20460
|
+
"xl"
|
|
20461
|
+
];
|
|
20462
|
+
const defaultHeaderNavPreferences = {
|
|
20463
|
+
typography: "inherit",
|
|
20464
|
+
fontSize: "inherit",
|
|
20465
|
+
interactionPalette: "recommended"
|
|
20466
|
+
};
|
|
20467
|
+
/**
|
|
20468
|
+
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
20469
|
+
* nav link style. This is where the precedence contract lands:
|
|
20470
|
+
*
|
|
20471
|
+
* - The nav typography TREATMENT (case/tracking identity: plain / heading /
|
|
20472
|
+
* quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
|
|
20473
|
+
* baked into `style` before this call (see `applyNavTypography` in
|
|
20474
|
+
* headerLayoutStyles.ts).
|
|
20475
|
+
* - Each treatment resolves through a DEFAULT font role: `heading` resolves
|
|
20476
|
+
* through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
|
|
20477
|
+
* through the body role.
|
|
20478
|
+
* - The user's `navFontRole` (surfaced here as `preferences.typography`) only
|
|
20479
|
+
* overrides which font ROLE the treatment resolves through. It never changes
|
|
20480
|
+
* the treatment. `'inherit'` keeps the treatment's default role.
|
|
20481
|
+
*/
|
|
20482
|
+
function mergeHeaderNavTypographyPreference(style, preferences) {
|
|
20483
|
+
return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
|
|
20484
|
+
...style,
|
|
20485
|
+
...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
|
|
20486
|
+
...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
|
|
20487
|
+
};
|
|
20488
|
+
}
|
|
20489
|
+
function readHeaderNavFontSizePreference(value) {
|
|
20490
|
+
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
20491
|
+
}
|
|
20492
|
+
//#endregion
|
|
20493
|
+
//#region ../theme-core/src/site-styles/headerBrandPresentation.ts
|
|
20494
|
+
/**
|
|
20495
|
+
* Whether the header wears a brand presentation at all (#1318 Phase 4c).
|
|
20496
|
+
*
|
|
20497
|
+
* `applyHeaderBrandPresentation` is unconditional today: given a logo context
|
|
20498
|
+
* it always writes a `logo` object carrying a resolved display, size and
|
|
20499
|
+
* accommodation. That makes "this header has no brand presentation block" —
|
|
20500
|
+
* which is what several live sites persist, including one whose site *does*
|
|
20501
|
+
* have a logo asset — a state the preference record could not express, so a
|
|
20502
|
+
* recompile would silently give the site a logo treatment it has never
|
|
20503
|
+
* rendered.
|
|
20504
|
+
*
|
|
20505
|
+
* This is the third field in this phase with that shape, after the interaction
|
|
20506
|
+
* palette and the nav text size: the persisted bytes carry intent the reader
|
|
20507
|
+
* cannot say, so the recompile "corrects" it. The fix is the same one — widen
|
|
20508
|
+
* the closed vocabulary with a named member and derive it by baseline
|
|
20509
|
+
* comparison — and it is a *widening*, not a restyle: `'recommended'` is the
|
|
20510
|
+
* default and reproduces today's behaviour exactly, so no row whose bytes carry
|
|
20511
|
+
* a presentation compiles differently for `'plain'` existing.
|
|
20512
|
+
*
|
|
20513
|
+
* `'plain'` is deliberately not "preserve whatever bytes are there". It is a
|
|
20514
|
+
* constructive instruction — *write no presentation block* — so Phase 4e's
|
|
20515
|
+
* compiler, which builds the header from nothing, can produce it.
|
|
20516
|
+
*/
|
|
20517
|
+
const headerBrandPresentationValues = ["recommended", "plain"];
|
|
20518
|
+
/**
|
|
20519
|
+
* Key-order-safe structural equality over the persisted `logo` object.
|
|
20520
|
+
*
|
|
20521
|
+
* The persisted value comes back from `jsonb`, whose key order is not the order
|
|
20522
|
+
* the applier builds; a `JSON.stringify` comparison would read two equal logo
|
|
20523
|
+
* blocks as different and quietly classify a `'recommended'` row as
|
|
20524
|
+
* unrecognised — the same trap the interaction-palette transcription test
|
|
20525
|
+
* exists to catch.
|
|
20526
|
+
*/
|
|
20527
|
+
function jsonEquals(left, right) {
|
|
20528
|
+
if (left === right) return true;
|
|
20529
|
+
if (left === null || right === null) return false;
|
|
20530
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
20531
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false;
|
|
20532
|
+
if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((value, index) => jsonEquals(value, right[index]));
|
|
20533
|
+
const leftRecord = left;
|
|
20534
|
+
const rightRecord = right;
|
|
20535
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(leftRecord), ...Object.keys(rightRecord)]);
|
|
20536
|
+
for (const key of keys) if (!jsonEquals(leftRecord[key], rightRecord[key])) return false;
|
|
20537
|
+
return true;
|
|
20538
|
+
}
|
|
20539
|
+
defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
|
|
20540
|
+
function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
|
|
20541
|
+
const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
|
|
20542
|
+
const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
|
|
20543
|
+
if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
|
|
20544
|
+
const transition = compileTransitionRuntime({
|
|
20545
|
+
treatment: boundary.treatment,
|
|
20546
|
+
from: fromSurface,
|
|
20547
|
+
to: toSurface
|
|
20548
|
+
});
|
|
20549
|
+
return {
|
|
20550
|
+
...boundary,
|
|
20551
|
+
from: transition.from,
|
|
20552
|
+
to: transition.to,
|
|
20553
|
+
cssVars: transition.cssVars
|
|
20554
|
+
};
|
|
20555
|
+
}
|
|
20556
|
+
function contentOwnedBackgroundEndpoint(content) {
|
|
20557
|
+
const background = readRecord(readRecord(content?._sectionStyles)?.background);
|
|
20558
|
+
if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
|
|
20559
|
+
const token = parseThemeColorToken(background.color);
|
|
20560
|
+
if (!token) return null;
|
|
20561
|
+
return {
|
|
20562
|
+
kind: "color-token",
|
|
20563
|
+
token
|
|
20564
|
+
};
|
|
20565
|
+
}
|
|
20566
|
+
function readRecord(value) {
|
|
20567
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
20568
|
+
}
|
|
20569
|
+
//#endregion
|
|
20313
20570
|
//#region ../theme-core/src/site-styles/generatedSchemas.ts
|
|
20314
20571
|
function uniqueNonEmpty(values) {
|
|
20315
20572
|
const uniqueValues = [...new Set(values)];
|
|
@@ -20378,6 +20635,76 @@ z$1.object({
|
|
|
20378
20635
|
blocks: z$1.array(generatedPageDesignBlockIntentSchema)
|
|
20379
20636
|
}).strict();
|
|
20380
20637
|
//#endregion
|
|
20638
|
+
//#region ../theme-core/src/site-styles/headerPreferencesPersistence.ts
|
|
20639
|
+
const backgroundColorTokenSchema = z$1.unknown().transform((value, ctx) => {
|
|
20640
|
+
const token = parseSiteChromeBackgroundColorToken(value);
|
|
20641
|
+
if (token === null) {
|
|
20642
|
+
ctx.addIssue({
|
|
20643
|
+
code: "custom",
|
|
20644
|
+
message: "Unknown site chrome background colour token"
|
|
20645
|
+
});
|
|
20646
|
+
return z$1.NEVER;
|
|
20647
|
+
}
|
|
20648
|
+
return token;
|
|
20649
|
+
});
|
|
20650
|
+
const backgroundOverrideSchema = z$1.discriminatedUnion("kind", [z$1.object({ kind: z$1.literal("default") }).strict(), z$1.object({
|
|
20651
|
+
kind: z$1.literal("color"),
|
|
20652
|
+
token: backgroundColorTokenSchema
|
|
20653
|
+
}).strict()]);
|
|
20654
|
+
const logoTextIntentSchema = z$1.object({
|
|
20655
|
+
mobileMaxWidth: z$1.enum([
|
|
20656
|
+
"xs",
|
|
20657
|
+
"sm",
|
|
20658
|
+
"md"
|
|
20659
|
+
]).optional(),
|
|
20660
|
+
mobileWrapLines: z$1.union([z$1.literal(1), z$1.literal(2)]).optional(),
|
|
20661
|
+
hideOnShrink: z$1.boolean().optional()
|
|
20662
|
+
}).strict();
|
|
20663
|
+
const structureSchema = z$1.object({
|
|
20664
|
+
positioning: z$1.union([z$1.literal("inherit"), z$1.enum(headerPositioningValues)]),
|
|
20665
|
+
shrinkOnScroll: z$1.union([z$1.literal("inherit"), z$1.boolean()]),
|
|
20666
|
+
logoText: z$1.union([
|
|
20667
|
+
z$1.literal("inherit"),
|
|
20668
|
+
z$1.literal("none"),
|
|
20669
|
+
logoTextIntentSchema
|
|
20670
|
+
])
|
|
20671
|
+
}).strict();
|
|
20672
|
+
const headerPreferencesShape = {
|
|
20673
|
+
brandPresentation: z$1.enum(headerBrandPresentationValues),
|
|
20674
|
+
brandDisplay: z$1.enum(brandDisplayModeValues),
|
|
20675
|
+
logoSize: z$1.enum(logoSizePolicyValues),
|
|
20676
|
+
navFontRole: z$1.enum(headerNavTypographyPreferenceValues),
|
|
20677
|
+
navTextSize: z$1.enum(headerNavFontSizePreferenceValues),
|
|
20678
|
+
interactionPalette: z$1.union([z$1.literal("recommended"), z$1.enum(headerInteractionPaletteValues)]),
|
|
20679
|
+
background: backgroundOverrideSchema,
|
|
20680
|
+
structure: structureSchema
|
|
20681
|
+
};
|
|
20682
|
+
z$1.object(headerPreferencesShape).strict();
|
|
20683
|
+
z$1.object(headerPreferencesShape).omit({
|
|
20684
|
+
brandPresentation: true,
|
|
20685
|
+
structure: true
|
|
20686
|
+
}).strict();
|
|
20687
|
+
//#endregion
|
|
20688
|
+
//#region ../theme-core/src/site-styles/headerBytesComparison.ts
|
|
20689
|
+
function isPlainObject$5(value) {
|
|
20690
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20691
|
+
}
|
|
20692
|
+
/**
|
|
20693
|
+
* Structural diff by JSON path. Values are compared through
|
|
20694
|
+
* `JSON.stringify`, so a key present with the value `undefined` and an absent
|
|
20695
|
+
* key read as the same thing — which is what `jsonb` does to them anyway
|
|
20696
|
+
* (#1318 Correction 5).
|
|
20697
|
+
*/
|
|
20698
|
+
function diffJsonPaths(left, right, path = "") {
|
|
20699
|
+
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));
|
|
20700
|
+
if (JSON.stringify(left) === JSON.stringify(right)) return [];
|
|
20701
|
+
return [{
|
|
20702
|
+
path,
|
|
20703
|
+
left,
|
|
20704
|
+
right
|
|
20705
|
+
}];
|
|
20706
|
+
}
|
|
20707
|
+
//#endregion
|
|
20381
20708
|
//#region ../theme-core/src/site-styles/pageDesignAuthority.ts
|
|
20382
20709
|
const nonEmptyStringSchema = z$1.string().min(1);
|
|
20383
20710
|
const blockContentVersionIdSchema = nonEmptyStringSchema.transform(asBlockContentVersionId);
|
|
@@ -20479,46 +20806,6 @@ function collectBlockSequenceIssues(snapshots) {
|
|
|
20479
20806
|
return issues;
|
|
20480
20807
|
}
|
|
20481
20808
|
//#endregion
|
|
20482
|
-
//#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
|
|
20483
|
-
const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
|
|
20484
|
-
const quickSiteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceQuickColorTokenNames);
|
|
20485
|
-
siteChromeBackgroundColorTokens.filter((token) => !quickSiteChromeBackgroundColorTokens.includes(token));
|
|
20486
|
-
function parseSiteChromeBackgroundColorToken(value) {
|
|
20487
|
-
const token = parseBackgroundAppearanceColorToken(value);
|
|
20488
|
-
return token ? token : null;
|
|
20489
|
-
}
|
|
20490
|
-
function toSiteChromeBackgroundColorTokens(tokenNames) {
|
|
20491
|
-
return tokenNames.flatMap((name) => {
|
|
20492
|
-
const token = parseSiteChromeBackgroundColorToken(name);
|
|
20493
|
-
return token ? [token] : [];
|
|
20494
|
-
});
|
|
20495
|
-
}
|
|
20496
|
-
/**
|
|
20497
|
-
* Applies the user's nav font-role/size overrides on top of a style-resolved
|
|
20498
|
-
* nav link style. This is where the precedence contract lands:
|
|
20499
|
-
*
|
|
20500
|
-
* - The nav typography TREATMENT (case/tracking identity: plain / heading /
|
|
20501
|
-
* quiet-caps / brand-caps) is ALWAYS owned by the active header style and is
|
|
20502
|
-
* baked into `style` before this call (see `applyNavTypography` in
|
|
20503
|
-
* headerLayoutStyles.ts).
|
|
20504
|
-
* - Each treatment resolves through a DEFAULT font role: `heading` resolves
|
|
20505
|
-
* through the heading role; `plain` / `quiet-caps` / `brand-caps` resolve
|
|
20506
|
-
* through the body role.
|
|
20507
|
-
* - The user's `navFontRole` (surfaced here as `preferences.typography`) only
|
|
20508
|
-
* overrides which font ROLE the treatment resolves through. It never changes
|
|
20509
|
-
* the treatment. `'inherit'` keeps the treatment's default role.
|
|
20510
|
-
*/
|
|
20511
|
-
function mergeHeaderNavTypographyPreference(style, preferences) {
|
|
20512
|
-
return preferences.typography === "inherit" && preferences.fontSize === "inherit" ? style : {
|
|
20513
|
-
...style,
|
|
20514
|
-
...preferences.typography === "inherit" ? {} : { typography: preferences.typography },
|
|
20515
|
-
...preferences.fontSize === "inherit" ? {} : { textSize: preferences.fontSize }
|
|
20516
|
-
};
|
|
20517
|
-
}
|
|
20518
|
-
function readHeaderNavFontSizePreference(value) {
|
|
20519
|
-
return value === "sm" || value === "base" || value === "lg" || value === "xl" ? value : "inherit";
|
|
20520
|
-
}
|
|
20521
|
-
//#endregion
|
|
20522
20809
|
//#region ../theme-core/src/site-styles/tokenRecipes.ts
|
|
20523
20810
|
const themeTokenRecipeOptions = {
|
|
20524
20811
|
palette: [
|
|
@@ -32342,7 +32629,7 @@ const eventDisplayFields = [
|
|
|
32342
32629
|
{
|
|
32343
32630
|
id: "showCapacity",
|
|
32344
32631
|
type: "boolean",
|
|
32345
|
-
label: "Show available
|
|
32632
|
+
label: "Show available places",
|
|
32346
32633
|
description: "Display remaining capacity",
|
|
32347
32634
|
required: false,
|
|
32348
32635
|
defaultValue: true,
|
|
@@ -32667,303 +32954,1273 @@ function planEventRegistrationScope(input) {
|
|
|
32667
32954
|
};
|
|
32668
32955
|
}
|
|
32669
32956
|
//#endregion
|
|
32670
|
-
//#region ../blocks/src/system/runtime/nodes/events/
|
|
32671
|
-
|
|
32672
|
-
|
|
32673
|
-
}
|
|
32674
|
-
|
|
32675
|
-
|
|
32676
|
-
|
|
32677
|
-
|
|
32678
|
-
|
|
32679
|
-
function
|
|
32680
|
-
if (
|
|
32681
|
-
|
|
32682
|
-
|
|
32683
|
-
mode: event.eventDestinationMode === "system-managed" ? "system-managed" : "cms-managed",
|
|
32684
|
-
path: typeof event.path === "string" ? event.path : ""
|
|
32685
|
-
};
|
|
32686
|
-
}
|
|
32687
|
-
//#endregion
|
|
32688
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/pricing.ts
|
|
32689
|
-
const DEFAULT_EVENT_CURRENCY = "USD";
|
|
32690
|
-
function resolveRuntimeEventPricing(event) {
|
|
32691
|
-
if (isPublicEventPricing(event.pricing)) return event.pricing;
|
|
32692
|
-
if (resolveRuntimeEventDestination(event).kind === "external") return {
|
|
32693
|
-
kind: "external",
|
|
32694
|
-
display: {
|
|
32695
|
-
kind: "external",
|
|
32696
|
-
label: "External registration"
|
|
32697
|
-
}
|
|
32698
|
-
};
|
|
32699
|
-
const legacy = event;
|
|
32700
|
-
const currency = readCurrency(legacy.currency);
|
|
32701
|
-
if (event.requiresRegistration === false) return {
|
|
32702
|
-
kind: "drop_in",
|
|
32703
|
-
currency,
|
|
32704
|
-
display: {
|
|
32705
|
-
kind: "free",
|
|
32706
|
-
currency
|
|
32707
|
-
}
|
|
32708
|
-
};
|
|
32709
|
-
if (legacy.pricingMode === "ticket_types") return {
|
|
32710
|
-
kind: "ticket_types",
|
|
32711
|
-
currency,
|
|
32712
|
-
ticketTypes: Array.isArray(legacy.ticketTypes) ? legacy.ticketTypes.filter(isPublicEventTicketType) : [],
|
|
32713
|
-
display: readTicketTypePriceDisplay(legacy.priceDisplay, currency)
|
|
32957
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
|
|
32958
|
+
/**
|
|
32959
|
+
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
32960
|
+
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
32961
|
+
* for uncapped events so call sites can render no capacity indicator at all.
|
|
32962
|
+
*
|
|
32963
|
+
* The single source of truth for capacity math across event cards, agenda
|
|
32964
|
+
* rows, and timetable cells — keep wording variations in the call sites.
|
|
32965
|
+
*/
|
|
32966
|
+
function getEventAvailability(event) {
|
|
32967
|
+
if (event.capacity == null) return {
|
|
32968
|
+
available: null,
|
|
32969
|
+
isFull: false
|
|
32714
32970
|
};
|
|
32715
|
-
const
|
|
32716
|
-
if (
|
|
32717
|
-
|
|
32718
|
-
|
|
32719
|
-
currency,
|
|
32720
|
-
display: {
|
|
32721
|
-
kind: "exact",
|
|
32722
|
-
amount,
|
|
32723
|
-
currency
|
|
32724
|
-
}
|
|
32971
|
+
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
32972
|
+
if (available <= 0) return {
|
|
32973
|
+
available: 0,
|
|
32974
|
+
isFull: true
|
|
32725
32975
|
};
|
|
32726
32976
|
return {
|
|
32727
|
-
|
|
32728
|
-
|
|
32729
|
-
display: {
|
|
32730
|
-
kind: "free",
|
|
32731
|
-
currency
|
|
32732
|
-
}
|
|
32977
|
+
available,
|
|
32978
|
+
isFull: false
|
|
32733
32979
|
};
|
|
32734
32980
|
}
|
|
32735
|
-
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32981
|
+
/**
|
|
32982
|
+
* Pick the capacity row a booking decision should read: the selected
|
|
32983
|
+
* attendance mode's availability when the visitor has chosen one and the
|
|
32984
|
+
* event publishes it, otherwise the event-level aggregate.
|
|
32985
|
+
*
|
|
32986
|
+
* The mode row is used wholesale. A mode with `capacity: null` on a capped
|
|
32987
|
+
* event is uncapped *by design*: the registration write path applies the
|
|
32988
|
+
* same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
|
|
32989
|
+
* back to the event's `availableSpots` here would block bookings the server
|
|
32990
|
+
* accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
|
|
32991
|
+
*/
|
|
32992
|
+
function selectEventCapacitySource(event, attendanceMode) {
|
|
32993
|
+
if (!attendanceMode) return event;
|
|
32994
|
+
return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
|
|
32744
32995
|
}
|
|
32745
|
-
|
|
32746
|
-
|
|
32996
|
+
/**
|
|
32997
|
+
* Whether the visitor's next step is the waitlist: the source is full *and*
|
|
32998
|
+
* a waitlist is open. The one place this is decided — cards, rows, the
|
|
32999
|
+
* wizard's steps and its action bar all route through here so they agree,
|
|
33000
|
+
* including for producers that omit `availableSpots` and rely on the
|
|
33001
|
+
* `capacity - registeredCount` fallback.
|
|
33002
|
+
*/
|
|
33003
|
+
function canJoinEventWaitlist(event, options) {
|
|
33004
|
+
const source = selectEventCapacitySource(event, options?.attendanceMode);
|
|
33005
|
+
return getEventAvailability(source).isFull && source.waitlistEnabled === true;
|
|
32747
33006
|
}
|
|
32748
|
-
|
|
32749
|
-
|
|
32750
|
-
|
|
32751
|
-
|
|
32752
|
-
|
|
32753
|
-
|
|
33007
|
+
/**
|
|
33008
|
+
* Remaining places at or below which an event reads as scarce and the exact
|
|
33009
|
+
* count is worth showing. Above it, no number is shown at all: "147 places
|
|
33010
|
+
* left" is not something a visitor acts on, and it is the figure most likely
|
|
33011
|
+
* to be stale on a cached page.
|
|
33012
|
+
*
|
|
33013
|
+
* max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
|
|
33014
|
+
*
|
|
33015
|
+
* The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
|
|
33016
|
+
* 5% floor stops large events staying silent until three remain.
|
|
33017
|
+
* capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
|
|
33018
|
+
*/
|
|
33019
|
+
function lowCapacityThreshold(capacity) {
|
|
33020
|
+
return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
|
|
32754
33021
|
}
|
|
32755
|
-
function
|
|
32756
|
-
|
|
32757
|
-
|
|
32758
|
-
|
|
32759
|
-
|
|
32760
|
-
|
|
32761
|
-
|
|
32762
|
-
|
|
32763
|
-
|
|
32764
|
-
|
|
32765
|
-
|
|
32766
|
-
|
|
32767
|
-
|
|
32768
|
-
|
|
32769
|
-
|
|
32770
|
-
|
|
32771
|
-
|
|
32772
|
-
|
|
32773
|
-
|
|
32774
|
-
|
|
32775
|
-
|
|
32776
|
-
|
|
32777
|
-
|
|
32778
|
-
|
|
32779
|
-
|
|
32780
|
-
|
|
32781
|
-
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
|
|
32786
|
-
|
|
32787
|
-
|
|
32788
|
-
|
|
32789
|
-
|
|
32790
|
-
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32796
|
-
|
|
32797
|
-
|
|
32798
|
-
|
|
32799
|
-
|
|
32800
|
-
|
|
32801
|
-
|
|
32802
|
-
currency
|
|
33022
|
+
function getRemainingPlaces(source) {
|
|
33023
|
+
const { available, isFull } = getEventAvailability(source);
|
|
33024
|
+
if (available === null || source.capacity === null) return { kind: "unlimited" };
|
|
33025
|
+
if (isFull) return {
|
|
33026
|
+
kind: "full",
|
|
33027
|
+
waitlist: canJoinEventWaitlist(source)
|
|
33028
|
+
};
|
|
33029
|
+
if (available <= lowCapacityThreshold(source.capacity)) return {
|
|
33030
|
+
kind: "low",
|
|
33031
|
+
remaining: available
|
|
33032
|
+
};
|
|
33033
|
+
return { kind: "open" };
|
|
33034
|
+
}
|
|
33035
|
+
/**
|
|
33036
|
+
* The one place a remaining-places count becomes text. Returns `null` when
|
|
33037
|
+
* nothing should be shown: uncapped events, and capped events above the
|
|
33038
|
+
* scarcity threshold. Surfaces that need their own full-state wording pass
|
|
33039
|
+
* it in rather than re-deriving the count.
|
|
33040
|
+
*/
|
|
33041
|
+
function formatRemainingPlaces(source, wording = {}) {
|
|
33042
|
+
const remaining = getRemainingPlaces(source);
|
|
33043
|
+
switch (remaining.kind) {
|
|
33044
|
+
case "unlimited":
|
|
33045
|
+
case "open": return null;
|
|
33046
|
+
case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
|
|
33047
|
+
case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
|
|
33048
|
+
}
|
|
33049
|
+
}
|
|
33050
|
+
function formatPlacesLeft(count, compact) {
|
|
33051
|
+
if (compact) return `${count} left`;
|
|
33052
|
+
return count === 1 ? "1 place left" : `${count} places left`;
|
|
33053
|
+
}
|
|
33054
|
+
/** Total capacity as copy ("12 places"), for detail surfaces that state the size of an event. */
|
|
33055
|
+
function formatTotalCapacity(capacity) {
|
|
33056
|
+
if (capacity === null) return null;
|
|
33057
|
+
return capacity === 1 ? "1 place" : `${capacity} places`;
|
|
33058
|
+
}
|
|
33059
|
+
function getEventAvailabilityStatus(event) {
|
|
33060
|
+
const source = selectMostOpenCapacitySource(event);
|
|
33061
|
+
const remaining = getRemainingPlaces(source);
|
|
33062
|
+
const label = formatEventRemainingPlaces(event, { compact: true });
|
|
33063
|
+
switch (remaining.kind) {
|
|
33064
|
+
case "unlimited": return {
|
|
33065
|
+
status: "available",
|
|
33066
|
+
available: null,
|
|
33067
|
+
isFull: false,
|
|
33068
|
+
label
|
|
32803
33069
|
};
|
|
32804
|
-
case "
|
|
32805
|
-
|
|
32806
|
-
|
|
33070
|
+
case "open": return {
|
|
33071
|
+
status: "available",
|
|
33072
|
+
available: getEventAvailability(source).available,
|
|
33073
|
+
isFull: false,
|
|
33074
|
+
label
|
|
32807
33075
|
};
|
|
32808
|
-
case "
|
|
32809
|
-
|
|
32810
|
-
|
|
32811
|
-
|
|
32812
|
-
|
|
32813
|
-
display: {
|
|
32814
|
-
kind: "unavailable",
|
|
32815
|
-
currency: displayCurrency
|
|
32816
|
-
}
|
|
33076
|
+
case "low": return {
|
|
33077
|
+
status: "filling",
|
|
33078
|
+
available: remaining.remaining,
|
|
33079
|
+
isFull: false,
|
|
33080
|
+
label
|
|
32817
33081
|
};
|
|
32818
|
-
|
|
32819
|
-
|
|
32820
|
-
|
|
33082
|
+
case "full": return {
|
|
33083
|
+
status: "waitlist",
|
|
33084
|
+
available: 0,
|
|
33085
|
+
isFull: true,
|
|
33086
|
+
label
|
|
32821
33087
|
};
|
|
32822
33088
|
}
|
|
32823
33089
|
}
|
|
32824
|
-
|
|
32825
|
-
|
|
32826
|
-
|
|
32827
|
-
|
|
32828
|
-
|
|
32829
|
-
|
|
32830
|
-
|
|
32831
|
-
|
|
32832
|
-
|
|
32833
|
-
|
|
32834
|
-
|
|
32835
|
-
|
|
32836
|
-
|
|
32837
|
-
|
|
32838
|
-
|
|
32839
|
-
|
|
32840
|
-
|
|
32841
|
-
|
|
32842
|
-
|
|
32843
|
-
|
|
32844
|
-
|
|
32845
|
-
|
|
32846
|
-
|
|
32847
|
-
|
|
32848
|
-
|
|
32849
|
-
|
|
33090
|
+
/**
|
|
33091
|
+
* Which capacity figure wins when they disagree.
|
|
33092
|
+
*
|
|
33093
|
+
* For a hybrid event that publishes both attendance-mode rows, **the mode
|
|
33094
|
+
* rows are authoritative and the event-level aggregate is only a summary**.
|
|
33095
|
+
* The registration write path admits or refuses per mode
|
|
33096
|
+
* (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
|
|
33097
|
+
* aggregate can read full while a mode still has room, and can read open
|
|
33098
|
+
* while the mode the visitor wants is full. Believing the aggregate on a
|
|
33099
|
+
* card is how a "places available" event turns into a dead end at step two.
|
|
33100
|
+
*
|
|
33101
|
+
* Everything else — single-mode events, events that publish no rows, and the
|
|
33102
|
+
* aggregate-only helpers {@link getEventAvailability} /
|
|
33103
|
+
* {@link getRemainingPlaces} that existing callers rely on — keeps reading
|
|
33104
|
+
* the aggregate, unchanged.
|
|
33105
|
+
*/
|
|
33106
|
+
function findHybridModeRows(event) {
|
|
33107
|
+
if (event.deliveryMode !== "hybrid") return null;
|
|
33108
|
+
const rows = event.attendanceModes ?? [];
|
|
33109
|
+
const inPerson = rows.find((row) => row.mode === "in_person");
|
|
33110
|
+
const online = rows.find((row) => row.mode === "online");
|
|
33111
|
+
return inPerson && online ? {
|
|
33112
|
+
inPerson,
|
|
33113
|
+
online
|
|
33114
|
+
} : null;
|
|
33115
|
+
}
|
|
33116
|
+
/** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
|
|
33117
|
+
function remainingPlacesRank(remaining) {
|
|
33118
|
+
switch (remaining.kind) {
|
|
33119
|
+
case "unlimited": return 4;
|
|
33120
|
+
case "open": return 3;
|
|
33121
|
+
case "low": return 2;
|
|
33122
|
+
case "full": return remaining.waitlist ? 1 : 0;
|
|
32850
33123
|
}
|
|
32851
33124
|
}
|
|
32852
|
-
function
|
|
32853
|
-
|
|
33125
|
+
function isMoreOpen(candidate, than) {
|
|
33126
|
+
const rank = remainingPlacesRank(candidate);
|
|
33127
|
+
const otherRank = remainingPlacesRank(than);
|
|
33128
|
+
if (rank !== otherRank) return rank > otherRank;
|
|
33129
|
+
return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
|
|
32854
33130
|
}
|
|
32855
|
-
|
|
32856
|
-
|
|
32857
|
-
|
|
32858
|
-
"list",
|
|
32859
|
-
"timetable",
|
|
32860
|
-
"week-board"
|
|
32861
|
-
];
|
|
32862
|
-
const eventCalendarDefaultViewValues = ["month", "week"];
|
|
32863
|
-
const eventCalendarListLayoutValues = [
|
|
32864
|
-
"stack",
|
|
32865
|
-
"grid",
|
|
32866
|
-
"compact"
|
|
32867
|
-
];
|
|
32868
|
-
const eventCalendarListLayoutOptions = [
|
|
32869
|
-
{
|
|
32870
|
-
id: "stack",
|
|
32871
|
-
label: "Stack"
|
|
32872
|
-
},
|
|
32873
|
-
{
|
|
32874
|
-
id: "grid",
|
|
32875
|
-
label: "Grid"
|
|
32876
|
-
},
|
|
32877
|
-
{
|
|
32878
|
-
id: "compact",
|
|
32879
|
-
label: "Compact"
|
|
32880
|
-
}
|
|
32881
|
-
];
|
|
32882
|
-
const eventCalendarListLayoutCompatibleDisplayModeValues = ["combined", "list"];
|
|
32883
|
-
function isEventCalendarDisplayMode(value) {
|
|
32884
|
-
return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
|
|
33131
|
+
/** True when `candidate` has less room left than `than`. */
|
|
33132
|
+
function isScarcer(candidate, than) {
|
|
33133
|
+
return isMoreOpen(than, candidate);
|
|
32885
33134
|
}
|
|
32886
|
-
|
|
32887
|
-
|
|
33135
|
+
/**
|
|
33136
|
+
* The capacity row a whole-event decision should read: the most open
|
|
33137
|
+
* attendance mode of a hybrid event, otherwise the aggregate. A hybrid event
|
|
33138
|
+
* is sold out only when both of its modes are, and its waitlist is reachable
|
|
33139
|
+
* when either full mode has one open.
|
|
33140
|
+
*/
|
|
33141
|
+
function selectMostOpenCapacitySource(event) {
|
|
33142
|
+
const rows = findHybridModeRows(event);
|
|
33143
|
+
if (!rows) return event;
|
|
33144
|
+
return isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? rows.online : rows.inPerson;
|
|
32888
33145
|
}
|
|
32889
|
-
|
|
32890
|
-
|
|
33146
|
+
/** True when no attendance mode has room. Hybrid-aware; use this for sold-out decisions. */
|
|
33147
|
+
function isEventFull(event) {
|
|
33148
|
+
return getEventAvailability(selectMostOpenCapacitySource(event)).isFull;
|
|
32891
33149
|
}
|
|
32892
|
-
//#endregion
|
|
32893
|
-
//#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
|
|
32894
|
-
const previewStageSchema = z$1.enum(["preview", "published"]);
|
|
32895
|
-
const registrationButtonVariantSchema = blockButtonRoleSchema;
|
|
32896
|
-
const eventButtonVariantSchema = blockButtonRoleSchema;
|
|
32897
|
-
const eventLayoutSchema = z$1.enum(eventCalendarListLayoutValues);
|
|
32898
|
-
const eventCalendarDisplayModeSchema = z$1.enum(eventCalendarDisplayModeValues);
|
|
32899
|
-
const eventCalendarDefaultViewSchema = z$1.enum(eventCalendarDefaultViewValues);
|
|
32900
|
-
const columnOptionSchema = z$1.enum([
|
|
32901
|
-
"2",
|
|
32902
|
-
"3",
|
|
32903
|
-
"4"
|
|
32904
|
-
]);
|
|
32905
|
-
const weekStartSchema = z$1.enum(["sunday", "monday"]);
|
|
32906
|
-
const filterAutoShowSchema = z$1.enum([
|
|
32907
|
-
"auto",
|
|
32908
|
-
"always",
|
|
32909
|
-
"never"
|
|
32910
|
-
]);
|
|
32911
|
-
const calendarTabViewsSchema = z$1.enum([
|
|
32912
|
-
"both",
|
|
32913
|
-
"month",
|
|
32914
|
-
"week"
|
|
32915
|
-
]);
|
|
32916
33150
|
/**
|
|
32917
|
-
*
|
|
33151
|
+
* True when the event is full on every mode and at least one of those full
|
|
33152
|
+
* modes has a waitlist open. An event with a mode still bookable is not a
|
|
33153
|
+
* waitlist prospect: it should send the visitor to that mode instead.
|
|
33154
|
+
*/
|
|
33155
|
+
function canJoinEventWaitlistOnAnyMode(event) {
|
|
33156
|
+
return canJoinEventWaitlist(selectMostOpenCapacitySource(event));
|
|
33157
|
+
}
|
|
33158
|
+
const ATTENDANCE_MODE_LABEL = {
|
|
33159
|
+
in_person: "In person",
|
|
33160
|
+
online: "Online"
|
|
33161
|
+
};
|
|
33162
|
+
/** Wording for a mode with room to spare, where the count is deliberately withheld. */
|
|
33163
|
+
const PLACES_AVAILABLE = "Places available";
|
|
33164
|
+
/**
|
|
33165
|
+
* The browse-surface label for an event's remaining places. Single-mode
|
|
33166
|
+
* events get exactly the aggregate label {@link formatRemainingPlaces}
|
|
33167
|
+
* produces, and so does a hybrid event whose two modes tell the same story.
|
|
33168
|
+
* When the modes differ, both sides are named: "available" on a card is a
|
|
33169
|
+
* promise the registration wizard would otherwise have to break.
|
|
32918
33170
|
*
|
|
32919
|
-
*
|
|
32920
|
-
*
|
|
32921
|
-
*
|
|
33171
|
+
* Hybrid examples, in person full with 3 online places left:
|
|
33172
|
+
* default -> "Online: 3 places left · In person: Full"
|
|
33173
|
+
* { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
|
|
33174
|
+
* { compact: true } -> "Online: 3 left"
|
|
33175
|
+
*/
|
|
33176
|
+
function formatEventRemainingPlaces(event, wording = {}) {
|
|
33177
|
+
const rows = findHybridModeRows(event);
|
|
33178
|
+
if (!rows) return formatRemainingPlaces(event, wording);
|
|
33179
|
+
const inPerson = formatRemainingPlaces(rows.inPerson, wording);
|
|
33180
|
+
const online = formatRemainingPlaces(rows.online, wording);
|
|
33181
|
+
if (inPerson === online) return inPerson;
|
|
33182
|
+
if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
|
|
33183
|
+
return formatBothHybridSides(rows, {
|
|
33184
|
+
inPerson,
|
|
33185
|
+
online
|
|
33186
|
+
});
|
|
33187
|
+
}
|
|
33188
|
+
/**
|
|
33189
|
+
* Both sides, the one with room first: that is the half a visitor can act on,
|
|
33190
|
+
* and it should not arrive after a full stop's worth of bad news.
|
|
32922
33191
|
*/
|
|
32923
|
-
function
|
|
32924
|
-
|
|
32925
|
-
"select-event",
|
|
32926
|
-
"select-tickets",
|
|
32927
|
-
"enter-details"
|
|
32928
|
-
];
|
|
32929
|
-
if (!needsPaymentStep) return baseSteps;
|
|
32930
|
-
return [...baseSteps, "select-payment"];
|
|
33192
|
+
function formatBothHybridSides(rows, labels) {
|
|
33193
|
+
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(" · ");
|
|
32931
33194
|
}
|
|
32932
|
-
|
|
32933
|
-
|
|
32934
|
-
function eventMatchesOccurrenceId(event, occurrenceId) {
|
|
32935
|
-
return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
|
|
33195
|
+
function namedModePlaces(mode, label) {
|
|
33196
|
+
return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
|
|
32936
33197
|
}
|
|
32937
|
-
|
|
32938
|
-
|
|
32939
|
-
|
|
32940
|
-
|
|
32941
|
-
|
|
32942
|
-
|
|
32943
|
-
|
|
32944
|
-
|
|
32945
|
-
|
|
32946
|
-
|
|
32947
|
-
|
|
33198
|
+
/**
|
|
33199
|
+
* Pills and timetable cells have room for one short phrase, so they name the
|
|
33200
|
+
* single mode that carries news rather than listing both: the scarce side
|
|
33201
|
+
* when there is a count to show, and otherwise the side that has closed —
|
|
33202
|
+
* "In person: Full", never "Online only", which reads as a statement about
|
|
33203
|
+
* how the event is delivered rather than about its remaining places.
|
|
33204
|
+
*/
|
|
33205
|
+
function formatCompactHybridPlaces(rows, wording) {
|
|
33206
|
+
const inPerson = getRemainingPlaces(rows.inPerson);
|
|
33207
|
+
const online = getRemainingPlaces(rows.online);
|
|
33208
|
+
if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
|
|
33209
|
+
if (inPerson.kind === "full" || online.kind === "full") {
|
|
33210
|
+
const fullSide = inPerson.kind === "full" ? "in_person" : "online";
|
|
33211
|
+
const openSide = inPerson.kind === "full" ? "online" : "in_person";
|
|
33212
|
+
const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
|
|
33213
|
+
return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
|
|
33214
|
+
}
|
|
33215
|
+
const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
|
|
33216
|
+
return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
|
|
32948
33217
|
}
|
|
32949
|
-
function
|
|
32950
|
-
|
|
32951
|
-
if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
|
|
32952
|
-
if (!input.event) return false;
|
|
32953
|
-
return publicEventPricingType(resolveRuntimeEventPricing(input.event)) === "paid";
|
|
33218
|
+
function rowFor(rows, mode) {
|
|
33219
|
+
return mode === "online" ? rows.online : rows.inPerson;
|
|
32954
33220
|
}
|
|
32955
33221
|
//#endregion
|
|
32956
|
-
//#region ../blocks/src/system/runtime/nodes/
|
|
32957
|
-
function
|
|
32958
|
-
return
|
|
32959
|
-
|
|
32960
|
-
|
|
32961
|
-
|
|
32962
|
-
|
|
32963
|
-
|
|
32964
|
-
|
|
32965
|
-
|
|
32966
|
-
|
|
33222
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/destination.ts
|
|
33223
|
+
function isRecord$24(value) {
|
|
33224
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33225
|
+
}
|
|
33226
|
+
function isRuntimeEventDestination(value) {
|
|
33227
|
+
if (!isRecord$24(value) || typeof value.kind !== "string") return false;
|
|
33228
|
+
if (value.kind === "native") return (value.mode === "cms-managed" || value.mode === "system-managed") && typeof value.path === "string";
|
|
33229
|
+
return value.kind === "external" && typeof value.url === "string" && typeof value.label === "string";
|
|
33230
|
+
}
|
|
33231
|
+
function resolveRuntimeEventDestination(event) {
|
|
33232
|
+
if (isRuntimeEventDestination(event.destination)) return event.destination;
|
|
33233
|
+
return {
|
|
33234
|
+
kind: "native",
|
|
33235
|
+
mode: event.eventDestinationMode === "system-managed" ? "system-managed" : "cms-managed",
|
|
33236
|
+
path: typeof event.path === "string" ? event.path : ""
|
|
33237
|
+
};
|
|
33238
|
+
}
|
|
33239
|
+
//#endregion
|
|
33240
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/cta.ts
|
|
33241
|
+
/**
|
|
33242
|
+
* Booking call-to-action resolution for events: where an event links to and
|
|
33243
|
+
* what the button says. Sold-out and waitlist decisions come from capacity.ts
|
|
33244
|
+
* so cards, rows and the wizard never disagree.
|
|
33245
|
+
*/
|
|
33246
|
+
function closedEventBookingLabel(reason) {
|
|
33247
|
+
switch (reason) {
|
|
33248
|
+
case "occurrence_cancelled":
|
|
33249
|
+
case "series_cancelled": return "Cancelled";
|
|
33250
|
+
case "occurrence_completed":
|
|
33251
|
+
case "series_completed": return "Completed";
|
|
33252
|
+
case "series_draft":
|
|
33253
|
+
case "series_archived":
|
|
33254
|
+
case "parent_archived":
|
|
33255
|
+
case "parent_inconsistent":
|
|
33256
|
+
case "not_public":
|
|
33257
|
+
case "already_started":
|
|
33258
|
+
case "enrollment_closed": return "Registration closed";
|
|
33259
|
+
default: return assertNever$23(reason);
|
|
33260
|
+
}
|
|
33261
|
+
}
|
|
33262
|
+
function resolveEventHref(event) {
|
|
33263
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
33264
|
+
return destination.kind === "external" ? destination.url : destination.path;
|
|
33265
|
+
}
|
|
33266
|
+
function resolveEventLinkProps(event) {
|
|
33267
|
+
const href = resolveEventHref(event);
|
|
33268
|
+
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
33269
|
+
href,
|
|
33270
|
+
target: "_blank",
|
|
33271
|
+
rel: "noopener noreferrer"
|
|
33272
|
+
} : { href };
|
|
33273
|
+
}
|
|
33274
|
+
/**
|
|
33275
|
+
* Resolve the CTA state for an event.
|
|
33276
|
+
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
33277
|
+
* and course-specific defaults consistent.
|
|
33278
|
+
*/
|
|
33279
|
+
function resolveEventCta(event, buttonText) {
|
|
33280
|
+
const isSoldOut = isEventFull(event);
|
|
33281
|
+
const destination = resolveRuntimeEventDestination(event);
|
|
33282
|
+
const isExternal = destination.kind === "external";
|
|
33283
|
+
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
33284
|
+
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
33285
|
+
const href = resolveEventHref(event);
|
|
33286
|
+
if (event.bookingAvailability.kind === "closed") {
|
|
33287
|
+
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
33288
|
+
return {
|
|
33289
|
+
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
33290
|
+
href,
|
|
33291
|
+
external: isExternal,
|
|
33292
|
+
hidden: !linksToClosedWholeSeriesDetails
|
|
33293
|
+
};
|
|
33294
|
+
}
|
|
33295
|
+
if (!isExternal && isSoldOut) {
|
|
33296
|
+
if (canJoinEventWaitlistOnAnyMode(event)) return {
|
|
33297
|
+
label: "Join waitlist",
|
|
33298
|
+
href,
|
|
33299
|
+
external: false,
|
|
33300
|
+
hidden: false
|
|
33301
|
+
};
|
|
33302
|
+
return {
|
|
33303
|
+
label: "Sold out",
|
|
33304
|
+
href,
|
|
33305
|
+
external: false,
|
|
33306
|
+
hidden: true
|
|
33307
|
+
};
|
|
33308
|
+
}
|
|
33309
|
+
return {
|
|
33310
|
+
label: effectiveButtonText,
|
|
33311
|
+
href,
|
|
33312
|
+
external: isExternal,
|
|
33313
|
+
hidden: false
|
|
33314
|
+
};
|
|
33315
|
+
}
|
|
33316
|
+
//#endregion
|
|
33317
|
+
//#region ../calendar/src/grid.ts
|
|
33318
|
+
const DAY_NAMES = [
|
|
33319
|
+
"Sun",
|
|
33320
|
+
"Mon",
|
|
33321
|
+
"Tue",
|
|
33322
|
+
"Wed",
|
|
33323
|
+
"Thu",
|
|
33324
|
+
"Fri",
|
|
33325
|
+
"Sat"
|
|
33326
|
+
];
|
|
33327
|
+
const MONTH_NAMES = [
|
|
33328
|
+
"January",
|
|
33329
|
+
"February",
|
|
33330
|
+
"March",
|
|
33331
|
+
"April",
|
|
33332
|
+
"May",
|
|
33333
|
+
"June",
|
|
33334
|
+
"July",
|
|
33335
|
+
"August",
|
|
33336
|
+
"September",
|
|
33337
|
+
"October",
|
|
33338
|
+
"November",
|
|
33339
|
+
"December"
|
|
33340
|
+
];
|
|
33341
|
+
function formatDateKey(date) {
|
|
33342
|
+
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
33343
|
+
}
|
|
33344
|
+
function isToday(date, today) {
|
|
33345
|
+
return isSameCalendarDay(date, today);
|
|
33346
|
+
}
|
|
33347
|
+
function isSameCalendarDay(left, right) {
|
|
33348
|
+
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
33349
|
+
}
|
|
33350
|
+
function addDays(date, days) {
|
|
33351
|
+
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
33352
|
+
return {
|
|
33353
|
+
year: result.getUTCFullYear(),
|
|
33354
|
+
month: result.getUTCMonth() + 1,
|
|
33355
|
+
day: result.getUTCDate()
|
|
33356
|
+
};
|
|
33357
|
+
}
|
|
33358
|
+
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
33359
|
+
const firstDay = {
|
|
33360
|
+
year,
|
|
33361
|
+
month,
|
|
33362
|
+
day: 1
|
|
33363
|
+
};
|
|
33364
|
+
const dayOfWeek = getDayOfWeek(firstDay);
|
|
33365
|
+
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33366
|
+
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
33367
|
+
}
|
|
33368
|
+
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
33369
|
+
const dayOfWeek = getDayOfWeek(baseDate);
|
|
33370
|
+
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33371
|
+
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
33372
|
+
}
|
|
33373
|
+
function getDayOfWeek(date) {
|
|
33374
|
+
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
33375
|
+
}
|
|
33376
|
+
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
33377
|
+
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
33378
|
+
assertWeekCalendarDays(days);
|
|
33379
|
+
return days;
|
|
33380
|
+
}
|
|
33381
|
+
function assertWeekCalendarDays(days) {
|
|
33382
|
+
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
33383
|
+
}
|
|
33384
|
+
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
33385
|
+
if (startOfWeek === "monday") {
|
|
33386
|
+
const sunday = dayNames[0] ?? "Sun";
|
|
33387
|
+
return [...dayNames.slice(1), sunday];
|
|
33388
|
+
}
|
|
33389
|
+
return [...dayNames];
|
|
33390
|
+
}
|
|
33391
|
+
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
33392
|
+
if (view === "week") {
|
|
33393
|
+
const weekStart = days[0];
|
|
33394
|
+
const weekEnd = days[6];
|
|
33395
|
+
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
33396
|
+
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
33397
|
+
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
33398
|
+
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
33399
|
+
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
33400
|
+
}
|
|
33401
|
+
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
33402
|
+
}
|
|
33403
|
+
function buildCalendarViewport(input) {
|
|
33404
|
+
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
33405
|
+
if (input.view === "week") {
|
|
33406
|
+
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
33407
|
+
return {
|
|
33408
|
+
kind: "week",
|
|
33409
|
+
anchorDate: input.anchorDate,
|
|
33410
|
+
days,
|
|
33411
|
+
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
33412
|
+
};
|
|
33413
|
+
}
|
|
33414
|
+
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
33415
|
+
return {
|
|
33416
|
+
kind: "month",
|
|
33417
|
+
anchorDate: input.anchorDate,
|
|
33418
|
+
days,
|
|
33419
|
+
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
33420
|
+
};
|
|
33421
|
+
}
|
|
33422
|
+
//#endregion
|
|
33423
|
+
//#region ../calendar/src/lane-packing.ts
|
|
33424
|
+
/**
|
|
33425
|
+
* Assign lanes to a list of intervals.
|
|
33426
|
+
*
|
|
33427
|
+
* Returns one result per input in the same order as the input. Internally
|
|
33428
|
+
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
33429
|
+
* permutes results back to input order.
|
|
33430
|
+
*/
|
|
33431
|
+
function assignLanes(intervals) {
|
|
33432
|
+
if (intervals.length === 0) return [];
|
|
33433
|
+
const indexed = intervals.map((interval, index) => ({
|
|
33434
|
+
interval,
|
|
33435
|
+
index
|
|
33436
|
+
}));
|
|
33437
|
+
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
33438
|
+
const results = new Array(intervals.length);
|
|
33439
|
+
let cluster = [];
|
|
33440
|
+
let activeLaneEnds = [];
|
|
33441
|
+
let clusterMaxEnd = -Infinity;
|
|
33442
|
+
const closeCluster = () => {
|
|
33443
|
+
if (cluster.length === 0) return;
|
|
33444
|
+
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
33445
|
+
for (const entry of cluster) results[entry.index] = {
|
|
33446
|
+
laneIndex: entry.laneIndex,
|
|
33447
|
+
laneCount
|
|
33448
|
+
};
|
|
33449
|
+
cluster = [];
|
|
33450
|
+
activeLaneEnds = [];
|
|
33451
|
+
clusterMaxEnd = -Infinity;
|
|
33452
|
+
};
|
|
33453
|
+
for (const { interval, index } of indexed) {
|
|
33454
|
+
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
33455
|
+
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
33456
|
+
activeLaneEnds[laneIndex] = interval.end;
|
|
33457
|
+
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
33458
|
+
cluster.push({
|
|
33459
|
+
index,
|
|
33460
|
+
laneIndex
|
|
33461
|
+
});
|
|
33462
|
+
}
|
|
33463
|
+
closeCluster();
|
|
33464
|
+
return results;
|
|
33465
|
+
}
|
|
33466
|
+
function firstAvailableLane(activeLaneEnds, start) {
|
|
33467
|
+
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
33468
|
+
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
33469
|
+
}
|
|
33470
|
+
//#endregion
|
|
33471
|
+
//#region ../calendar/src/timezone.ts
|
|
33472
|
+
/**
|
|
33473
|
+
* Timezone boundary helpers — internal.
|
|
33474
|
+
*
|
|
33475
|
+
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
33476
|
+
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
33477
|
+
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
33478
|
+
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
33479
|
+
* public surface.
|
|
33480
|
+
*/
|
|
33481
|
+
const MINUTES_PER_HOUR = 60;
|
|
33482
|
+
/**
|
|
33483
|
+
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
33484
|
+
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
33485
|
+
*/
|
|
33486
|
+
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
33487
|
+
const date = new Date(isoTimestamp);
|
|
33488
|
+
if (Number.isNaN(date.getTime())) return NaN;
|
|
33489
|
+
if (!isValidTimeZone(timeZone)) return NaN;
|
|
33490
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
33491
|
+
timeZone,
|
|
33492
|
+
hour: "2-digit",
|
|
33493
|
+
minute: "2-digit",
|
|
33494
|
+
hourCycle: "h23"
|
|
33495
|
+
}).formatToParts(date);
|
|
33496
|
+
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
33497
|
+
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
33498
|
+
const hour = hourPart === 24 ? 0 : hourPart;
|
|
33499
|
+
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
33500
|
+
return hour * MINUTES_PER_HOUR + minute;
|
|
33501
|
+
}
|
|
33502
|
+
/**
|
|
33503
|
+
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
33504
|
+
* Returns null if the inputs cannot be parsed.
|
|
33505
|
+
*/
|
|
33506
|
+
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
33507
|
+
const date = new Date(isoTimestamp);
|
|
33508
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
33509
|
+
if (!isValidTimeZone(timeZone)) return null;
|
|
33510
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
33511
|
+
timeZone,
|
|
33512
|
+
year: "numeric",
|
|
33513
|
+
month: "2-digit",
|
|
33514
|
+
day: "2-digit"
|
|
33515
|
+
}).formatToParts(date);
|
|
33516
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
33517
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
33518
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
33519
|
+
if (year === void 0 || year.length === 0) return null;
|
|
33520
|
+
if (month === void 0 || month.length === 0) return null;
|
|
33521
|
+
if (day === void 0 || day.length === 0) return null;
|
|
33522
|
+
return `${year}-${month}-${day}`;
|
|
33523
|
+
}
|
|
33524
|
+
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
33525
|
+
function isValidTimeZone(timeZone) {
|
|
33526
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
33527
|
+
const cached = validTimeZoneCache.get(timeZone);
|
|
33528
|
+
if (cached !== void 0) return cached;
|
|
33529
|
+
try {
|
|
33530
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
33531
|
+
validTimeZoneCache.set(timeZone, true);
|
|
33532
|
+
return true;
|
|
33533
|
+
} catch {
|
|
33534
|
+
validTimeZoneCache.set(timeZone, false);
|
|
33535
|
+
return false;
|
|
33536
|
+
}
|
|
33537
|
+
}
|
|
33538
|
+
//#endregion
|
|
33539
|
+
//#region ../calendar/src/timetable.ts
|
|
33540
|
+
/**
|
|
33541
|
+
* Timetable model — the package's core surface.
|
|
33542
|
+
*
|
|
33543
|
+
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
33544
|
+
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
33545
|
+
* plus a diagnostics list for items dropped during processing.
|
|
33546
|
+
*
|
|
33547
|
+
* The function is pure: given identical input (including `now`), it returns
|
|
33548
|
+
* the same output. The model has no opinion on what an item is — pass
|
|
33549
|
+
* accessors.
|
|
33550
|
+
*/
|
|
33551
|
+
const DEFAULT_BOUNDS = {
|
|
33552
|
+
minStartHour: 5,
|
|
33553
|
+
maxEndHour: 23,
|
|
33554
|
+
defaultStartHour: 6,
|
|
33555
|
+
defaultEndHour: 21,
|
|
33556
|
+
minHeightPercent: 2.5
|
|
33557
|
+
};
|
|
33558
|
+
function buildTimetableModel$1(input) {
|
|
33559
|
+
const bounds = resolveBounds(input.bounds);
|
|
33560
|
+
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
33561
|
+
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
33562
|
+
const parsed = parseAllItems(input.items, input);
|
|
33563
|
+
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
33564
|
+
const hourRange = resolveHourRange(inViewport, bounds);
|
|
33565
|
+
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
33566
|
+
return {
|
|
33567
|
+
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
33568
|
+
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
33569
|
+
startHour: hourRange.startHour,
|
|
33570
|
+
endHour: hourRange.endHour,
|
|
33571
|
+
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
33572
|
+
hasItems: inViewport.length > 0,
|
|
33573
|
+
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
33574
|
+
};
|
|
33575
|
+
}
|
|
33576
|
+
function parseAllItems(items, accessors) {
|
|
33577
|
+
const drafts = [];
|
|
33578
|
+
const diagnostics = [];
|
|
33579
|
+
for (const item of items) {
|
|
33580
|
+
const result = parseDraft(item, accessors);
|
|
33581
|
+
if (result.ok) drafts.push(result.draft);
|
|
33582
|
+
else diagnostics.push({
|
|
33583
|
+
item,
|
|
33584
|
+
reason: result.reason
|
|
33585
|
+
});
|
|
33586
|
+
}
|
|
33587
|
+
return {
|
|
33588
|
+
drafts,
|
|
33589
|
+
diagnostics
|
|
33590
|
+
};
|
|
33591
|
+
}
|
|
33592
|
+
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
33593
|
+
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
33594
|
+
const rangeStartMinutes = hourRange.startHour * 60;
|
|
33595
|
+
const rangeEndMinutes = hourRange.endHour * 60;
|
|
33596
|
+
const segmentsByDate = groupSegmentsByDate(segments);
|
|
33597
|
+
const cellsByDate = /* @__PURE__ */ new Map();
|
|
33598
|
+
const diagnostics = [];
|
|
33599
|
+
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
33600
|
+
const clipped = [];
|
|
33601
|
+
for (const segment of daySegments) {
|
|
33602
|
+
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
33603
|
+
if (result.ok) clipped.push(result.clipped);
|
|
33604
|
+
else diagnostics.push({
|
|
33605
|
+
item: segment.draft.item,
|
|
33606
|
+
reason: result.reason
|
|
33607
|
+
});
|
|
33608
|
+
}
|
|
33609
|
+
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
33610
|
+
start: entry.visibleStart,
|
|
33611
|
+
end: entry.visibleEnd,
|
|
33612
|
+
orderKey: entry.segment.draft.orderKey
|
|
33613
|
+
})));
|
|
33614
|
+
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
33615
|
+
laneIndex: 0,
|
|
33616
|
+
laneCount: 1
|
|
33617
|
+
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
33618
|
+
}
|
|
33619
|
+
return {
|
|
33620
|
+
cellsByDate,
|
|
33621
|
+
diagnostics
|
|
33622
|
+
};
|
|
33623
|
+
}
|
|
33624
|
+
function assembleDays(viewportDays, cellsByDate, today) {
|
|
33625
|
+
return viewportDays.map((date) => {
|
|
33626
|
+
const dateKey = formatDateKey(date);
|
|
33627
|
+
return {
|
|
33628
|
+
date,
|
|
33629
|
+
dateKey,
|
|
33630
|
+
isToday: isSameCalendarDay(date, today),
|
|
33631
|
+
cells: cellsByDate.get(dateKey) ?? []
|
|
33632
|
+
};
|
|
33633
|
+
});
|
|
33634
|
+
}
|
|
33635
|
+
function parseDraft(item, accessors) {
|
|
33636
|
+
const startsAtIso = accessors.getStartsAt(item);
|
|
33637
|
+
const endsAtIso = accessors.getEndsAt(item);
|
|
33638
|
+
const timeZone = accessors.getTimeZone(item);
|
|
33639
|
+
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
33640
|
+
if (Number.isNaN(startsAtMinutes)) return {
|
|
33641
|
+
ok: false,
|
|
33642
|
+
reason: "invalid-start"
|
|
33643
|
+
};
|
|
33644
|
+
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
33645
|
+
if (startDateKey == null) return {
|
|
33646
|
+
ok: false,
|
|
33647
|
+
reason: "invalid-timezone"
|
|
33648
|
+
};
|
|
33649
|
+
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
33650
|
+
if (endDateKey == null) return {
|
|
33651
|
+
ok: false,
|
|
33652
|
+
reason: "invalid-end"
|
|
33653
|
+
};
|
|
33654
|
+
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
33655
|
+
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
33656
|
+
ok: false,
|
|
33657
|
+
reason: "invalid-end"
|
|
33658
|
+
};
|
|
33659
|
+
const spansMultipleDays = startDateKey < endDateKey;
|
|
33660
|
+
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
33661
|
+
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
33662
|
+
ok: false,
|
|
33663
|
+
reason: "non-positive-duration"
|
|
33664
|
+
};
|
|
33665
|
+
return {
|
|
33666
|
+
ok: true,
|
|
33667
|
+
draft: {
|
|
33668
|
+
item,
|
|
33669
|
+
startDateKey,
|
|
33670
|
+
endDateKey,
|
|
33671
|
+
startsAtMinutes,
|
|
33672
|
+
endsAtMinutes,
|
|
33673
|
+
color: accessors.getColor?.(item) ?? null,
|
|
33674
|
+
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
33675
|
+
}
|
|
33676
|
+
};
|
|
33677
|
+
}
|
|
33678
|
+
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
33679
|
+
const segments = [];
|
|
33680
|
+
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
33681
|
+
if (!viewportKeys.has(dateKey)) continue;
|
|
33682
|
+
const isStartDay = dateKey === draft.startDateKey;
|
|
33683
|
+
const isEndDay = dateKey === draft.endDateKey;
|
|
33684
|
+
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
33685
|
+
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
33686
|
+
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
33687
|
+
segments.push({
|
|
33688
|
+
draft,
|
|
33689
|
+
dateKey,
|
|
33690
|
+
startsAtMinutes,
|
|
33691
|
+
endsAtMinutes,
|
|
33692
|
+
continuesBefore: !isStartDay,
|
|
33693
|
+
continuesAfter: !isEndDay
|
|
33694
|
+
});
|
|
33695
|
+
}
|
|
33696
|
+
return segments;
|
|
33697
|
+
}
|
|
33698
|
+
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
33699
|
+
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
33700
|
+
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
33701
|
+
if (visibleEnd <= visibleStart) return {
|
|
33702
|
+
ok: false,
|
|
33703
|
+
reason: "out-of-visible-hours"
|
|
33704
|
+
};
|
|
33705
|
+
return {
|
|
33706
|
+
ok: true,
|
|
33707
|
+
clipped: {
|
|
33708
|
+
segment,
|
|
33709
|
+
visibleStart,
|
|
33710
|
+
visibleEnd
|
|
33711
|
+
}
|
|
33712
|
+
};
|
|
33713
|
+
}
|
|
33714
|
+
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
33715
|
+
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
33716
|
+
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
33717
|
+
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
33718
|
+
return {
|
|
33719
|
+
item: entry.segment.draft.item,
|
|
33720
|
+
dateKey: entry.segment.dateKey,
|
|
33721
|
+
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
33722
|
+
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
33723
|
+
visibleStartsAtMinutes: entry.visibleStart,
|
|
33724
|
+
visibleEndsAtMinutes: entry.visibleEnd,
|
|
33725
|
+
topPercent,
|
|
33726
|
+
heightPercent,
|
|
33727
|
+
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
33728
|
+
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
33729
|
+
laneIndex: lane.laneIndex,
|
|
33730
|
+
laneCount: lane.laneCount,
|
|
33731
|
+
color: entry.segment.draft.color
|
|
33732
|
+
};
|
|
33733
|
+
}
|
|
33734
|
+
function resolveBounds(bounds) {
|
|
33735
|
+
return {
|
|
33736
|
+
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
33737
|
+
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
33738
|
+
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
33739
|
+
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
33740
|
+
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
33741
|
+
};
|
|
33742
|
+
}
|
|
33743
|
+
function resolveHourRange(segments, bounds) {
|
|
33744
|
+
if (segments.length === 0) return {
|
|
33745
|
+
startHour: bounds.defaultStartHour,
|
|
33746
|
+
endHour: bounds.defaultEndHour
|
|
33747
|
+
};
|
|
33748
|
+
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
33749
|
+
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
33750
|
+
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
33751
|
+
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
33752
|
+
return {
|
|
33753
|
+
startHour,
|
|
33754
|
+
endHour: Math.max(startHour + 1, endHour)
|
|
33755
|
+
};
|
|
33756
|
+
}
|
|
33757
|
+
function buildHourTicks(startHour, endHour) {
|
|
33758
|
+
const ticks = [];
|
|
33759
|
+
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
33760
|
+
return ticks;
|
|
33761
|
+
}
|
|
33762
|
+
function groupSegmentsByDate(segments) {
|
|
33763
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
33764
|
+
for (const segment of segments) {
|
|
33765
|
+
const existing = grouped.get(segment.dateKey) ?? [];
|
|
33766
|
+
existing.push(segment);
|
|
33767
|
+
grouped.set(segment.dateKey, existing);
|
|
33768
|
+
}
|
|
33769
|
+
return grouped;
|
|
33770
|
+
}
|
|
33771
|
+
function toRangePercent(minutes, rangeMinutes) {
|
|
33772
|
+
if (rangeMinutes <= 0) return 0;
|
|
33773
|
+
return minutes / rangeMinutes * 100;
|
|
33774
|
+
}
|
|
33775
|
+
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
33776
|
+
if (view === "day") return [viewDate];
|
|
33777
|
+
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
33778
|
+
}
|
|
33779
|
+
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
33780
|
+
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
33781
|
+
const start = parseDateKey(startDateKey);
|
|
33782
|
+
parseDateKey(endDateKey);
|
|
33783
|
+
const keys = [];
|
|
33784
|
+
let cursor = start;
|
|
33785
|
+
while (formatDateKey(cursor) <= endDateKey) {
|
|
33786
|
+
keys.push(formatDateKey(cursor));
|
|
33787
|
+
cursor = addDays(cursor, 1);
|
|
33788
|
+
}
|
|
33789
|
+
return keys;
|
|
33790
|
+
}
|
|
33791
|
+
function parseDateKey(dateKey) {
|
|
33792
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
33793
|
+
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
33794
|
+
const [, year, month, day] = match;
|
|
33795
|
+
return {
|
|
33796
|
+
year: Number(year),
|
|
33797
|
+
month: Number(month),
|
|
33798
|
+
day: Number(day)
|
|
33799
|
+
};
|
|
33800
|
+
}
|
|
33801
|
+
//#endregion
|
|
33802
|
+
//#region ../calendar/src/group.ts
|
|
33803
|
+
/**
|
|
33804
|
+
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
33805
|
+
*
|
|
33806
|
+
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
33807
|
+
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
33808
|
+
*
|
|
33809
|
+
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
33810
|
+
* relative order of items within each date matches input order.
|
|
33811
|
+
*/
|
|
33812
|
+
function groupItemsByDate(items, accessors) {
|
|
33813
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
33814
|
+
for (const item of items) {
|
|
33815
|
+
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
33816
|
+
if (dateKey == null) continue;
|
|
33817
|
+
const existing = grouped.get(dateKey);
|
|
33818
|
+
if (existing !== void 0) existing.push(item);
|
|
33819
|
+
else grouped.set(dateKey, [item]);
|
|
33820
|
+
}
|
|
33821
|
+
return grouped;
|
|
33822
|
+
}
|
|
33823
|
+
//#endregion
|
|
33824
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/dates.ts
|
|
33825
|
+
/**
|
|
33826
|
+
* Date and time presentation for events: the single place an event's
|
|
33827
|
+
* timestamps become strings or date buckets. Calendar-grid primitives are
|
|
33828
|
+
* re-exported from @riverbankcms/calendar so block code has one import.
|
|
33829
|
+
*/
|
|
33830
|
+
/**
|
|
33831
|
+
* Format date for display.
|
|
33832
|
+
* Returns: "Monday, January 15, 2025"
|
|
33833
|
+
*/
|
|
33834
|
+
function formatDate$2(isoTimestamp, options) {
|
|
33835
|
+
return formatBookingDate(toScheduledBookingDateTime({
|
|
33836
|
+
startsAt: isoTimestamp,
|
|
33837
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33838
|
+
locale: options?.locale ?? "en-US"
|
|
33839
|
+
}), {
|
|
33840
|
+
weekday: "long",
|
|
33841
|
+
year: "numeric",
|
|
33842
|
+
month: "long",
|
|
33843
|
+
day: "numeric"
|
|
33844
|
+
});
|
|
33845
|
+
}
|
|
33846
|
+
/**
|
|
33847
|
+
* Format time for display.
|
|
33848
|
+
* Returns: "10:00 AM"
|
|
33849
|
+
*/
|
|
33850
|
+
function formatTime(isoTimestamp, options) {
|
|
33851
|
+
return formatBookingTime(toScheduledBookingDateTime({
|
|
33852
|
+
startsAt: isoTimestamp,
|
|
33853
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33854
|
+
locale: options?.locale ?? "en-US"
|
|
33855
|
+
}), {
|
|
33856
|
+
hour: "numeric",
|
|
33857
|
+
minute: "2-digit",
|
|
33858
|
+
hour12: true
|
|
33859
|
+
});
|
|
33860
|
+
}
|
|
33861
|
+
function formatEventDateParts(args) {
|
|
33862
|
+
const scheduledRange = toScheduledBookingDateRange({
|
|
33863
|
+
startsAt: args.startsAt,
|
|
33864
|
+
endsAt: args.endsAt,
|
|
33865
|
+
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
33866
|
+
locale: args.locale ?? "en-US"
|
|
33867
|
+
});
|
|
33868
|
+
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
33869
|
+
weekday: "long",
|
|
33870
|
+
year: "numeric",
|
|
33871
|
+
month: "long",
|
|
33872
|
+
day: "numeric"
|
|
33873
|
+
} });
|
|
33874
|
+
return {
|
|
33875
|
+
date,
|
|
33876
|
+
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
33877
|
+
startsAt: args.endsAt,
|
|
33878
|
+
timeZone: scheduledRange.timeZone,
|
|
33879
|
+
locale: args.locale ?? "en-US"
|
|
33880
|
+
}))}`
|
|
33881
|
+
};
|
|
33882
|
+
}
|
|
33883
|
+
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
33884
|
+
const dateTime = toScheduledBookingDateTime({
|
|
33885
|
+
startsAt: isoTimestamp,
|
|
33886
|
+
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
33887
|
+
locale: options?.locale ?? "en-US"
|
|
33888
|
+
});
|
|
33889
|
+
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33890
|
+
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
33891
|
+
return {
|
|
33892
|
+
weekday,
|
|
33893
|
+
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
33894
|
+
month,
|
|
33895
|
+
label: formatBookingDate(dateTime, {
|
|
33896
|
+
weekday: "long",
|
|
33897
|
+
year: "numeric",
|
|
33898
|
+
month: "long",
|
|
33899
|
+
day: "numeric"
|
|
33900
|
+
})
|
|
33901
|
+
};
|
|
33902
|
+
}
|
|
33903
|
+
/**
|
|
33904
|
+
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
33905
|
+
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
33906
|
+
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
33907
|
+
*/
|
|
33908
|
+
function groupEventsByDate(events, calendarTimeZone) {
|
|
33909
|
+
const grouped = groupItemsByDate(events, {
|
|
33910
|
+
getStartsAt: (event) => event.startsAt,
|
|
33911
|
+
getTimeZone: () => calendarTimeZone
|
|
33912
|
+
});
|
|
33913
|
+
const map = /* @__PURE__ */ new Map();
|
|
33914
|
+
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
33915
|
+
return map;
|
|
33916
|
+
}
|
|
33917
|
+
//#endregion
|
|
33918
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/people.ts
|
|
33919
|
+
function formatTeamMemberNames(names) {
|
|
33920
|
+
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
33921
|
+
if (normalized.length === 0) return null;
|
|
33922
|
+
const [firstName, secondName] = normalized;
|
|
33923
|
+
if (!firstName) return null;
|
|
33924
|
+
if (normalized.length === 1) return firstName;
|
|
33925
|
+
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
33926
|
+
const finalName = normalized[normalized.length - 1] ?? "";
|
|
33927
|
+
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
33928
|
+
}
|
|
33929
|
+
function formatEventTeacherLine(event) {
|
|
33930
|
+
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
33931
|
+
return names ? `with ${names}` : null;
|
|
33932
|
+
}
|
|
33933
|
+
//#endregion
|
|
33934
|
+
//#region ../blocks/src/system/runtime/nodes/events/domain/pricing.ts
|
|
33935
|
+
const DEFAULT_EVENT_CURRENCY = "USD";
|
|
33936
|
+
function resolveRuntimeEventPricing(event) {
|
|
33937
|
+
if (isPublicEventPricing(event.pricing)) return event.pricing;
|
|
33938
|
+
if (resolveRuntimeEventDestination(event).kind === "external") return {
|
|
33939
|
+
kind: "external",
|
|
33940
|
+
display: {
|
|
33941
|
+
kind: "external",
|
|
33942
|
+
label: "External registration"
|
|
33943
|
+
}
|
|
33944
|
+
};
|
|
33945
|
+
const legacy = event;
|
|
33946
|
+
const currency = readCurrency(legacy.currency);
|
|
33947
|
+
if (event.requiresRegistration === false) return {
|
|
33948
|
+
kind: "drop_in",
|
|
33949
|
+
currency,
|
|
33950
|
+
display: {
|
|
33951
|
+
kind: "free",
|
|
33952
|
+
currency
|
|
33953
|
+
}
|
|
33954
|
+
};
|
|
33955
|
+
if (legacy.pricingMode === "ticket_types") return {
|
|
33956
|
+
kind: "ticket_types",
|
|
33957
|
+
currency,
|
|
33958
|
+
ticketTypes: Array.isArray(legacy.ticketTypes) ? legacy.ticketTypes.filter(isPublicEventTicketType) : [],
|
|
33959
|
+
display: readTicketTypePriceDisplay(legacy.priceDisplay, currency)
|
|
33960
|
+
};
|
|
33961
|
+
const amount = typeof legacy.price === "number" ? legacy.price : 0;
|
|
33962
|
+
if (legacy.pricingType === "paid" && amount > 0) return {
|
|
33963
|
+
kind: "single_paid",
|
|
33964
|
+
amount,
|
|
33965
|
+
currency,
|
|
33966
|
+
display: {
|
|
33967
|
+
kind: "exact",
|
|
33968
|
+
amount,
|
|
33969
|
+
currency
|
|
33970
|
+
}
|
|
33971
|
+
};
|
|
33972
|
+
return {
|
|
33973
|
+
kind: "free_registration",
|
|
33974
|
+
currency,
|
|
33975
|
+
display: {
|
|
33976
|
+
kind: "free",
|
|
33977
|
+
currency
|
|
33978
|
+
}
|
|
33979
|
+
};
|
|
33980
|
+
}
|
|
33981
|
+
function readCurrency(value) {
|
|
33982
|
+
return typeof value === "string" && value.trim().length > 0 ? value : DEFAULT_EVENT_CURRENCY;
|
|
33983
|
+
}
|
|
33984
|
+
function isRecord$23(value) {
|
|
33985
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33986
|
+
}
|
|
33987
|
+
function isPublicEventPricing(value) {
|
|
33988
|
+
if (!isRecord$23(value) || typeof value.kind !== "string") return false;
|
|
33989
|
+
return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "sliding_scale" || value.kind === "ticket_types" || value.kind === "whole_series" || value.kind === "external";
|
|
33990
|
+
}
|
|
33991
|
+
function isPublicEventTicketType(value) {
|
|
33992
|
+
return isRecord$23(value) && typeof value.id === "string";
|
|
33993
|
+
}
|
|
33994
|
+
function readTicketTypePriceDisplay(value, currency) {
|
|
33995
|
+
const display = readLegacyPriceDisplay(value, currency);
|
|
33996
|
+
return display.kind === "whole_series" || display.kind === "external" ? {
|
|
33997
|
+
kind: "unavailable",
|
|
33998
|
+
currency
|
|
33999
|
+
} : display;
|
|
34000
|
+
}
|
|
34001
|
+
function readLegacyPriceDisplay(value, currency) {
|
|
34002
|
+
if (!isRecord$23(value) || typeof value.kind !== "string") return {
|
|
34003
|
+
kind: "unavailable",
|
|
34004
|
+
currency
|
|
34005
|
+
};
|
|
34006
|
+
const displayCurrency = readCurrency(value.currency);
|
|
34007
|
+
switch (value.kind) {
|
|
34008
|
+
case "free": return {
|
|
34009
|
+
kind: "free",
|
|
34010
|
+
currency: displayCurrency
|
|
34011
|
+
};
|
|
34012
|
+
case "exact": return readAmount(value.amount) !== null ? {
|
|
34013
|
+
kind: "exact",
|
|
34014
|
+
amount: readAmount(value.amount) ?? 0,
|
|
34015
|
+
currency: displayCurrency
|
|
34016
|
+
} : {
|
|
34017
|
+
kind: "unavailable",
|
|
34018
|
+
currency
|
|
34019
|
+
};
|
|
34020
|
+
case "from": return readAmount(value.amount) !== null ? {
|
|
34021
|
+
kind: "from",
|
|
34022
|
+
amount: readAmount(value.amount) ?? 0,
|
|
34023
|
+
currency: displayCurrency
|
|
34024
|
+
} : {
|
|
34025
|
+
kind: "unavailable",
|
|
34026
|
+
currency
|
|
34027
|
+
};
|
|
34028
|
+
case "range": {
|
|
34029
|
+
const minAmount = readAmount(value.minAmount);
|
|
34030
|
+
const maxAmount = readAmount(value.maxAmount);
|
|
34031
|
+
return minAmount !== null && maxAmount !== null ? {
|
|
34032
|
+
kind: "range",
|
|
34033
|
+
minAmount,
|
|
34034
|
+
maxAmount,
|
|
34035
|
+
currency: displayCurrency
|
|
34036
|
+
} : {
|
|
34037
|
+
kind: "unavailable",
|
|
34038
|
+
currency
|
|
34039
|
+
};
|
|
34040
|
+
}
|
|
34041
|
+
case "mixed": return readAmount(value.paidFromAmount) !== null ? {
|
|
34042
|
+
kind: "mixed",
|
|
34043
|
+
paidFromAmount: readAmount(value.paidFromAmount) ?? 0,
|
|
34044
|
+
paidToAmount: readAmount(value.paidToAmount) ?? readAmount(value.paidFromAmount) ?? 0,
|
|
34045
|
+
currency: displayCurrency
|
|
34046
|
+
} : {
|
|
34047
|
+
kind: "unavailable",
|
|
34048
|
+
currency
|
|
34049
|
+
};
|
|
34050
|
+
case "unavailable": return {
|
|
34051
|
+
kind: "unavailable",
|
|
34052
|
+
currency: displayCurrency
|
|
34053
|
+
};
|
|
34054
|
+
case "whole_series": return isRecord$23(value.display) ? {
|
|
34055
|
+
kind: "whole_series",
|
|
34056
|
+
display: readCoursePriceDisplay(value.display, displayCurrency)
|
|
34057
|
+
} : {
|
|
34058
|
+
kind: "whole_series",
|
|
34059
|
+
display: {
|
|
34060
|
+
kind: "unavailable",
|
|
34061
|
+
currency: displayCurrency
|
|
34062
|
+
}
|
|
34063
|
+
};
|
|
34064
|
+
default: return {
|
|
34065
|
+
kind: "unavailable",
|
|
34066
|
+
currency
|
|
34067
|
+
};
|
|
34068
|
+
}
|
|
34069
|
+
}
|
|
34070
|
+
function readCoursePriceDisplay(value, currency) {
|
|
34071
|
+
switch (value.kind) {
|
|
34072
|
+
case "free": return {
|
|
34073
|
+
kind: "free",
|
|
34074
|
+
currency: readCurrency(value.currency)
|
|
34075
|
+
};
|
|
34076
|
+
case "exact": return readAmount(value.amount) !== null ? {
|
|
34077
|
+
kind: "exact",
|
|
34078
|
+
amount: readAmount(value.amount) ?? 0,
|
|
34079
|
+
currency: readCurrency(value.currency)
|
|
34080
|
+
} : {
|
|
34081
|
+
kind: "unavailable",
|
|
34082
|
+
currency
|
|
34083
|
+
};
|
|
34084
|
+
case "from": return readAmount(value.amount) !== null ? {
|
|
34085
|
+
kind: "from",
|
|
34086
|
+
amount: readAmount(value.amount) ?? 0,
|
|
34087
|
+
currency: readCurrency(value.currency)
|
|
34088
|
+
} : {
|
|
34089
|
+
kind: "unavailable",
|
|
34090
|
+
currency
|
|
34091
|
+
};
|
|
34092
|
+
default: return {
|
|
34093
|
+
kind: "unavailable",
|
|
34094
|
+
currency
|
|
34095
|
+
};
|
|
34096
|
+
}
|
|
34097
|
+
}
|
|
34098
|
+
function readAmount(value) {
|
|
34099
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
34100
|
+
}
|
|
34101
|
+
/**
|
|
34102
|
+
* Format event price for display.
|
|
34103
|
+
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
34104
|
+
*
|
|
34105
|
+
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
34106
|
+
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
34107
|
+
* surfaces stay consistent.
|
|
34108
|
+
*/
|
|
34109
|
+
function formatEventPrice(event) {
|
|
34110
|
+
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
34111
|
+
}
|
|
34112
|
+
const eventCalendarDisplayModeValues = [
|
|
34113
|
+
"combined",
|
|
34114
|
+
"calendar",
|
|
34115
|
+
"list",
|
|
34116
|
+
"timetable",
|
|
34117
|
+
"week-board"
|
|
34118
|
+
];
|
|
34119
|
+
const eventCalendarDefaultViewValues = ["month", "week"];
|
|
34120
|
+
const eventCalendarListLayoutValues = [
|
|
34121
|
+
"stack",
|
|
34122
|
+
"grid",
|
|
34123
|
+
"compact"
|
|
34124
|
+
];
|
|
34125
|
+
const eventCalendarListLayoutOptions = [
|
|
34126
|
+
{
|
|
34127
|
+
id: "stack",
|
|
34128
|
+
label: "Stack"
|
|
34129
|
+
},
|
|
34130
|
+
{
|
|
34131
|
+
id: "grid",
|
|
34132
|
+
label: "Grid"
|
|
34133
|
+
},
|
|
34134
|
+
{
|
|
34135
|
+
id: "compact",
|
|
34136
|
+
label: "Compact"
|
|
34137
|
+
}
|
|
34138
|
+
];
|
|
34139
|
+
const eventCalendarListLayoutCompatibleDisplayModeValues = ["combined", "list"];
|
|
34140
|
+
function isEventCalendarDisplayMode(value) {
|
|
34141
|
+
return typeof value === "string" && eventCalendarDisplayModeValues.includes(value);
|
|
34142
|
+
}
|
|
34143
|
+
function isEventCalendarDefaultView(value) {
|
|
34144
|
+
return typeof value === "string" && eventCalendarDefaultViewValues.includes(value);
|
|
34145
|
+
}
|
|
34146
|
+
function isEventCalendarListLayout(value) {
|
|
34147
|
+
return typeof value === "string" && eventCalendarListLayoutValues.includes(value);
|
|
34148
|
+
}
|
|
34149
|
+
//#endregion
|
|
34150
|
+
//#region ../blocks/src/system/runtime/nodes/shared/contract-schemas.ts
|
|
34151
|
+
const previewStageSchema = z$1.enum(["preview", "published"]);
|
|
34152
|
+
const registrationButtonVariantSchema = blockButtonRoleSchema;
|
|
34153
|
+
const eventButtonVariantSchema = blockButtonRoleSchema;
|
|
34154
|
+
const eventLayoutSchema = z$1.enum(eventCalendarListLayoutValues);
|
|
34155
|
+
const eventCalendarDisplayModeSchema = z$1.enum(eventCalendarDisplayModeValues);
|
|
34156
|
+
const eventCalendarDefaultViewSchema = z$1.enum(eventCalendarDefaultViewValues);
|
|
34157
|
+
const columnOptionSchema = z$1.enum([
|
|
34158
|
+
"2",
|
|
34159
|
+
"3",
|
|
34160
|
+
"4"
|
|
34161
|
+
]);
|
|
34162
|
+
const weekStartSchema = z$1.enum(["sunday", "monday"]);
|
|
34163
|
+
const filterAutoShowSchema = z$1.enum([
|
|
34164
|
+
"auto",
|
|
34165
|
+
"always",
|
|
34166
|
+
"never"
|
|
34167
|
+
]);
|
|
34168
|
+
const calendarTabViewsSchema = z$1.enum([
|
|
34169
|
+
"both",
|
|
34170
|
+
"month",
|
|
34171
|
+
"week"
|
|
34172
|
+
]);
|
|
34173
|
+
/**
|
|
34174
|
+
* Determine which wizard steps to show based on context and payment requirements.
|
|
34175
|
+
*
|
|
34176
|
+
* @param hasPreSelectedContext - Whether event is pre-selected (skips event selection)
|
|
34177
|
+
* @param needsPaymentStep - Whether user must complete payment method selection
|
|
34178
|
+
* @returns Array of wizard steps to display
|
|
34179
|
+
*/
|
|
34180
|
+
function getWizardStepsForContext(hasPreSelectedContext, needsPaymentStep) {
|
|
34181
|
+
const baseSteps = hasPreSelectedContext ? ["select-tickets", "enter-details"] : [
|
|
34182
|
+
"select-event",
|
|
34183
|
+
"select-tickets",
|
|
34184
|
+
"enter-details"
|
|
34185
|
+
];
|
|
34186
|
+
if (!needsPaymentStep) return baseSteps;
|
|
34187
|
+
return [...baseSteps, "select-payment"];
|
|
34188
|
+
}
|
|
34189
|
+
//#endregion
|
|
34190
|
+
//#region ../blocks/src/system/runtime/nodes/event-registration/registrationDerivations.ts
|
|
34191
|
+
function eventMatchesOccurrenceId(event, occurrenceId) {
|
|
34192
|
+
return event.registrationSubject.kind === "occurrence" && (event.registrationSubject.occurrenceId === occurrenceId || event.selectionId === occurrenceId);
|
|
34193
|
+
}
|
|
34194
|
+
function findPreSelectedEvent(input) {
|
|
34195
|
+
const occurrenceContext = input.occurrenceContext;
|
|
34196
|
+
if (!occurrenceContext) return input.scopedEvents.length === 1 ? input.scopedEvents[0] ?? null : null;
|
|
34197
|
+
const contextDate = getBookingLocalDateKey(toScheduledBookingDateTime({
|
|
34198
|
+
startsAt: occurrenceContext.startsAt,
|
|
34199
|
+
timeZone: occurrenceContext.timeZone
|
|
34200
|
+
}));
|
|
34201
|
+
return input.scopedEvents.find((event) => eventMatchesOccurrenceId(event, occurrenceContext.id) || getBookingLocalDateKey(toScheduledBookingDateTime({
|
|
34202
|
+
startsAt: event.startsAt,
|
|
34203
|
+
timeZone: event.timeZone
|
|
34204
|
+
})) === contextDate) ?? null;
|
|
34205
|
+
}
|
|
34206
|
+
function resolveEventRequiresPayment(input) {
|
|
34207
|
+
if (input.eligibility) return input.eligibility.requiresPayment;
|
|
34208
|
+
if (input.selectedTicketType) return input.selectedTicketType.priceAmount > 0;
|
|
34209
|
+
if (!input.event) return false;
|
|
34210
|
+
return publicEventPricingType(resolveRuntimeEventPricing(input.event)) === "paid";
|
|
34211
|
+
}
|
|
34212
|
+
//#endregion
|
|
34213
|
+
//#region ../blocks/src/system/runtime/nodes/event-registration/registrationEventAdapter.ts
|
|
34214
|
+
function toRegistrationEvent(event) {
|
|
34215
|
+
return {
|
|
34216
|
+
...event,
|
|
34217
|
+
pricing: resolveRuntimeEventPricing(event),
|
|
34218
|
+
selectionId: event.id,
|
|
34219
|
+
registrationSubject: {
|
|
34220
|
+
kind: "occurrence",
|
|
34221
|
+
occurrenceId: event.occurrenceId
|
|
34222
|
+
}
|
|
34223
|
+
};
|
|
32967
34224
|
}
|
|
32968
34225
|
function toRegistrationEvents(events) {
|
|
32969
34226
|
return events.filter((event) => event.bookingScope === "single_occurrence").map(toRegistrationEvent);
|
|
@@ -33376,7 +34633,7 @@ const eventRegistrationBlockDefinition = {
|
|
|
33376
34633
|
{
|
|
33377
34634
|
id: "showCapacity",
|
|
33378
34635
|
type: "boolean",
|
|
33379
|
-
label: "Show available
|
|
34636
|
+
label: "Show available places",
|
|
33380
34637
|
description: "Display remaining capacity",
|
|
33381
34638
|
required: false,
|
|
33382
34639
|
defaultValue: true,
|
|
@@ -33921,591 +35178,26 @@ const eventSpotlightBlockDefinition = {
|
|
|
33921
35178
|
dataLoaders: { events: {
|
|
33922
35179
|
endpoint: "listPublicEvents",
|
|
33923
35180
|
params: {
|
|
33924
|
-
siteId: { $bind: { from: "$root.siteId" } },
|
|
33925
|
-
limit: { $bind: {
|
|
33926
|
-
from: "content.maxEvents",
|
|
33927
|
-
fallback: "3"
|
|
33928
|
-
} },
|
|
33929
|
-
stage: { $bind: {
|
|
33930
|
-
from: "$root.previewStage",
|
|
33931
|
-
fallback: "published"
|
|
33932
|
-
} },
|
|
33933
|
-
detailLevel: "listing",
|
|
33934
|
-
readProfile: "spotlight"
|
|
33935
|
-
},
|
|
33936
|
-
mode: "server"
|
|
33937
|
-
} }
|
|
33938
|
-
};
|
|
33939
|
-
//#endregion
|
|
33940
|
-
//#region ../calendar/src/grid.ts
|
|
33941
|
-
const DAY_NAMES = [
|
|
33942
|
-
"Sun",
|
|
33943
|
-
"Mon",
|
|
33944
|
-
"Tue",
|
|
33945
|
-
"Wed",
|
|
33946
|
-
"Thu",
|
|
33947
|
-
"Fri",
|
|
33948
|
-
"Sat"
|
|
33949
|
-
];
|
|
33950
|
-
const MONTH_NAMES = [
|
|
33951
|
-
"January",
|
|
33952
|
-
"February",
|
|
33953
|
-
"March",
|
|
33954
|
-
"April",
|
|
33955
|
-
"May",
|
|
33956
|
-
"June",
|
|
33957
|
-
"July",
|
|
33958
|
-
"August",
|
|
33959
|
-
"September",
|
|
33960
|
-
"October",
|
|
33961
|
-
"November",
|
|
33962
|
-
"December"
|
|
33963
|
-
];
|
|
33964
|
-
function formatDateKey(date) {
|
|
33965
|
-
return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
|
|
33966
|
-
}
|
|
33967
|
-
function isToday(date, today) {
|
|
33968
|
-
return isSameCalendarDay(date, today);
|
|
33969
|
-
}
|
|
33970
|
-
function isSameCalendarDay(left, right) {
|
|
33971
|
-
return left.year === right.year && left.month === right.month && left.day === right.day;
|
|
33972
|
-
}
|
|
33973
|
-
function addDays(date, days) {
|
|
33974
|
-
const result = new Date(Date.UTC(date.year, date.month - 1, date.day + days, 12));
|
|
33975
|
-
return {
|
|
33976
|
-
year: result.getUTCFullYear(),
|
|
33977
|
-
month: result.getUTCMonth() + 1,
|
|
33978
|
-
day: result.getUTCDate()
|
|
33979
|
-
};
|
|
33980
|
-
}
|
|
33981
|
-
function getMonthCalendarDays(year, month, startOfWeek) {
|
|
33982
|
-
const firstDay = {
|
|
33983
|
-
year,
|
|
33984
|
-
month,
|
|
33985
|
-
day: 1
|
|
33986
|
-
};
|
|
33987
|
-
const dayOfWeek = getDayOfWeek(firstDay);
|
|
33988
|
-
const gridStart = addDays(firstDay, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33989
|
-
return Array.from({ length: 42 }, (_, index) => addDays(gridStart, index));
|
|
33990
|
-
}
|
|
33991
|
-
function getWeekCalendarDays(baseDate, startOfWeek) {
|
|
33992
|
-
const dayOfWeek = getDayOfWeek(baseDate);
|
|
33993
|
-
const weekStart = addDays(baseDate, -(startOfWeek === "monday" ? dayOfWeek === 0 ? 6 : dayOfWeek - 1 : dayOfWeek));
|
|
33994
|
-
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
|
33995
|
-
}
|
|
33996
|
-
function getDayOfWeek(date) {
|
|
33997
|
-
return new Date(Date.UTC(date.year, date.month - 1, date.day, 12)).getUTCDay();
|
|
33998
|
-
}
|
|
33999
|
-
function getWeekCalendarDayTuple(baseDate, startOfWeek) {
|
|
34000
|
-
const days = getWeekCalendarDays(baseDate, startOfWeek);
|
|
34001
|
-
assertWeekCalendarDays(days);
|
|
34002
|
-
return days;
|
|
34003
|
-
}
|
|
34004
|
-
function assertWeekCalendarDays(days) {
|
|
34005
|
-
if (days.length !== 7) throw new Error("Expected exactly 7 calendar days");
|
|
34006
|
-
}
|
|
34007
|
-
function getOrderedDayNames(startOfWeek, dayNames = DAY_NAMES) {
|
|
34008
|
-
if (startOfWeek === "monday") {
|
|
34009
|
-
const sunday = dayNames[0] ?? "Sun";
|
|
34010
|
-
return [...dayNames.slice(1), sunday];
|
|
34011
|
-
}
|
|
34012
|
-
return [...dayNames];
|
|
34013
|
-
}
|
|
34014
|
-
function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
|
|
34015
|
-
if (view === "week") {
|
|
34016
|
-
const weekStart = days[0];
|
|
34017
|
-
const weekEnd = days[6];
|
|
34018
|
-
if (weekStart === void 0 || weekEnd === void 0) return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
34019
|
-
const startMonth = monthNames[weekStart.month - 1] ?? "";
|
|
34020
|
-
const endMonth = monthNames[weekEnd.month - 1] ?? "";
|
|
34021
|
-
if (weekStart.month === weekEnd.month) return `${startMonth} ${weekStart.day} - ${weekEnd.day}, ${weekEnd.year}`;
|
|
34022
|
-
return `${startMonth} ${weekStart.day} - ${endMonth} ${weekEnd.day}, ${weekEnd.year}`;
|
|
34023
|
-
}
|
|
34024
|
-
return `${monthNames[viewDate.month - 1] ?? ""} ${viewDate.year}`;
|
|
34025
|
-
}
|
|
34026
|
-
function buildCalendarViewport(input) {
|
|
34027
|
-
const monthNames = input.monthNames ?? MONTH_NAMES;
|
|
34028
|
-
if (input.view === "week") {
|
|
34029
|
-
const days = getWeekCalendarDayTuple(input.anchorDate, input.startOfWeek);
|
|
34030
|
-
return {
|
|
34031
|
-
kind: "week",
|
|
34032
|
-
anchorDate: input.anchorDate,
|
|
34033
|
-
days,
|
|
34034
|
-
title: formatCalendarTitle(input.anchorDate, "week", days, monthNames)
|
|
34035
|
-
};
|
|
34036
|
-
}
|
|
34037
|
-
const days = getMonthCalendarDays(input.anchorDate.year, input.anchorDate.month, input.startOfWeek);
|
|
34038
|
-
return {
|
|
34039
|
-
kind: "month",
|
|
34040
|
-
anchorDate: input.anchorDate,
|
|
34041
|
-
days,
|
|
34042
|
-
title: formatCalendarTitle(input.anchorDate, "month", days, monthNames)
|
|
34043
|
-
};
|
|
34044
|
-
}
|
|
34045
|
-
//#endregion
|
|
34046
|
-
//#region ../calendar/src/lane-packing.ts
|
|
34047
|
-
/**
|
|
34048
|
-
* Assign lanes to a list of intervals.
|
|
34049
|
-
*
|
|
34050
|
-
* Returns one result per input in the same order as the input. Internally
|
|
34051
|
-
* sorts a copy by `(start, end, orderKey)` to ensure determinism, then
|
|
34052
|
-
* permutes results back to input order.
|
|
34053
|
-
*/
|
|
34054
|
-
function assignLanes(intervals) {
|
|
34055
|
-
if (intervals.length === 0) return [];
|
|
34056
|
-
const indexed = intervals.map((interval, index) => ({
|
|
34057
|
-
interval,
|
|
34058
|
-
index
|
|
34059
|
-
}));
|
|
34060
|
-
indexed.sort((left, right) => left.interval.start - right.interval.start || left.interval.end - right.interval.end || left.interval.orderKey.localeCompare(right.interval.orderKey));
|
|
34061
|
-
const results = new Array(intervals.length);
|
|
34062
|
-
let cluster = [];
|
|
34063
|
-
let activeLaneEnds = [];
|
|
34064
|
-
let clusterMaxEnd = -Infinity;
|
|
34065
|
-
const closeCluster = () => {
|
|
34066
|
-
if (cluster.length === 0) return;
|
|
34067
|
-
const laneCount = Math.max(1, cluster.reduce((max, entry) => Math.max(max, entry.laneIndex + 1), 0));
|
|
34068
|
-
for (const entry of cluster) results[entry.index] = {
|
|
34069
|
-
laneIndex: entry.laneIndex,
|
|
34070
|
-
laneCount
|
|
34071
|
-
};
|
|
34072
|
-
cluster = [];
|
|
34073
|
-
activeLaneEnds = [];
|
|
34074
|
-
clusterMaxEnd = -Infinity;
|
|
34075
|
-
};
|
|
34076
|
-
for (const { interval, index } of indexed) {
|
|
34077
|
-
if (interval.start >= clusterMaxEnd) closeCluster();
|
|
34078
|
-
const laneIndex = firstAvailableLane(activeLaneEnds, interval.start);
|
|
34079
|
-
activeLaneEnds[laneIndex] = interval.end;
|
|
34080
|
-
clusterMaxEnd = Math.max(clusterMaxEnd, interval.end);
|
|
34081
|
-
cluster.push({
|
|
34082
|
-
index,
|
|
34083
|
-
laneIndex
|
|
34084
|
-
});
|
|
34085
|
-
}
|
|
34086
|
-
closeCluster();
|
|
34087
|
-
return results;
|
|
34088
|
-
}
|
|
34089
|
-
function firstAvailableLane(activeLaneEnds, start) {
|
|
34090
|
-
const laneIndex = activeLaneEnds.findIndex((end) => end <= start);
|
|
34091
|
-
return laneIndex >= 0 ? laneIndex : activeLaneEnds.length;
|
|
34092
|
-
}
|
|
34093
|
-
//#endregion
|
|
34094
|
-
//#region ../calendar/src/timezone.ts
|
|
34095
|
-
/**
|
|
34096
|
-
* Timezone boundary helpers — internal.
|
|
34097
|
-
*
|
|
34098
|
-
* Both functions read an ISO instant in a given IANA timezone and project it
|
|
34099
|
-
* onto the wall clock of that zone. They use Intl.DateTimeFormat with
|
|
34100
|
-
* hourCycle: 'h23' to avoid 12/24-hour ambiguity. Used internally by the
|
|
34101
|
-
* timetable model and `groupItemsByDate`; not exported from the package's
|
|
34102
|
-
* public surface.
|
|
34103
|
-
*/
|
|
34104
|
-
const MINUTES_PER_HOUR = 60;
|
|
34105
|
-
/**
|
|
34106
|
-
* Local minute-of-day [0, 24*60) for an ISO instant in the given zone.
|
|
34107
|
-
* Returns NaN if the inputs cannot be parsed (caller should drop the item).
|
|
34108
|
-
*/
|
|
34109
|
-
function getLocalMinuteOfDay(isoTimestamp, timeZone) {
|
|
34110
|
-
const date = new Date(isoTimestamp);
|
|
34111
|
-
if (Number.isNaN(date.getTime())) return NaN;
|
|
34112
|
-
if (!isValidTimeZone(timeZone)) return NaN;
|
|
34113
|
-
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
34114
|
-
timeZone,
|
|
34115
|
-
hour: "2-digit",
|
|
34116
|
-
minute: "2-digit",
|
|
34117
|
-
hourCycle: "h23"
|
|
34118
|
-
}).formatToParts(date);
|
|
34119
|
-
const hourPart = Number(parts.find((part) => part.type === "hour")?.value);
|
|
34120
|
-
const minute = Number(parts.find((part) => part.type === "minute")?.value);
|
|
34121
|
-
const hour = hourPart === 24 ? 0 : hourPart;
|
|
34122
|
-
if (Number.isNaN(hour) || Number.isNaN(minute)) return NaN;
|
|
34123
|
-
return hour * MINUTES_PER_HOUR + minute;
|
|
34124
|
-
}
|
|
34125
|
-
/**
|
|
34126
|
-
* Local YYYY-MM-DD for an ISO instant in the given zone.
|
|
34127
|
-
* Returns null if the inputs cannot be parsed.
|
|
34128
|
-
*/
|
|
34129
|
-
function getLocalDateKey(isoTimestamp, timeZone) {
|
|
34130
|
-
const date = new Date(isoTimestamp);
|
|
34131
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
34132
|
-
if (!isValidTimeZone(timeZone)) return null;
|
|
34133
|
-
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
34134
|
-
timeZone,
|
|
34135
|
-
year: "numeric",
|
|
34136
|
-
month: "2-digit",
|
|
34137
|
-
day: "2-digit"
|
|
34138
|
-
}).formatToParts(date);
|
|
34139
|
-
const year = parts.find((part) => part.type === "year")?.value;
|
|
34140
|
-
const month = parts.find((part) => part.type === "month")?.value;
|
|
34141
|
-
const day = parts.find((part) => part.type === "day")?.value;
|
|
34142
|
-
if (year === void 0 || year.length === 0) return null;
|
|
34143
|
-
if (month === void 0 || month.length === 0) return null;
|
|
34144
|
-
if (day === void 0 || day.length === 0) return null;
|
|
34145
|
-
return `${year}-${month}-${day}`;
|
|
34146
|
-
}
|
|
34147
|
-
const validTimeZoneCache = /* @__PURE__ */ new Map();
|
|
34148
|
-
function isValidTimeZone(timeZone) {
|
|
34149
|
-
if (typeof timeZone !== "string" || timeZone.length === 0) return false;
|
|
34150
|
-
const cached = validTimeZoneCache.get(timeZone);
|
|
34151
|
-
if (cached !== void 0) return cached;
|
|
34152
|
-
try {
|
|
34153
|
-
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
34154
|
-
validTimeZoneCache.set(timeZone, true);
|
|
34155
|
-
return true;
|
|
34156
|
-
} catch {
|
|
34157
|
-
validTimeZoneCache.set(timeZone, false);
|
|
34158
|
-
return false;
|
|
34159
|
-
}
|
|
34160
|
-
}
|
|
34161
|
-
//#endregion
|
|
34162
|
-
//#region ../calendar/src/timetable.ts
|
|
34163
|
-
/**
|
|
34164
|
-
* Timetable model — the package's core surface.
|
|
34165
|
-
*
|
|
34166
|
-
* Generic over `T`. Takes a list of timed items + a viewport; returns a
|
|
34167
|
-
* day-by-day layout with overlap-packed lanes and percent-based positions
|
|
34168
|
-
* plus a diagnostics list for items dropped during processing.
|
|
34169
|
-
*
|
|
34170
|
-
* The function is pure: given identical input (including `now`), it returns
|
|
34171
|
-
* the same output. The model has no opinion on what an item is — pass
|
|
34172
|
-
* accessors.
|
|
34173
|
-
*/
|
|
34174
|
-
const DEFAULT_BOUNDS = {
|
|
34175
|
-
minStartHour: 5,
|
|
34176
|
-
maxEndHour: 23,
|
|
34177
|
-
defaultStartHour: 6,
|
|
34178
|
-
defaultEndHour: 21,
|
|
34179
|
-
minHeightPercent: 2.5
|
|
35181
|
+
siteId: { $bind: { from: "$root.siteId" } },
|
|
35182
|
+
limit: { $bind: {
|
|
35183
|
+
from: "content.maxEvents",
|
|
35184
|
+
fallback: "3"
|
|
35185
|
+
} },
|
|
35186
|
+
stage: { $bind: {
|
|
35187
|
+
from: "$root.previewStage",
|
|
35188
|
+
fallback: "published"
|
|
35189
|
+
} },
|
|
35190
|
+
detailLevel: "listing",
|
|
35191
|
+
readProfile: "spotlight"
|
|
35192
|
+
},
|
|
35193
|
+
mode: "server"
|
|
35194
|
+
} }
|
|
34180
35195
|
};
|
|
34181
|
-
function buildTimetableModel$1(input) {
|
|
34182
|
-
const bounds = resolveBounds(input.bounds);
|
|
34183
|
-
const viewportDays = getTimetableDays(input.viewDate, input.view ?? "week", input.startOfWeek);
|
|
34184
|
-
const viewportKeys = new Set(viewportDays.map(formatDateKey));
|
|
34185
|
-
const parsed = parseAllItems(input.items, input);
|
|
34186
|
-
const inViewport = deriveVisibleSegments(parsed.drafts, viewportKeys);
|
|
34187
|
-
const hourRange = resolveHourRange(inViewport, bounds);
|
|
34188
|
-
const laid = layoutCellsByDate(inViewport, hourRange, bounds);
|
|
34189
|
-
return {
|
|
34190
|
-
days: assembleDays(viewportDays, laid.cellsByDate, input.today),
|
|
34191
|
-
hourTicks: buildHourTicks(hourRange.startHour, hourRange.endHour),
|
|
34192
|
-
startHour: hourRange.startHour,
|
|
34193
|
-
endHour: hourRange.endHour,
|
|
34194
|
-
rangeMinutes: (hourRange.endHour - hourRange.startHour) * 60,
|
|
34195
|
-
hasItems: inViewport.length > 0,
|
|
34196
|
-
diagnostics: [...parsed.diagnostics, ...laid.diagnostics]
|
|
34197
|
-
};
|
|
34198
|
-
}
|
|
34199
|
-
function parseAllItems(items, accessors) {
|
|
34200
|
-
const drafts = [];
|
|
34201
|
-
const diagnostics = [];
|
|
34202
|
-
for (const item of items) {
|
|
34203
|
-
const result = parseDraft(item, accessors);
|
|
34204
|
-
if (result.ok) drafts.push(result.draft);
|
|
34205
|
-
else diagnostics.push({
|
|
34206
|
-
item,
|
|
34207
|
-
reason: result.reason
|
|
34208
|
-
});
|
|
34209
|
-
}
|
|
34210
|
-
return {
|
|
34211
|
-
drafts,
|
|
34212
|
-
diagnostics
|
|
34213
|
-
};
|
|
34214
|
-
}
|
|
34215
|
-
function layoutCellsByDate(segments, hourRange, bounds) {
|
|
34216
|
-
const rangeMinutes = (hourRange.endHour - hourRange.startHour) * 60;
|
|
34217
|
-
const rangeStartMinutes = hourRange.startHour * 60;
|
|
34218
|
-
const rangeEndMinutes = hourRange.endHour * 60;
|
|
34219
|
-
const segmentsByDate = groupSegmentsByDate(segments);
|
|
34220
|
-
const cellsByDate = /* @__PURE__ */ new Map();
|
|
34221
|
-
const diagnostics = [];
|
|
34222
|
-
for (const [dateKey, daySegments] of segmentsByDate) {
|
|
34223
|
-
const clipped = [];
|
|
34224
|
-
for (const segment of daySegments) {
|
|
34225
|
-
const result = clipDraft(segment, rangeStartMinutes, rangeEndMinutes);
|
|
34226
|
-
if (result.ok) clipped.push(result.clipped);
|
|
34227
|
-
else diagnostics.push({
|
|
34228
|
-
item: segment.draft.item,
|
|
34229
|
-
reason: result.reason
|
|
34230
|
-
});
|
|
34231
|
-
}
|
|
34232
|
-
const laneResults = assignLanes(clipped.map((entry) => ({
|
|
34233
|
-
start: entry.visibleStart,
|
|
34234
|
-
end: entry.visibleEnd,
|
|
34235
|
-
orderKey: entry.segment.draft.orderKey
|
|
34236
|
-
})));
|
|
34237
|
-
cellsByDate.set(dateKey, clipped.map((entry, idx) => buildCell(entry, laneResults[idx] ?? {
|
|
34238
|
-
laneIndex: 0,
|
|
34239
|
-
laneCount: 1
|
|
34240
|
-
}, rangeStartMinutes, rangeMinutes, bounds.minHeightPercent)));
|
|
34241
|
-
}
|
|
34242
|
-
return {
|
|
34243
|
-
cellsByDate,
|
|
34244
|
-
diagnostics
|
|
34245
|
-
};
|
|
34246
|
-
}
|
|
34247
|
-
function assembleDays(viewportDays, cellsByDate, today) {
|
|
34248
|
-
return viewportDays.map((date) => {
|
|
34249
|
-
const dateKey = formatDateKey(date);
|
|
34250
|
-
return {
|
|
34251
|
-
date,
|
|
34252
|
-
dateKey,
|
|
34253
|
-
isToday: isSameCalendarDay(date, today),
|
|
34254
|
-
cells: cellsByDate.get(dateKey) ?? []
|
|
34255
|
-
};
|
|
34256
|
-
});
|
|
34257
|
-
}
|
|
34258
|
-
function parseDraft(item, accessors) {
|
|
34259
|
-
const startsAtIso = accessors.getStartsAt(item);
|
|
34260
|
-
const endsAtIso = accessors.getEndsAt(item);
|
|
34261
|
-
const timeZone = accessors.getTimeZone(item);
|
|
34262
|
-
const startsAtMinutes = getLocalMinuteOfDay(startsAtIso, timeZone);
|
|
34263
|
-
if (Number.isNaN(startsAtMinutes)) return {
|
|
34264
|
-
ok: false,
|
|
34265
|
-
reason: "invalid-start"
|
|
34266
|
-
};
|
|
34267
|
-
const startDateKey = getLocalDateKey(startsAtIso, timeZone);
|
|
34268
|
-
if (startDateKey == null) return {
|
|
34269
|
-
ok: false,
|
|
34270
|
-
reason: "invalid-timezone"
|
|
34271
|
-
};
|
|
34272
|
-
const endDateKey = getLocalDateKey(endsAtIso, timeZone);
|
|
34273
|
-
if (endDateKey == null) return {
|
|
34274
|
-
ok: false,
|
|
34275
|
-
reason: "invalid-end"
|
|
34276
|
-
};
|
|
34277
|
-
const endsAtMinutesRaw = getLocalMinuteOfDay(endsAtIso, timeZone);
|
|
34278
|
-
if (Number.isNaN(endsAtMinutesRaw)) return {
|
|
34279
|
-
ok: false,
|
|
34280
|
-
reason: "invalid-end"
|
|
34281
|
-
};
|
|
34282
|
-
const spansMultipleDays = startDateKey < endDateKey;
|
|
34283
|
-
const endsAtMinutes = spansMultipleDays || endsAtMinutesRaw > startsAtMinutes ? endsAtMinutesRaw : Math.min(1440, startsAtMinutes + 60);
|
|
34284
|
-
if (!spansMultipleDays && endsAtMinutes <= startsAtMinutes) return {
|
|
34285
|
-
ok: false,
|
|
34286
|
-
reason: "non-positive-duration"
|
|
34287
|
-
};
|
|
34288
|
-
return {
|
|
34289
|
-
ok: true,
|
|
34290
|
-
draft: {
|
|
34291
|
-
item,
|
|
34292
|
-
startDateKey,
|
|
34293
|
-
endDateKey,
|
|
34294
|
-
startsAtMinutes,
|
|
34295
|
-
endsAtMinutes,
|
|
34296
|
-
color: accessors.getColor?.(item) ?? null,
|
|
34297
|
-
orderKey: accessors.getOrderKey?.(item) ?? accessors.getId(item)
|
|
34298
|
-
}
|
|
34299
|
-
};
|
|
34300
|
-
}
|
|
34301
|
-
function deriveVisibleSegments(drafts, viewportKeys) {
|
|
34302
|
-
const segments = [];
|
|
34303
|
-
for (const draft of drafts) for (const dateKey of enumerateDateKeys(draft.startDateKey, draft.endDateKey)) {
|
|
34304
|
-
if (!viewportKeys.has(dateKey)) continue;
|
|
34305
|
-
const isStartDay = dateKey === draft.startDateKey;
|
|
34306
|
-
const isEndDay = dateKey === draft.endDateKey;
|
|
34307
|
-
const startsAtMinutes = isStartDay ? draft.startsAtMinutes : 0;
|
|
34308
|
-
const endsAtMinutes = isEndDay ? draft.endsAtMinutes : 1440;
|
|
34309
|
-
if (endsAtMinutes <= startsAtMinutes) continue;
|
|
34310
|
-
segments.push({
|
|
34311
|
-
draft,
|
|
34312
|
-
dateKey,
|
|
34313
|
-
startsAtMinutes,
|
|
34314
|
-
endsAtMinutes,
|
|
34315
|
-
continuesBefore: !isStartDay,
|
|
34316
|
-
continuesAfter: !isEndDay
|
|
34317
|
-
});
|
|
34318
|
-
}
|
|
34319
|
-
return segments;
|
|
34320
|
-
}
|
|
34321
|
-
function clipDraft(segment, rangeStartMinutes, rangeEndMinutes) {
|
|
34322
|
-
const visibleStart = Math.max(rangeStartMinutes, segment.startsAtMinutes);
|
|
34323
|
-
const visibleEnd = Math.min(rangeEndMinutes, segment.endsAtMinutes);
|
|
34324
|
-
if (visibleEnd <= visibleStart) return {
|
|
34325
|
-
ok: false,
|
|
34326
|
-
reason: "out-of-visible-hours"
|
|
34327
|
-
};
|
|
34328
|
-
return {
|
|
34329
|
-
ok: true,
|
|
34330
|
-
clipped: {
|
|
34331
|
-
segment,
|
|
34332
|
-
visibleStart,
|
|
34333
|
-
visibleEnd
|
|
34334
|
-
}
|
|
34335
|
-
};
|
|
34336
|
-
}
|
|
34337
|
-
function buildCell(entry, lane, rangeStartMinutes, rangeMinutes, minHeightPercent) {
|
|
34338
|
-
const heightPercent = Math.max(minHeightPercent, toRangePercent(entry.visibleEnd - entry.visibleStart, rangeMinutes));
|
|
34339
|
-
const rawTopPercent = toRangePercent(entry.visibleStart - rangeStartMinutes, rangeMinutes);
|
|
34340
|
-
const topPercent = Math.min(rawTopPercent, 100 - heightPercent);
|
|
34341
|
-
return {
|
|
34342
|
-
item: entry.segment.draft.item,
|
|
34343
|
-
dateKey: entry.segment.dateKey,
|
|
34344
|
-
startsAtMinutes: entry.segment.startsAtMinutes,
|
|
34345
|
-
endsAtMinutes: entry.segment.endsAtMinutes,
|
|
34346
|
-
visibleStartsAtMinutes: entry.visibleStart,
|
|
34347
|
-
visibleEndsAtMinutes: entry.visibleEnd,
|
|
34348
|
-
topPercent,
|
|
34349
|
-
heightPercent,
|
|
34350
|
-
isClippedStart: entry.segment.continuesBefore || entry.segment.startsAtMinutes < entry.visibleStart,
|
|
34351
|
-
isClippedEnd: entry.segment.continuesAfter || entry.segment.endsAtMinutes > entry.visibleEnd,
|
|
34352
|
-
laneIndex: lane.laneIndex,
|
|
34353
|
-
laneCount: lane.laneCount,
|
|
34354
|
-
color: entry.segment.draft.color
|
|
34355
|
-
};
|
|
34356
|
-
}
|
|
34357
|
-
function resolveBounds(bounds) {
|
|
34358
|
-
return {
|
|
34359
|
-
minStartHour: bounds?.minStartHour ?? DEFAULT_BOUNDS.minStartHour,
|
|
34360
|
-
maxEndHour: bounds?.maxEndHour ?? DEFAULT_BOUNDS.maxEndHour,
|
|
34361
|
-
defaultStartHour: bounds?.defaultStartHour ?? DEFAULT_BOUNDS.defaultStartHour,
|
|
34362
|
-
defaultEndHour: bounds?.defaultEndHour ?? DEFAULT_BOUNDS.defaultEndHour,
|
|
34363
|
-
minHeightPercent: bounds?.minHeightPercent ?? DEFAULT_BOUNDS.minHeightPercent
|
|
34364
|
-
};
|
|
34365
|
-
}
|
|
34366
|
-
function resolveHourRange(segments, bounds) {
|
|
34367
|
-
if (segments.length === 0) return {
|
|
34368
|
-
startHour: bounds.defaultStartHour,
|
|
34369
|
-
endHour: bounds.defaultEndHour
|
|
34370
|
-
};
|
|
34371
|
-
const earliestStart = Math.min(...segments.map((segment) => segment.startsAtMinutes));
|
|
34372
|
-
const latestEnd = Math.max(...segments.map((segment) => segment.endsAtMinutes));
|
|
34373
|
-
const startHour = Math.max(bounds.minStartHour, Math.floor(earliestStart / 60));
|
|
34374
|
-
const endHour = Math.min(bounds.maxEndHour, Math.ceil(latestEnd / 60));
|
|
34375
|
-
return {
|
|
34376
|
-
startHour,
|
|
34377
|
-
endHour: Math.max(startHour + 1, endHour)
|
|
34378
|
-
};
|
|
34379
|
-
}
|
|
34380
|
-
function buildHourTicks(startHour, endHour) {
|
|
34381
|
-
const ticks = [];
|
|
34382
|
-
for (let hour = startHour; hour <= endHour; hour += 1) ticks.push(hour);
|
|
34383
|
-
return ticks;
|
|
34384
|
-
}
|
|
34385
|
-
function groupSegmentsByDate(segments) {
|
|
34386
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
34387
|
-
for (const segment of segments) {
|
|
34388
|
-
const existing = grouped.get(segment.dateKey) ?? [];
|
|
34389
|
-
existing.push(segment);
|
|
34390
|
-
grouped.set(segment.dateKey, existing);
|
|
34391
|
-
}
|
|
34392
|
-
return grouped;
|
|
34393
|
-
}
|
|
34394
|
-
function toRangePercent(minutes, rangeMinutes) {
|
|
34395
|
-
if (rangeMinutes <= 0) return 0;
|
|
34396
|
-
return minutes / rangeMinutes * 100;
|
|
34397
|
-
}
|
|
34398
|
-
function getTimetableDays(viewDate, view, startOfWeek) {
|
|
34399
|
-
if (view === "day") return [viewDate];
|
|
34400
|
-
return getWeekCalendarDays(viewDate, startOfWeek);
|
|
34401
|
-
}
|
|
34402
|
-
function enumerateDateKeys(startDateKey, endDateKey) {
|
|
34403
|
-
if (startDateKey > endDateKey) throw new Error(`Invalid timetable date range: ${startDateKey} is after ${endDateKey}`);
|
|
34404
|
-
const start = parseDateKey(startDateKey);
|
|
34405
|
-
parseDateKey(endDateKey);
|
|
34406
|
-
const keys = [];
|
|
34407
|
-
let cursor = start;
|
|
34408
|
-
while (formatDateKey(cursor) <= endDateKey) {
|
|
34409
|
-
keys.push(formatDateKey(cursor));
|
|
34410
|
-
cursor = addDays(cursor, 1);
|
|
34411
|
-
}
|
|
34412
|
-
return keys;
|
|
34413
|
-
}
|
|
34414
|
-
function parseDateKey(dateKey) {
|
|
34415
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
|
|
34416
|
-
if (match === null) throw new Error(`Invalid timetable date key: ${dateKey}`);
|
|
34417
|
-
const [, year, month, day] = match;
|
|
34418
|
-
return {
|
|
34419
|
-
year: Number(year),
|
|
34420
|
-
month: Number(month),
|
|
34421
|
-
day: Number(day)
|
|
34422
|
-
};
|
|
34423
|
-
}
|
|
34424
|
-
//#endregion
|
|
34425
|
-
//#region ../calendar/src/group.ts
|
|
34426
|
-
/**
|
|
34427
|
-
* Group items by their local date (YYYY-MM-DD in the item's own timezone).
|
|
34428
|
-
*
|
|
34429
|
-
* Generic over `T`. Items with un-parseable startsAt or invalid timezone are
|
|
34430
|
-
* silently dropped — call sites that need diagnostics should validate upstream.
|
|
34431
|
-
*
|
|
34432
|
-
* The output map preserves insertion order of dates (ES2015 spec) and the
|
|
34433
|
-
* relative order of items within each date matches input order.
|
|
34434
|
-
*/
|
|
34435
|
-
function groupItemsByDate(items, accessors) {
|
|
34436
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
34437
|
-
for (const item of items) {
|
|
34438
|
-
const dateKey = getLocalDateKey(accessors.getStartsAt(item), accessors.getTimeZone(item));
|
|
34439
|
-
if (dateKey == null) continue;
|
|
34440
|
-
const existing = grouped.get(dateKey);
|
|
34441
|
-
if (existing !== void 0) existing.push(item);
|
|
34442
|
-
else grouped.set(dateKey, [item]);
|
|
34443
|
-
}
|
|
34444
|
-
return grouped;
|
|
34445
|
-
}
|
|
34446
|
-
//#endregion
|
|
34447
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/eventCapacity.ts
|
|
34448
|
-
/**
|
|
34449
|
-
* Compute remaining spots for an event from `availableSpots` with a fallback
|
|
34450
|
-
* to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
|
|
34451
|
-
* for uncapped events so call sites can render no capacity indicator at all.
|
|
34452
|
-
*
|
|
34453
|
-
* The single source of truth for capacity math across event cards, agenda
|
|
34454
|
-
* rows, and timetable cells — keep wording variations in the call sites.
|
|
34455
|
-
*/
|
|
34456
|
-
function getEventAvailability(event) {
|
|
34457
|
-
if (event.capacity == null) return {
|
|
34458
|
-
available: null,
|
|
34459
|
-
isFull: false
|
|
34460
|
-
};
|
|
34461
|
-
const available = event.availableSpots ?? event.capacity - event.registeredCount;
|
|
34462
|
-
if (available <= 0) return {
|
|
34463
|
-
available: 0,
|
|
34464
|
-
isFull: true
|
|
34465
|
-
};
|
|
34466
|
-
return {
|
|
34467
|
-
available,
|
|
34468
|
-
isFull: false
|
|
34469
|
-
};
|
|
34470
|
-
}
|
|
34471
|
-
/**
|
|
34472
|
-
* Derive the three-state availability status (available / filling / waitlist)
|
|
34473
|
-
* plus a short pill label. Builds on {@link getEventAvailability} so the
|
|
34474
|
-
* capacity math (availableSpots with a `capacity - registeredCount` fallback)
|
|
34475
|
-
* stays consistent with the binary helper used elsewhere.
|
|
34476
|
-
*/
|
|
34477
|
-
function getEventAvailabilityStatus(event, options) {
|
|
34478
|
-
const threshold = options?.fillingThreshold ?? 2;
|
|
34479
|
-
const { available, isFull } = getEventAvailability(event);
|
|
34480
|
-
if (available == null) return {
|
|
34481
|
-
status: "available",
|
|
34482
|
-
available: null,
|
|
34483
|
-
isFull: false,
|
|
34484
|
-
label: null
|
|
34485
|
-
};
|
|
34486
|
-
if (isFull) return {
|
|
34487
|
-
status: "waitlist",
|
|
34488
|
-
available: 0,
|
|
34489
|
-
isFull: true,
|
|
34490
|
-
label: event.waitlistEnabled ? "Waitlist" : "Full"
|
|
34491
|
-
};
|
|
34492
|
-
if (available <= threshold) return {
|
|
34493
|
-
status: "filling",
|
|
34494
|
-
available,
|
|
34495
|
-
isFull: false,
|
|
34496
|
-
label: `${available} left`
|
|
34497
|
-
};
|
|
34498
|
-
return {
|
|
34499
|
-
status: "available",
|
|
34500
|
-
available,
|
|
34501
|
-
isFull: false,
|
|
34502
|
-
label: `${available} places`
|
|
34503
|
-
};
|
|
34504
|
-
}
|
|
34505
35196
|
//#endregion
|
|
34506
|
-
//#region ../blocks/src/system/runtime/nodes/events/shared/
|
|
35197
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/listingLayout.ts
|
|
34507
35198
|
/**
|
|
34508
|
-
*
|
|
35199
|
+
* Layout classes for event listing containers and cards. Presentation
|
|
35200
|
+
* plumbing rather than event domain logic, so it lives beside the components.
|
|
34509
35201
|
*/
|
|
34510
35202
|
/**
|
|
34511
35203
|
* Column class mappings for grid layouts, derived from the canonical
|
|
@@ -34540,180 +35232,6 @@ function getCardOrientation(layout) {
|
|
|
34540
35232
|
case "compact": return "vertical";
|
|
34541
35233
|
}
|
|
34542
35234
|
}
|
|
34543
|
-
/**
|
|
34544
|
-
* Format date for display.
|
|
34545
|
-
* Returns: "Monday, January 15, 2025"
|
|
34546
|
-
*/
|
|
34547
|
-
function formatDate$2(isoTimestamp, options) {
|
|
34548
|
-
return formatBookingDate(toScheduledBookingDateTime({
|
|
34549
|
-
startsAt: isoTimestamp,
|
|
34550
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
34551
|
-
locale: options?.locale ?? "en-US"
|
|
34552
|
-
}), {
|
|
34553
|
-
weekday: "long",
|
|
34554
|
-
year: "numeric",
|
|
34555
|
-
month: "long",
|
|
34556
|
-
day: "numeric"
|
|
34557
|
-
});
|
|
34558
|
-
}
|
|
34559
|
-
/**
|
|
34560
|
-
* Format time for display.
|
|
34561
|
-
* Returns: "10:00 AM"
|
|
34562
|
-
*/
|
|
34563
|
-
function formatTime(isoTimestamp, options) {
|
|
34564
|
-
return formatBookingTime(toScheduledBookingDateTime({
|
|
34565
|
-
startsAt: isoTimestamp,
|
|
34566
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
34567
|
-
locale: options?.locale ?? "en-US"
|
|
34568
|
-
}), {
|
|
34569
|
-
hour: "numeric",
|
|
34570
|
-
minute: "2-digit",
|
|
34571
|
-
hour12: true
|
|
34572
|
-
});
|
|
34573
|
-
}
|
|
34574
|
-
function formatEventDateParts(args) {
|
|
34575
|
-
const scheduledRange = toScheduledBookingDateRange({
|
|
34576
|
-
startsAt: args.startsAt,
|
|
34577
|
-
endsAt: args.endsAt,
|
|
34578
|
-
timeZone: toBookingTimeZone(args.timeZone ?? "UTC"),
|
|
34579
|
-
locale: args.locale ?? "en-US"
|
|
34580
|
-
});
|
|
34581
|
-
const { date, time: start } = formatBookingDateParts(scheduledRange, { dateFormat: {
|
|
34582
|
-
weekday: "long",
|
|
34583
|
-
year: "numeric",
|
|
34584
|
-
month: "long",
|
|
34585
|
-
day: "numeric"
|
|
34586
|
-
} });
|
|
34587
|
-
return {
|
|
34588
|
-
date,
|
|
34589
|
-
timeRange: `${start} – ${formatBookingTime(toScheduledBookingDateTime({
|
|
34590
|
-
startsAt: args.endsAt,
|
|
34591
|
-
timeZone: scheduledRange.timeZone,
|
|
34592
|
-
locale: args.locale ?? "en-US"
|
|
34593
|
-
}))}`
|
|
34594
|
-
};
|
|
34595
|
-
}
|
|
34596
|
-
function formatEventCompactDateRail(isoTimestamp, options) {
|
|
34597
|
-
const dateTime = toScheduledBookingDateTime({
|
|
34598
|
-
startsAt: isoTimestamp,
|
|
34599
|
-
timeZone: toBookingTimeZone(options?.timeZone ?? "UTC"),
|
|
34600
|
-
locale: options?.locale ?? "en-US"
|
|
34601
|
-
});
|
|
34602
|
-
const weekday = formatBookingDate(dateTime, { weekday: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
34603
|
-
const month = formatBookingDate(dateTime, { month: "short" }).toLocaleUpperCase(options?.locale ?? "en-US");
|
|
34604
|
-
return {
|
|
34605
|
-
weekday,
|
|
34606
|
-
day: formatBookingDate(dateTime, { day: "numeric" }),
|
|
34607
|
-
month,
|
|
34608
|
-
label: formatBookingDate(dateTime, {
|
|
34609
|
-
weekday: "long",
|
|
34610
|
-
year: "numeric",
|
|
34611
|
-
month: "long",
|
|
34612
|
-
day: "numeric"
|
|
34613
|
-
})
|
|
34614
|
-
};
|
|
34615
|
-
}
|
|
34616
|
-
function formatTeamMemberNames(names) {
|
|
34617
|
-
const normalized = names.map((name) => name.trim()).filter(Boolean);
|
|
34618
|
-
if (normalized.length === 0) return null;
|
|
34619
|
-
const [firstName, secondName] = normalized;
|
|
34620
|
-
if (!firstName) return null;
|
|
34621
|
-
if (normalized.length === 1) return firstName;
|
|
34622
|
-
if (normalized.length === 2 && secondName) return `${firstName} and ${secondName}`;
|
|
34623
|
-
const finalName = normalized[normalized.length - 1] ?? "";
|
|
34624
|
-
return `${normalized.slice(0, -1).join(", ")}, and ${finalName}`;
|
|
34625
|
-
}
|
|
34626
|
-
function formatEventTeacherLine(event) {
|
|
34627
|
-
const names = formatTeamMemberNames((event.teamMembers ?? []).map((teamMember) => teamMember.name));
|
|
34628
|
-
return names ? `with ${names}` : null;
|
|
34629
|
-
}
|
|
34630
|
-
/**
|
|
34631
|
-
* Group events by their local date (YYYY-MM-DD key in each event's own
|
|
34632
|
-
* timezone). Thin domain wrapper over `groupItemsByDate` from the calendar
|
|
34633
|
-
* package; preserves the historical `Map<string, PublicEvent[]>` shape.
|
|
34634
|
-
*/
|
|
34635
|
-
function groupEventsByDate(events, calendarTimeZone) {
|
|
34636
|
-
const grouped = groupItemsByDate(events, {
|
|
34637
|
-
getStartsAt: (event) => event.startsAt,
|
|
34638
|
-
getTimeZone: () => calendarTimeZone
|
|
34639
|
-
});
|
|
34640
|
-
const map = /* @__PURE__ */ new Map();
|
|
34641
|
-
for (const [dateKey, bucket] of grouped) map.set(dateKey, [...bucket]);
|
|
34642
|
-
return map;
|
|
34643
|
-
}
|
|
34644
|
-
/**
|
|
34645
|
-
* Format event price for display.
|
|
34646
|
-
* @returns "Free", "£25", "£12 - £25", "Free / £12 - £25", "Course (From £40)", etc.
|
|
34647
|
-
*
|
|
34648
|
-
* Events render compact amounts (no trailing ".00"); the actual ADT→string
|
|
34649
|
-
* mapping lives in `formatBookingPriceDisplay` in @riverbankcms/core so all
|
|
34650
|
-
* surfaces stay consistent.
|
|
34651
|
-
*/
|
|
34652
|
-
function formatEventPrice(event) {
|
|
34653
|
-
return formatEventPricing(resolveRuntimeEventPricing(event), { decimals: "auto" });
|
|
34654
|
-
}
|
|
34655
|
-
function closedEventBookingLabel(reason) {
|
|
34656
|
-
switch (reason) {
|
|
34657
|
-
case "occurrence_cancelled":
|
|
34658
|
-
case "series_cancelled": return "Cancelled";
|
|
34659
|
-
case "occurrence_completed":
|
|
34660
|
-
case "series_completed": return "Completed";
|
|
34661
|
-
case "series_draft":
|
|
34662
|
-
case "series_archived":
|
|
34663
|
-
case "parent_archived":
|
|
34664
|
-
case "parent_inconsistent":
|
|
34665
|
-
case "not_public":
|
|
34666
|
-
case "already_started":
|
|
34667
|
-
case "enrollment_closed": return "Registration closed";
|
|
34668
|
-
default: return assertNever$23(reason);
|
|
34669
|
-
}
|
|
34670
|
-
}
|
|
34671
|
-
function resolveEventHref(event) {
|
|
34672
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
34673
|
-
return destination.kind === "external" ? destination.url : destination.path;
|
|
34674
|
-
}
|
|
34675
|
-
function resolveEventLinkProps(event) {
|
|
34676
|
-
const href = resolveEventHref(event);
|
|
34677
|
-
return resolveRuntimeEventDestination(event).kind === "external" ? {
|
|
34678
|
-
href,
|
|
34679
|
-
target: "_blank",
|
|
34680
|
-
rel: "noopener noreferrer"
|
|
34681
|
-
} : { href };
|
|
34682
|
-
}
|
|
34683
|
-
/**
|
|
34684
|
-
* Resolve the CTA state for an event.
|
|
34685
|
-
* Shared by EventCard and EventCompactRow to keep sold-out, registration-closed,
|
|
34686
|
-
* and course-specific defaults consistent.
|
|
34687
|
-
*/
|
|
34688
|
-
function resolveEventCta(event, buttonText) {
|
|
34689
|
-
const { isFull: isSoldOut } = getEventAvailability(event);
|
|
34690
|
-
const destination = resolveRuntimeEventDestination(event);
|
|
34691
|
-
const isExternal = destination.kind === "external";
|
|
34692
|
-
const defaultButtonText = isExternal ? destination.label : event.bookingScope === "whole_series" ? "Book course" : "Book";
|
|
34693
|
-
const effectiveButtonText = buttonText ?? defaultButtonText;
|
|
34694
|
-
const href = resolveEventHref(event);
|
|
34695
|
-
if (event.bookingAvailability.kind === "closed") {
|
|
34696
|
-
const linksToClosedWholeSeriesDetails = event.bookingAvailability.reason === "enrollment_closed" && !isExternal;
|
|
34697
|
-
return {
|
|
34698
|
-
label: closedEventBookingLabel(event.bookingAvailability.reason),
|
|
34699
|
-
href,
|
|
34700
|
-
external: isExternal,
|
|
34701
|
-
hidden: !linksToClosedWholeSeriesDetails
|
|
34702
|
-
};
|
|
34703
|
-
}
|
|
34704
|
-
if (!isExternal && isSoldOut) return {
|
|
34705
|
-
label: "Sold out",
|
|
34706
|
-
href,
|
|
34707
|
-
external: false,
|
|
34708
|
-
hidden: true
|
|
34709
|
-
};
|
|
34710
|
-
return {
|
|
34711
|
-
label: effectiveButtonText,
|
|
34712
|
-
href,
|
|
34713
|
-
external: isExternal,
|
|
34714
|
-
hidden: false
|
|
34715
|
-
};
|
|
34716
|
-
}
|
|
34717
35235
|
//#endregion
|
|
34718
35236
|
//#region ../blocks/src/system/media/slotContracts.ts
|
|
34719
35237
|
const DEFAULT_MIN_SOURCE_WIDTH = 1200;
|
|
@@ -36009,7 +36527,6 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
36009
36527
|
const formattedDate = formatDate$2(event.startsAt, { timeZone });
|
|
36010
36528
|
const formattedTime = `${formatTime(event.startsAt, { timeZone })} - ${formatTime(event.endsAt, { timeZone })}`;
|
|
36011
36529
|
const cta = resolveEventCta(event, buttonText);
|
|
36012
|
-
const { available: spotsLeft, isFull: isSoldOut } = getEventAvailability(event);
|
|
36013
36530
|
const cardClass = boxStyleClassNames({ role: cardRole }, { baseClass: "event-card" });
|
|
36014
36531
|
const buttonClass = themeButtonClassName({
|
|
36015
36532
|
variant: buttonVariant,
|
|
@@ -36025,10 +36542,7 @@ const EventCard = ({ event, cardRole = "default", buttonVariant = "primary", but
|
|
|
36025
36542
|
} : null;
|
|
36026
36543
|
const hasImage = Boolean(showImage && image);
|
|
36027
36544
|
const teacherLine = formatEventTeacherLine(event);
|
|
36028
|
-
const capacityText = showCapacity ?
|
|
36029
|
-
isSoldOut,
|
|
36030
|
-
spotsLeft
|
|
36031
|
-
}) : null;
|
|
36545
|
+
const capacityText = showCapacity ? formatEventRemainingPlaces(event, { full: "Sold out" }) : null;
|
|
36032
36546
|
return /* @__PURE__ */ jsxs("article", {
|
|
36033
36547
|
className: `${cardClass} group rb-flex rb-flex-col rb-h-full ${eventCardOrientationClass(orientation)}`,
|
|
36034
36548
|
"data-orientation": orientation,
|
|
@@ -36094,7 +36608,11 @@ function EventCardContent(props) {
|
|
|
36094
36608
|
}),
|
|
36095
36609
|
/* @__PURE__ */ jsx("h3", {
|
|
36096
36610
|
className: "card-title rb-mb-3",
|
|
36097
|
-
children:
|
|
36611
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
36612
|
+
className: "event-title-link",
|
|
36613
|
+
...resolveEventLinkProps(event),
|
|
36614
|
+
children: props.title
|
|
36615
|
+
})
|
|
36098
36616
|
}),
|
|
36099
36617
|
/* @__PURE__ */ jsx("div", {
|
|
36100
36618
|
className: "rb-mb-2 rb-caption",
|
|
@@ -36171,13 +36689,6 @@ function eventCardOrientationClass(orientation) {
|
|
|
36171
36689
|
case "vertical": return "event-card--vertical";
|
|
36172
36690
|
}
|
|
36173
36691
|
}
|
|
36174
|
-
function formatCapacityText(event, options) {
|
|
36175
|
-
if (event.capacity == null) return null;
|
|
36176
|
-
if (options.isSoldOut) return "Sold out";
|
|
36177
|
-
if (options.spotsLeft == null) return null;
|
|
36178
|
-
const count = options.spotsLeft;
|
|
36179
|
-
return `${count} ${count === 1 ? "spot" : "spots"} available`;
|
|
36180
|
-
}
|
|
36181
36692
|
function getCategoryBadgeStyle(background) {
|
|
36182
36693
|
const normalized = background?.trim() ?? "";
|
|
36183
36694
|
if (!normalized) return {
|
|
@@ -36246,7 +36757,11 @@ const EventCompactRow = ({ event, buttonVariant = "primary", buttonText }) => {
|
|
|
36246
36757
|
className: "event-compact-row-content",
|
|
36247
36758
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
36248
36759
|
className: "event-compact-row-title",
|
|
36249
|
-
children:
|
|
36760
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
36761
|
+
className: "event-title-link",
|
|
36762
|
+
...resolveEventLinkProps(event),
|
|
36763
|
+
children: event.title
|
|
36764
|
+
})
|
|
36250
36765
|
}), /* @__PURE__ */ jsxs("div", {
|
|
36251
36766
|
className: "event-compact-row-meta",
|
|
36252
36767
|
children: [
|
|
@@ -36335,6 +36850,23 @@ function DateTimeTile({ startsAt, endsAt, timeZone }) {
|
|
|
36335
36850
|
});
|
|
36336
36851
|
}
|
|
36337
36852
|
//#endregion
|
|
36853
|
+
//#region ../blocks/src/system/runtime/nodes/events/shared/OfferingTitle.tsx
|
|
36854
|
+
/**
|
|
36855
|
+
* Heading for an offering card or row. Links to the offering's page when one
|
|
36856
|
+
* exists so a sold-out or closed offering stays reachable (issue 1372); the
|
|
36857
|
+
* `.event-title-link` rule lives in theme-core's event-list.css.
|
|
36858
|
+
*/
|
|
36859
|
+
function OfferingTitle({ className, path, title }) {
|
|
36860
|
+
return /* @__PURE__ */ jsx("h3", {
|
|
36861
|
+
className,
|
|
36862
|
+
children: path ? /* @__PURE__ */ jsx("a", {
|
|
36863
|
+
className: "event-title-link",
|
|
36864
|
+
href: path,
|
|
36865
|
+
children: title
|
|
36866
|
+
}) : title
|
|
36867
|
+
});
|
|
36868
|
+
}
|
|
36869
|
+
//#endregion
|
|
36338
36870
|
//#region ../blocks/src/system/runtime/nodes/events/shared/listOptions.ts
|
|
36339
36871
|
const eventPageSizeValues = [
|
|
36340
36872
|
3,
|
|
@@ -37679,7 +38211,7 @@ const eventCalendarAdditionalFields = [
|
|
|
37679
38211
|
{
|
|
37680
38212
|
id: "showCapacity",
|
|
37681
38213
|
type: "boolean",
|
|
37682
|
-
label: "Show available
|
|
38214
|
+
label: "Show available places",
|
|
37683
38215
|
description: "Display remaining capacity on event cards and timetable cells",
|
|
37684
38216
|
required: false,
|
|
37685
38217
|
defaultValue: true,
|
|
@@ -47069,7 +47601,7 @@ function EventFacts({ selection, selected, venue, teamMembers, pricing, showCapa
|
|
|
47069
47601
|
children: [/* @__PURE__ */ jsx("div", {
|
|
47070
47602
|
className: "rb-label rb-mb-2",
|
|
47071
47603
|
children: "Places"
|
|
47072
|
-
}), /* @__PURE__ */
|
|
47604
|
+
}), /* @__PURE__ */ jsx("p", { children: formatTotalCapacity(selected.capacity) })]
|
|
47073
47605
|
}) : null,
|
|
47074
47606
|
pricing,
|
|
47075
47607
|
selected.bookingScope === "whole_series" ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -47913,9 +48445,10 @@ function CourseCompactRow({ course, path }) {
|
|
|
47913
48445
|
}),
|
|
47914
48446
|
/* @__PURE__ */ jsxs("div", {
|
|
47915
48447
|
className: "event-compact-row-content",
|
|
47916
|
-
children: [/* @__PURE__ */ jsx(
|
|
48448
|
+
children: [/* @__PURE__ */ jsx(OfferingTitle, {
|
|
47917
48449
|
className: "event-compact-row-title",
|
|
47918
|
-
|
|
48450
|
+
path,
|
|
48451
|
+
title: course.title
|
|
47919
48452
|
}), /* @__PURE__ */ jsxs("div", {
|
|
47920
48453
|
className: "event-compact-row-meta",
|
|
47921
48454
|
children: [
|
|
@@ -47972,9 +48505,10 @@ function CourseFeatureCard({ course, path, buttonVariant, buttonText }) {
|
|
|
47972
48505
|
children: /* @__PURE__ */ jsxs("div", {
|
|
47973
48506
|
className: "card-content rb-flex rb-flex-1 rb-flex-col",
|
|
47974
48507
|
children: [
|
|
47975
|
-
/* @__PURE__ */ jsx(
|
|
48508
|
+
/* @__PURE__ */ jsx(OfferingTitle, {
|
|
47976
48509
|
className: "card-title rb-mb-3",
|
|
47977
|
-
|
|
48510
|
+
path,
|
|
48511
|
+
title: course.title
|
|
47978
48512
|
}),
|
|
47979
48513
|
/* @__PURE__ */ jsxs("div", {
|
|
47980
48514
|
className: "rb-mb-2 rb-caption",
|
|
@@ -48830,8 +49364,8 @@ const AgendaEventCard = ({ event, showCapacity, onClick }) => {
|
|
|
48830
49364
|
const eventLinkProps = resolveEventLinkProps(event);
|
|
48831
49365
|
const categoryColor = event.eventCategory?.color || "rgb(var(--tb-accent))";
|
|
48832
49366
|
const price = formatEventPrice(event);
|
|
48833
|
-
const
|
|
48834
|
-
const
|
|
49367
|
+
const capacity = showCapacity ? formatEventRemainingPlaces(event) : null;
|
|
49368
|
+
const isFull = isEventFull(event);
|
|
48835
49369
|
const title = event.title;
|
|
48836
49370
|
const timeZone = event.timeZone ?? null;
|
|
48837
49371
|
const teacherLine = formatEventTeacherLine(event);
|
|
@@ -49273,7 +49807,7 @@ function buildTimetableModel(input) {
|
|
|
49273
49807
|
getEndsAt: (event) => event.endsAt,
|
|
49274
49808
|
getTimeZone: (event) => event.timeZone ?? "UTC",
|
|
49275
49809
|
getColor: (event) => event.eventCategory?.color ?? null,
|
|
49276
|
-
getOrderKey: (event) => `${event.title}
|
|
49810
|
+
getOrderKey: (event) => `${event.title}\u0000${event.id}`
|
|
49277
49811
|
});
|
|
49278
49812
|
return {
|
|
49279
49813
|
hourTicks: [...generic.hourTicks],
|
|
@@ -49310,18 +49844,9 @@ function toEventCell(cell) {
|
|
|
49310
49844
|
};
|
|
49311
49845
|
}
|
|
49312
49846
|
function formatTimetableCapacity(event) {
|
|
49313
|
-
const { available, isFull } = getEventAvailability(event);
|
|
49314
|
-
if (available == null) return {
|
|
49315
|
-
label: null,
|
|
49316
|
-
isFull: false
|
|
49317
|
-
};
|
|
49318
|
-
if (isFull) return {
|
|
49319
|
-
label: "Full",
|
|
49320
|
-
isFull: true
|
|
49321
|
-
};
|
|
49322
49847
|
return {
|
|
49323
|
-
label:
|
|
49324
|
-
isFull:
|
|
49848
|
+
label: formatEventRemainingPlaces(event, { compact: true }),
|
|
49849
|
+
isFull: isEventFull(event)
|
|
49325
49850
|
};
|
|
49326
49851
|
}
|
|
49327
49852
|
function formatEventTimeRange(event) {
|
|
@@ -49797,7 +50322,7 @@ function EventRegistrationSSR({ blockId, subject, children, ...sourceProps }) {
|
|
|
49797
50322
|
className: `event-registration ${className ?? ""}`.trim(),
|
|
49798
50323
|
"data-block": "event-registration",
|
|
49799
50324
|
"data-rb-island-required": "true",
|
|
49800
|
-
"data-rb-island-error-message": "
|
|
50325
|
+
"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.",
|
|
49801
50326
|
"data-rb-island-error-class": "event-registration__hydration-error",
|
|
49802
50327
|
...getIslandRootProps("event-registration", blockId),
|
|
49803
50328
|
children: [
|
|
@@ -55707,6 +56232,32 @@ function isSiteStyleTransitionStyleGroup(value) {
|
|
|
55707
56232
|
function boundaryMapForAdjacentBlockIds(boundaries) {
|
|
55708
56233
|
return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
|
|
55709
56234
|
}
|
|
56235
|
+
/** Keep a surviving block's outgoing treatment when omitted blocks change its neighbor. */
|
|
56236
|
+
function boundariesForSurvivingBlocks(boundaries, blocks) {
|
|
56237
|
+
const outgoing = new Map(boundaries.map((boundary) => [boundary.previousBlockId, boundary]));
|
|
56238
|
+
const incoming = new Map(boundaries.map((boundary) => [boundary.nextBlockId, boundary]));
|
|
56239
|
+
return blocks.flatMap((block, index) => {
|
|
56240
|
+
const next = blocks[index + 1];
|
|
56241
|
+
if (!block.id || !next?.id) return [];
|
|
56242
|
+
const boundary = outgoing.get(block.id);
|
|
56243
|
+
if (!boundary) return [];
|
|
56244
|
+
if (boundary.nextBlockId === next.id) return [boundary];
|
|
56245
|
+
const destination = incoming.get(next.id);
|
|
56246
|
+
return [retargetCompiledBoundaryRuntimeForContentAdjacency({
|
|
56247
|
+
...boundary,
|
|
56248
|
+
boundaryId: `boundary:${block.id}:${next.id}`,
|
|
56249
|
+
nextBlockId: next.id,
|
|
56250
|
+
to: destination?.to ?? boundary.to,
|
|
56251
|
+
cssVars: {
|
|
56252
|
+
...boundary.cssVars,
|
|
56253
|
+
to: destination?.cssVars.to ?? boundary.cssVars.to
|
|
56254
|
+
}
|
|
56255
|
+
}, {
|
|
56256
|
+
currentContent: block.content,
|
|
56257
|
+
nextContent: next.content
|
|
56258
|
+
})];
|
|
56259
|
+
});
|
|
56260
|
+
}
|
|
55710
56261
|
function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
|
|
55711
56262
|
const previousBlockId = previousBlock?.id ?? null;
|
|
55712
56263
|
const nextBlockId = nextBlock?.id ?? null;
|
|
@@ -55798,41 +56349,179 @@ function toPresentationPage(page) {
|
|
|
55798
56349
|
};
|
|
55799
56350
|
}
|
|
55800
56351
|
//#endregion
|
|
56352
|
+
//#region ../blocks/src/pageRendererBlockSupport.tsx
|
|
56353
|
+
/**
|
|
56354
|
+
* Support for rendering one block section: what a block that cannot render
|
|
56355
|
+
* leaves behind, how an override is found, and the props both override paths
|
|
56356
|
+
* hand to a component. `PageRenderer` owns the page-level composition; this
|
|
56357
|
+
* module owns the pieces it reaches for per block.
|
|
56358
|
+
*/
|
|
56359
|
+
function fallback(message) {
|
|
56360
|
+
return /* @__PURE__ */ jsx("div", {
|
|
56361
|
+
className: "rb-p-6 rb-caption",
|
|
56362
|
+
style: textColorStyle("mutedText"),
|
|
56363
|
+
children: message
|
|
56364
|
+
});
|
|
56365
|
+
}
|
|
56366
|
+
/**
|
|
56367
|
+
* A page with nothing to show. Authoring surfaces get the diagnostic; a public
|
|
56368
|
+
* page renders nothing at all rather than telling a visitor about the outline.
|
|
56369
|
+
*/
|
|
56370
|
+
function emptyPageNotice(audience) {
|
|
56371
|
+
if (audience === "public") return null;
|
|
56372
|
+
return /* @__PURE__ */ jsx("div", {
|
|
56373
|
+
className: "rb-p-6 rb-caption",
|
|
56374
|
+
style: textColorStyle("mutedText"),
|
|
56375
|
+
children: "No blocks found."
|
|
56376
|
+
});
|
|
56377
|
+
}
|
|
56378
|
+
/**
|
|
56379
|
+
* A public page dropped a block, so nothing in the rendered HTML says the
|
|
56380
|
+
* content is missing. Say it in the logs instead.
|
|
56381
|
+
*/
|
|
56382
|
+
function warnOmittedFromPublicPage(input) {
|
|
56383
|
+
try {
|
|
56384
|
+
console.warn("[blocks:PageRenderer] omitted a non-renderable block from a public page", {
|
|
56385
|
+
blockId: input.block.id ?? null,
|
|
56386
|
+
blockKind: input.block.kind,
|
|
56387
|
+
outcome: input.outcome,
|
|
56388
|
+
...input.admissionError ? { error: input.admissionError } : {}
|
|
56389
|
+
});
|
|
56390
|
+
} catch {}
|
|
56391
|
+
}
|
|
56392
|
+
function shouldDebugCustomBlocks() {
|
|
56393
|
+
try {
|
|
56394
|
+
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
56395
|
+
} catch {
|
|
56396
|
+
return false;
|
|
56397
|
+
}
|
|
56398
|
+
}
|
|
56399
|
+
/**
|
|
56400
|
+
* Look up a block override component by block kind.
|
|
56401
|
+
* Supports both full ("block.hero") and short ("hero") keys.
|
|
56402
|
+
*/
|
|
56403
|
+
function getBlockOverride(blockKind, overrides) {
|
|
56404
|
+
if (!overrides) return void 0;
|
|
56405
|
+
if (overrides[blockKind]) return overrides[blockKind];
|
|
56406
|
+
const shortKind = blockKind.replace(/^block\./, "");
|
|
56407
|
+
if (overrides[shortKind]) return overrides[shortKind];
|
|
56408
|
+
}
|
|
56409
|
+
/**
|
|
56410
|
+
* Resolves block content based on stage priority.
|
|
56411
|
+
* For draft/preview: draftContent takes priority over content.
|
|
56412
|
+
* For published: content is the source of truth.
|
|
56413
|
+
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
56414
|
+
*/
|
|
56415
|
+
function getRenderableContent(block, previewStage = "published") {
|
|
56416
|
+
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
56417
|
+
if (!isObjectRecord$1(source)) return {};
|
|
56418
|
+
return source;
|
|
56419
|
+
}
|
|
56420
|
+
function resolvePageRendererImageResolveContext(dataContext) {
|
|
56421
|
+
const input = dataContext?.imageResolveContext;
|
|
56422
|
+
return buildResolveImageUrlContext({
|
|
56423
|
+
siteId: input?.siteId ?? dataContext?.siteId,
|
|
56424
|
+
supabaseUrl: input?.supabaseUrl ?? dataContext?.supabaseUrl,
|
|
56425
|
+
mediaAssetIndex: input?.mediaAssetIndex,
|
|
56426
|
+
mediaAssets: input?.mediaAssets ?? dataContext?.mediaAssets
|
|
56427
|
+
});
|
|
56428
|
+
}
|
|
56429
|
+
/**
|
|
56430
|
+
* The RSC-safe props every override component receives, whether the block
|
|
56431
|
+
* resolved a manifest or is being rendered from an override alone.
|
|
56432
|
+
*/
|
|
56433
|
+
function buildBlockOverrideProps(input) {
|
|
56434
|
+
return {
|
|
56435
|
+
content: input.content,
|
|
56436
|
+
...overrideThemeProps(input.themeResources),
|
|
56437
|
+
...blockOverrideContextProps(input.dataContext),
|
|
56438
|
+
data: input.data,
|
|
56439
|
+
imageResolveContext: input.imageResolveContext,
|
|
56440
|
+
blockId: input.block.id ?? null,
|
|
56441
|
+
blockKind: input.block.kind
|
|
56442
|
+
};
|
|
56443
|
+
}
|
|
56444
|
+
/** The render-context half of the override props, absent context included. */
|
|
56445
|
+
function blockOverrideContextProps(dataContext) {
|
|
56446
|
+
const context = dataContext ?? {};
|
|
56447
|
+
return {
|
|
56448
|
+
siteId: context.siteId,
|
|
56449
|
+
apiBaseUrl: context.apiBaseUrl,
|
|
56450
|
+
bookingTimeZone: context.bookingTimeZone,
|
|
56451
|
+
portalToken: context.portalToken ?? null,
|
|
56452
|
+
previewToken: context.previewToken ?? null,
|
|
56453
|
+
stage: context.previewStage,
|
|
56454
|
+
occurrenceContext: context.occurrenceContext ?? null,
|
|
56455
|
+
entrySubrouteContext: context.entrySubrouteContext ?? null,
|
|
56456
|
+
contentEntry: context.contentEntry ?? null,
|
|
56457
|
+
pageSurface: context.pageSurface ?? null,
|
|
56458
|
+
subject: context.subject ?? null,
|
|
56459
|
+
supabaseUrl: context.supabaseUrl
|
|
56460
|
+
};
|
|
56461
|
+
}
|
|
56462
|
+
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
56463
|
+
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
56464
|
+
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
56465
|
+
function getPerfSlowBlockRenderThresholdMs() {
|
|
56466
|
+
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
56467
|
+
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
56468
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
56469
|
+
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
56470
|
+
return cachedPerfSlowBlockRenderThresholdMs;
|
|
56471
|
+
}
|
|
56472
|
+
function getCachedBlockComponent(manifest) {
|
|
56473
|
+
const cached = blockComponentCache.get(manifest);
|
|
56474
|
+
if (cached) return cached;
|
|
56475
|
+
const created = makeDefaultBlockComponent({ manifest });
|
|
56476
|
+
blockComponentCache.set(manifest, created);
|
|
56477
|
+
return created;
|
|
56478
|
+
}
|
|
56479
|
+
//#endregion
|
|
55801
56480
|
//#region ../blocks/src/PageRenderer.tsx
|
|
55802
56481
|
function PageRenderer$1(props) {
|
|
55803
56482
|
const page = React$1.useMemo(() => toPresentationPage(props.page), [props.page]);
|
|
55804
56483
|
return /* @__PURE__ */ jsx(PageRendererCore, {
|
|
55805
56484
|
...props,
|
|
55806
|
-
page
|
|
56485
|
+
page,
|
|
56486
|
+
audience: "public"
|
|
55807
56487
|
});
|
|
55808
56488
|
}
|
|
55809
56489
|
function PageRendererCore(props) {
|
|
55810
|
-
const { page, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
56490
|
+
const { page, audience, runtimeBoundaries, dataContext, wrapBlock, wrapBoundary, registry } = props;
|
|
55811
56491
|
const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
|
|
55812
56492
|
dataContext?.imageResolveContext,
|
|
55813
56493
|
dataContext?.mediaAssets,
|
|
55814
56494
|
dataContext?.siteId,
|
|
55815
56495
|
dataContext?.supabaseUrl
|
|
55816
56496
|
]);
|
|
55817
|
-
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(runtimeBoundaries ?? []), [runtimeBoundaries]);
|
|
55818
56497
|
const usesRuntimeBoundaries = runtimeBoundaries !== void 0;
|
|
55819
56498
|
const themeResources = React$1.useMemo(() => resolvePageThemeResources(props), [
|
|
55820
56499
|
props.rendering,
|
|
55821
56500
|
props.theme,
|
|
55822
56501
|
props.themeTokens
|
|
55823
56502
|
]);
|
|
55824
|
-
if (!page || page.blocks.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
55825
|
-
className: "rb-p-6 rb-caption",
|
|
55826
|
-
style: textColorStyle("mutedText"),
|
|
55827
|
-
children: "No blocks found."
|
|
55828
|
-
});
|
|
55829
56503
|
const resolvedRegistry = registry ?? getDefaultComponentRegistry();
|
|
55830
56504
|
setContextResolveImageUrlContext(imageResolveContext);
|
|
55831
|
-
|
|
55832
|
-
|
|
55833
|
-
|
|
55834
|
-
|
|
55835
|
-
|
|
56505
|
+
const sections = usePageSections(props, {
|
|
56506
|
+
page,
|
|
56507
|
+
themeResources,
|
|
56508
|
+
imageResolveContext,
|
|
56509
|
+
registry: resolvedRegistry
|
|
56510
|
+
});
|
|
56511
|
+
const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(boundariesForSurvivingBlocks(runtimeBoundaries ?? [], sections.map(({ block }) => ({
|
|
56512
|
+
...block,
|
|
56513
|
+
content: getRenderableContent(block, dataContext?.previewStage)
|
|
56514
|
+
})))), [
|
|
56515
|
+
runtimeBoundaries,
|
|
56516
|
+
sections,
|
|
56517
|
+
dataContext?.previewStage
|
|
56518
|
+
]);
|
|
56519
|
+
if (sections.length === 0) return emptyPageNotice(audience);
|
|
56520
|
+
return /* @__PURE__ */ jsx("div", { children: sections.map((section, index) => {
|
|
56521
|
+
const { block, blockKey, node } = section;
|
|
56522
|
+
const blockPosition = pageBlockPosition(index, sections.length);
|
|
56523
|
+
const previousBlock = sections[index - 1]?.block ?? null;
|
|
56524
|
+
const nextBlock = sections[index + 1]?.block ?? null;
|
|
55836
56525
|
const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
|
|
55837
56526
|
const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
|
|
55838
56527
|
const boundaryElement = renderBoundaryAfterBlock({
|
|
@@ -55847,19 +56536,7 @@ function PageRendererCore(props) {
|
|
|
55847
56536
|
const managesOwnWrapper = definition?.manifest.behaviours?.managesOwnWrapper;
|
|
55848
56537
|
const renderableContent = getRenderableContent(block, dataContext?.previewStage);
|
|
55849
56538
|
const variantKey = definition?.manifest ? resolveVariantKey(definition.manifest, renderableContent) : null;
|
|
55850
|
-
|
|
55851
|
-
const blockSectionProps = buildBlockSectionProps({
|
|
55852
|
-
props,
|
|
55853
|
-
themeResources,
|
|
55854
|
-
block,
|
|
55855
|
-
blockKey,
|
|
55856
|
-
imageResolveContext,
|
|
55857
|
-
registry: resolvedRegistry
|
|
55858
|
-
});
|
|
55859
|
-
if (!shouldUseWrapper) {
|
|
55860
|
-
const rendered = /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
55861
|
-
return boundaryElement ? /* @__PURE__ */ jsxs(React$1.Fragment, { children: [rendered, boundaryElement] }, blockKey) : rendered;
|
|
55862
|
-
}
|
|
56539
|
+
if (!(!managesOwnWrapper || Boolean(wrapBlock))) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [node, boundaryElement] }, blockKey);
|
|
55863
56540
|
const wrapperClassName = [`block-${cleanKind}`, wrapBlock ? "group relative" : null].filter(Boolean).join(" ");
|
|
55864
56541
|
const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
|
|
55865
56542
|
const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
|
|
@@ -55884,7 +56561,7 @@ function PageRendererCore(props) {
|
|
|
55884
56561
|
"data-site-style-boundary-after": boundaryAfter?.metrics.size,
|
|
55885
56562
|
"data-style-group": styleGroup,
|
|
55886
56563
|
style: blockStyle,
|
|
55887
|
-
children:
|
|
56564
|
+
children: node
|
|
55888
56565
|
}, blockKey);
|
|
55889
56566
|
if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
|
|
55890
56567
|
if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
|
|
@@ -55897,18 +56574,69 @@ function PageRendererCore(props) {
|
|
|
55897
56574
|
})] }, blockKey);
|
|
55898
56575
|
}) });
|
|
55899
56576
|
}
|
|
55900
|
-
|
|
55901
|
-
|
|
55902
|
-
|
|
55903
|
-
|
|
55904
|
-
|
|
55905
|
-
|
|
55906
|
-
|
|
56577
|
+
/**
|
|
56578
|
+
* Memoised so that a re-render with unchanged inputs reuses the same section
|
|
56579
|
+
* elements and React can skip the block subtrees, exactly as the per-block memo
|
|
56580
|
+
* did before public pages started resolving their bodies up front.
|
|
56581
|
+
*/
|
|
56582
|
+
function usePageSections(props, input) {
|
|
56583
|
+
const { page, themeResources, imageResolveContext, registry } = input;
|
|
56584
|
+
return React$1.useMemo(() => admitPageSections({
|
|
56585
|
+
props,
|
|
56586
|
+
blocks: page?.blocks ?? [],
|
|
56587
|
+
audience: props.audience,
|
|
56588
|
+
themeResources,
|
|
56589
|
+
imageResolveContext,
|
|
56590
|
+
registry
|
|
56591
|
+
}), [
|
|
56592
|
+
page,
|
|
56593
|
+
props.audience,
|
|
56594
|
+
themeResources,
|
|
56595
|
+
imageResolveContext,
|
|
56596
|
+
registry,
|
|
56597
|
+
props.dataContext,
|
|
56598
|
+
props.routeMap,
|
|
56599
|
+
props.wrapBlock,
|
|
56600
|
+
props.blockOverrides,
|
|
56601
|
+
props.sdkConfig,
|
|
56602
|
+
props.customBlocks,
|
|
56603
|
+
props.previewAuthoring
|
|
56604
|
+
]);
|
|
56605
|
+
}
|
|
56606
|
+
/**
|
|
56607
|
+
* Resolves each block's rendered body before the page decides what to wrap.
|
|
56608
|
+
*
|
|
56609
|
+
* On a public page the body is resolved eagerly so a block that cannot render
|
|
56610
|
+
* is dropped along with its wrapper, its data attributes, its boundary reserve
|
|
56611
|
+
* space and its style-group sibling — the section body is the single source of
|
|
56612
|
+
* truth for whether the block is on the page (issue 1378). Authoring surfaces
|
|
56613
|
+
* keep the memoised component so the editor's render-loop guards still hold.
|
|
56614
|
+
*/
|
|
56615
|
+
function admitPageSections(input) {
|
|
56616
|
+
const sections = [];
|
|
56617
|
+
input.blocks.forEach((block, index) => {
|
|
56618
|
+
const blockKey = block.id ?? `${block.kind}-${index}`;
|
|
56619
|
+
const blockSectionProps = buildBlockSectionProps({
|
|
56620
|
+
props: input.props,
|
|
56621
|
+
themeResources: input.themeResources,
|
|
56622
|
+
block,
|
|
56623
|
+
blockKey,
|
|
56624
|
+
imageResolveContext: input.imageResolveContext,
|
|
56625
|
+
registry: input.registry
|
|
56626
|
+
});
|
|
56627
|
+
const node = input.audience === "public" ? renderBlockSection(blockSectionProps) : /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps }, `${blockKey}:block`);
|
|
56628
|
+
if (node === null || node === void 0) return;
|
|
56629
|
+
sections.push({
|
|
56630
|
+
block,
|
|
56631
|
+
blockKey,
|
|
56632
|
+
node
|
|
56633
|
+
});
|
|
55907
56634
|
});
|
|
56635
|
+
return sections;
|
|
55908
56636
|
}
|
|
55909
|
-
const resolveBookingTimeZone = (dataContext) => dataContext?.bookingTimeZone;
|
|
55910
56637
|
function buildBlockSectionProps(input) {
|
|
55911
56638
|
return {
|
|
56639
|
+
audience: input.props.audience,
|
|
55912
56640
|
themeResources: input.themeResources,
|
|
55913
56641
|
block: input.block,
|
|
55914
56642
|
dataContext: input.props.dataContext,
|
|
@@ -55924,40 +56652,24 @@ function buildBlockSectionProps(input) {
|
|
|
55924
56652
|
};
|
|
55925
56653
|
}
|
|
55926
56654
|
/**
|
|
55927
|
-
* Look up a block override component by block kind.
|
|
55928
|
-
* Supports both full ("block.hero") and short ("hero") keys.
|
|
55929
|
-
*/
|
|
55930
|
-
function getBlockOverride(blockKind, overrides) {
|
|
55931
|
-
if (!overrides) return void 0;
|
|
55932
|
-
if (overrides[blockKind]) return overrides[blockKind];
|
|
55933
|
-
const shortKind = blockKind.replace(/^block\./, "");
|
|
55934
|
-
if (overrides[shortKind]) return overrides[shortKind];
|
|
55935
|
-
}
|
|
55936
|
-
const blockComponentCache = /* @__PURE__ */ new WeakMap();
|
|
55937
|
-
const DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS = 25;
|
|
55938
|
-
let cachedPerfSlowBlockRenderThresholdMs = null;
|
|
55939
|
-
function getPerfSlowBlockRenderThresholdMs() {
|
|
55940
|
-
if (cachedPerfSlowBlockRenderThresholdMs !== null) return cachedPerfSlowBlockRenderThresholdMs;
|
|
55941
|
-
const raw = globalThis.process?.env?.DEBUG_PERF_BLOCK_MS;
|
|
55942
|
-
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
55943
|
-
cachedPerfSlowBlockRenderThresholdMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_PERF_SLOW_BLOCK_RENDER_THRESHOLD_MS;
|
|
55944
|
-
return cachedPerfSlowBlockRenderThresholdMs;
|
|
55945
|
-
}
|
|
55946
|
-
function getCachedBlockComponent(manifest) {
|
|
55947
|
-
const cached = blockComponentCache.get(manifest);
|
|
55948
|
-
if (cached) return cached;
|
|
55949
|
-
const created = makeDefaultBlockComponent({ manifest });
|
|
55950
|
-
blockComponentCache.set(manifest, created);
|
|
55951
|
-
return created;
|
|
55952
|
-
}
|
|
55953
|
-
/**
|
|
55954
56655
|
* Memoized block section component.
|
|
55955
56656
|
*
|
|
55956
56657
|
* Custom memoization prevents render loops during editing by explicitly checking
|
|
55957
56658
|
* referential equality of all props that affect rendering. Standard React.memo
|
|
55958
56659
|
* wouldn't catch all the render triggers from the editor store.
|
|
55959
56660
|
*/
|
|
55960
|
-
const MemoBlockSection = React$1.memo(function MemoBlockSection(
|
|
56661
|
+
const MemoBlockSection = React$1.memo(function MemoBlockSection(props) {
|
|
56662
|
+
return renderBlockSection(props);
|
|
56663
|
+
}, (prev, next) => {
|
|
56664
|
+
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;
|
|
56665
|
+
});
|
|
56666
|
+
/**
|
|
56667
|
+
* Renders a single block body.
|
|
56668
|
+
*
|
|
56669
|
+
* Returns `null` when the block cannot render and the audience is `public`;
|
|
56670
|
+
* the caller treats that as "this block is not on the page".
|
|
56671
|
+
*/
|
|
56672
|
+
function renderBlockSection({ audience, themeResources, block, dataContext, registry, routeMap, wrapBlock, blockOverrides, sdkConfig, customBlocks, imageResolveContext, previewAuthoring }) {
|
|
55961
56673
|
const perfStart = globalThis.process?.env?.DEBUG_PERF === "1" && typeof window === "undefined" ? Date.now() : 0;
|
|
55962
56674
|
const fallbackImageResolveContext = getCurrentResolveImageUrlContext();
|
|
55963
56675
|
const resolvedImageResolveContext = imageResolveContext ?? fallbackImageResolveContext;
|
|
@@ -55977,19 +56689,30 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
55977
56689
|
} catch {}
|
|
55978
56690
|
return node;
|
|
55979
56691
|
}
|
|
55980
|
-
|
|
55981
|
-
|
|
55982
|
-
|
|
55983
|
-
|
|
55984
|
-
|
|
55985
|
-
|
|
55986
|
-
|
|
55987
|
-
|
|
55988
|
-
|
|
55989
|
-
|
|
55990
|
-
override: false
|
|
56692
|
+
/**
|
|
56693
|
+
* The one place that decides what a block that cannot render leaves behind.
|
|
56694
|
+
* A public page gets nothing (and a log line, since dropping content on a
|
|
56695
|
+
* 200 is otherwise invisible); authoring surfaces get the diagnostic.
|
|
56696
|
+
*/
|
|
56697
|
+
function diagnostic(message, detail) {
|
|
56698
|
+
if (audience !== "public") return fallback(message);
|
|
56699
|
+
warnOmittedFromPublicPage({
|
|
56700
|
+
block,
|
|
56701
|
+
...detail
|
|
55991
56702
|
});
|
|
56703
|
+
return null;
|
|
56704
|
+
}
|
|
56705
|
+
function withBlockWrapper(node) {
|
|
56706
|
+
if (node === null) return null;
|
|
56707
|
+
return wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, node) }) : /* @__PURE__ */ jsx(Fragment, { children: node });
|
|
55992
56708
|
}
|
|
56709
|
+
if (block.presentationAdmission.kind === "invalid") return finish(diagnostic(`Invalid content for block kind "${block.kind}".`, {
|
|
56710
|
+
outcome: "invalidContent",
|
|
56711
|
+
admissionError: block.presentationAdmission.error
|
|
56712
|
+
}), {
|
|
56713
|
+
outcome: "invalidContent",
|
|
56714
|
+
override: false
|
|
56715
|
+
});
|
|
55993
56716
|
const OverrideComponent = getBlockOverride(block.kind, blockOverrides);
|
|
55994
56717
|
requireBlockOverride(themeResources, OverrideComponent, block.kind);
|
|
55995
56718
|
let definition = getBlockDefinition(block.kind);
|
|
@@ -56002,25 +56725,14 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56002
56725
|
}).findManifest(block.kind)?.descriptor.definition;
|
|
56003
56726
|
if (!definition) {
|
|
56004
56727
|
if (OverrideComponent) {
|
|
56005
|
-
const rendered = /* @__PURE__ */ jsx(OverrideComponent, {
|
|
56728
|
+
const rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...buildBlockOverrideProps({
|
|
56729
|
+
block,
|
|
56006
56730
|
content: getRenderableContent(block, dataContext?.previewStage),
|
|
56007
|
-
...overrideThemeProps(themeResources),
|
|
56008
56731
|
data: void 0,
|
|
56009
|
-
|
|
56010
|
-
|
|
56011
|
-
|
|
56012
|
-
|
|
56013
|
-
stage: dataContext?.previewStage,
|
|
56014
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
56015
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
56016
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
56017
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
56018
|
-
subject: dataContext?.subject ?? null,
|
|
56019
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
56020
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
56021
|
-
blockId: block.id ?? null,
|
|
56022
|
-
blockKind: block.kind
|
|
56023
|
-
});
|
|
56732
|
+
themeResources,
|
|
56733
|
+
dataContext,
|
|
56734
|
+
imageResolveContext: resolvedImageResolveContext
|
|
56735
|
+
}) });
|
|
56024
56736
|
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56025
56737
|
outcome: "overrideWithoutDefinition",
|
|
56026
56738
|
override: true,
|
|
@@ -56036,8 +56748,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56036
56748
|
dbCustomBlocksCount: customBlocks?.length ?? 0
|
|
56037
56749
|
});
|
|
56038
56750
|
} catch {}
|
|
56039
|
-
|
|
56040
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56751
|
+
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" })), {
|
|
56041
56752
|
outcome: "missingDefinition",
|
|
56042
56753
|
override: false
|
|
56043
56754
|
});
|
|
@@ -56052,8 +56763,7 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56052
56763
|
manifestKeys: Object.keys(definition.manifest ?? {}).sort()
|
|
56053
56764
|
});
|
|
56054
56765
|
} catch {}
|
|
56055
|
-
|
|
56056
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56766
|
+
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" })), {
|
|
56057
56767
|
outcome: "missingLayout",
|
|
56058
56768
|
override: false
|
|
56059
56769
|
});
|
|
@@ -56063,39 +56773,24 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56063
56773
|
if (block.presentationAdmission.kind === "editor") {
|
|
56064
56774
|
const draft = validateManifestDraft(compileManifestContentContract(definition.manifest), content);
|
|
56065
56775
|
if (draft.kind === "previewable") data = draft.data;
|
|
56066
|
-
else {
|
|
56067
|
-
|
|
56068
|
-
|
|
56069
|
-
|
|
56070
|
-
override: false
|
|
56071
|
-
});
|
|
56072
|
-
}
|
|
56776
|
+
else return finish(withBlockWrapper(diagnostic(`Unable to render block kind "${block.kind}".`, { outcome: "invalidPlaceholder" })), {
|
|
56777
|
+
outcome: "invalidPlaceholder",
|
|
56778
|
+
override: false
|
|
56779
|
+
});
|
|
56073
56780
|
}
|
|
56074
56781
|
try {
|
|
56075
56782
|
const routes = routeMap ?? dataContext?.routes;
|
|
56076
56783
|
const hydratedContent = routes ? hydrateManifestLinks(definition.manifest, data, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : data;
|
|
56077
56784
|
const resolved = resolveBlockData$1(block.id, definition.dataLoaders, dataContext, routes);
|
|
56078
56785
|
const hasResolvedData = !!resolved && typeof resolved === "object" && Object.keys(resolved).length > 0;
|
|
56079
|
-
const baseProps = {
|
|
56786
|
+
const baseProps = buildBlockOverrideProps({
|
|
56787
|
+
block,
|
|
56080
56788
|
content: hydratedContent,
|
|
56081
|
-
...overrideThemeProps(themeResources),
|
|
56082
56789
|
data: resolved,
|
|
56083
|
-
|
|
56084
|
-
|
|
56085
|
-
|
|
56086
|
-
|
|
56087
|
-
stage: dataContext?.previewStage,
|
|
56088
|
-
occurrenceContext: dataContext?.occurrenceContext ?? null,
|
|
56089
|
-
entrySubrouteContext: dataContext?.entrySubrouteContext ?? null,
|
|
56090
|
-
contentEntry: dataContext?.contentEntry ?? null,
|
|
56091
|
-
pageSurface: dataContext?.pageSurface ?? null,
|
|
56092
|
-
subject: dataContext?.subject ?? null,
|
|
56093
|
-
supabaseUrl: dataContext?.supabaseUrl,
|
|
56094
|
-
imageResolveContext: resolvedImageResolveContext,
|
|
56095
|
-
blockId: block.id ?? null,
|
|
56096
|
-
blockKind: block.kind
|
|
56097
|
-
};
|
|
56098
|
-
baseProps.bookingTimeZone = resolveBookingTimeZone(dataContext);
|
|
56790
|
+
themeResources,
|
|
56791
|
+
dataContext,
|
|
56792
|
+
imageResolveContext: resolvedImageResolveContext
|
|
56793
|
+
});
|
|
56099
56794
|
let rendered;
|
|
56100
56795
|
if (OverrideComponent) rendered = /* @__PURE__ */ jsx(OverrideComponent, { ...baseProps });
|
|
56101
56796
|
else {
|
|
@@ -56142,39 +56837,11 @@ const MemoBlockSection = React$1.memo(function MemoBlockSection({ themeResources
|
|
|
56142
56837
|
hasResolvedData
|
|
56143
56838
|
});
|
|
56144
56839
|
} catch {
|
|
56145
|
-
|
|
56146
|
-
return finish(wrapBlock && block.id ? /* @__PURE__ */ jsx(Fragment, { children: wrapBlock(block.id, rendered) }) : /* @__PURE__ */ jsx(Fragment, { children: rendered }), {
|
|
56840
|
+
return finish(withBlockWrapper(diagnostic(block.presentationAdmission.kind === "editor" ? `Unable to render block kind "${block.kind}".` : `Invalid content for block kind "${block.kind}".`, { outcome: "exception" })), {
|
|
56147
56841
|
outcome: "exception",
|
|
56148
56842
|
override: false
|
|
56149
56843
|
});
|
|
56150
56844
|
}
|
|
56151
|
-
}, (prev, next) => {
|
|
56152
|
-
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;
|
|
56153
|
-
});
|
|
56154
|
-
function fallback(message) {
|
|
56155
|
-
return /* @__PURE__ */ jsx("div", {
|
|
56156
|
-
className: "rb-p-6 rb-caption",
|
|
56157
|
-
style: textColorStyle("mutedText"),
|
|
56158
|
-
children: message
|
|
56159
|
-
});
|
|
56160
|
-
}
|
|
56161
|
-
function shouldDebugCustomBlocks() {
|
|
56162
|
-
try {
|
|
56163
|
-
return globalThis.process?.env?.DEBUG_CUSTOM_BLOCKS === "1";
|
|
56164
|
-
} catch {
|
|
56165
|
-
return false;
|
|
56166
|
-
}
|
|
56167
|
-
}
|
|
56168
|
-
/**
|
|
56169
|
-
* Resolves block content based on stage priority.
|
|
56170
|
-
* For draft/preview: draftContent takes priority over content.
|
|
56171
|
-
* For published: content is the source of truth.
|
|
56172
|
-
* Returns empty object if no content exists (validator will handle placeholders if needed).
|
|
56173
|
-
*/
|
|
56174
|
-
function getRenderableContent(block, previewStage = "published") {
|
|
56175
|
-
const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
|
|
56176
|
-
if (!isObjectRecord$1(source)) return {};
|
|
56177
|
-
return source;
|
|
56178
56845
|
}
|
|
56179
56846
|
//#endregion
|
|
56180
56847
|
//#region ../theme-core/src/generated/containerResponsiveThemeCss.ts
|
|
@@ -65100,6 +65767,21 @@ const containerResponsiveThemeCss = `/*
|
|
|
65100
65767
|
outline-offset: 2px;
|
|
65101
65768
|
}
|
|
65102
65769
|
|
|
65770
|
+
/* Event titles always link to the event page, so a card or row whose CTA is
|
|
65771
|
+
hidden (sold out, closed) is never a dead end (issue 1372). The link inherits
|
|
65772
|
+
the heading's colour and only reveals itself on hover or keyboard focus.
|
|
65773
|
+
Shared by EventCard, EventCompactRow and the offerings-preview course cards via \`.event-title-link\`. */
|
|
65774
|
+
:where(.theme-scope) .event-title-link {
|
|
65775
|
+
color: inherit;
|
|
65776
|
+
text-decoration: none;
|
|
65777
|
+
}
|
|
65778
|
+
|
|
65779
|
+
:where(.theme-scope) .event-title-link:hover,
|
|
65780
|
+
:where(.theme-scope) .event-title-link:focus-visible {
|
|
65781
|
+
text-decoration: underline;
|
|
65782
|
+
text-underline-offset: 0.12em;
|
|
65783
|
+
}
|
|
65784
|
+
|
|
65103
65785
|
/* =============================================================================
|
|
65104
65786
|
* Block: Carousel Navigation (Phase 3 client component migration)
|
|
65105
65787
|
* =============================================================================
|
|
@@ -73155,6 +73837,83 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
73155
73837
|
errors: ["validation:invalid_input", "server:internal_error"]
|
|
73156
73838
|
}
|
|
73157
73839
|
});
|
|
73840
|
+
//#endregion
|
|
73841
|
+
//#region ../api/src/sdkMediaEndpointDefinitions.ts
|
|
73842
|
+
const SDK_MEDIA_ENDPOINT_DEFINITIONS = {
|
|
73843
|
+
sdkMediaExists: {
|
|
73844
|
+
path: "/sdk/{siteId}/media/exists",
|
|
73845
|
+
method: "POST",
|
|
73846
|
+
errors: ["validation:invalid_input"],
|
|
73847
|
+
auth: "service",
|
|
73848
|
+
responseKind: "json"
|
|
73849
|
+
},
|
|
73850
|
+
sdkMediaFindByChecksum: {
|
|
73851
|
+
path: "/sdk/{siteId}/media/find-by-checksum",
|
|
73852
|
+
method: "POST",
|
|
73853
|
+
errors: ["server:internal_error"],
|
|
73854
|
+
auth: "service",
|
|
73855
|
+
responseKind: "json"
|
|
73856
|
+
},
|
|
73857
|
+
sdkMediaResolve: {
|
|
73858
|
+
path: "/sdk/{siteId}/media/resolve",
|
|
73859
|
+
method: "POST",
|
|
73860
|
+
errors: ["server:internal_error"],
|
|
73861
|
+
auth: "service",
|
|
73862
|
+
responseKind: "json"
|
|
73863
|
+
},
|
|
73864
|
+
sdkMediaList: {
|
|
73865
|
+
path: "/sdk/{siteId}/media",
|
|
73866
|
+
method: "GET",
|
|
73867
|
+
auth: "service",
|
|
73868
|
+
responseKind: "json"
|
|
73869
|
+
},
|
|
73870
|
+
sdkMediaDownload: {
|
|
73871
|
+
path: "/sdk/{siteId}/media/{identifier}/download",
|
|
73872
|
+
method: "GET",
|
|
73873
|
+
auth: "service",
|
|
73874
|
+
responseKind: "json"
|
|
73875
|
+
},
|
|
73876
|
+
sdkMediaDeletePreview: {
|
|
73877
|
+
path: "/sdk/{siteId}/media/{identifier}/delete-preview",
|
|
73878
|
+
method: "POST",
|
|
73879
|
+
auth: "service",
|
|
73880
|
+
responseKind: "json"
|
|
73881
|
+
},
|
|
73882
|
+
sdkMediaDeleteApply: {
|
|
73883
|
+
path: "/sdk/{siteId}/media/{identifier}",
|
|
73884
|
+
method: "DELETE",
|
|
73885
|
+
auth: "service",
|
|
73886
|
+
responseKind: "json"
|
|
73887
|
+
},
|
|
73888
|
+
sdkMediaUploadUrl: {
|
|
73889
|
+
path: "/sdk/{siteId}/media/upload-url",
|
|
73890
|
+
method: "POST",
|
|
73891
|
+
errors: [
|
|
73892
|
+
"resource:not_found",
|
|
73893
|
+
"validation:invalid_input",
|
|
73894
|
+
"resource:conflict",
|
|
73895
|
+
"validation:payload_too_large",
|
|
73896
|
+
"billing:plan_limit_exceeded",
|
|
73897
|
+
"server:internal_error"
|
|
73898
|
+
],
|
|
73899
|
+
auth: "service",
|
|
73900
|
+
responseKind: "json"
|
|
73901
|
+
},
|
|
73902
|
+
sdkMediaUploadFinalize: {
|
|
73903
|
+
path: "/sdk/{siteId}/media/upload",
|
|
73904
|
+
method: "POST",
|
|
73905
|
+
errors: [
|
|
73906
|
+
"resource:not_found",
|
|
73907
|
+
"validation:invalid_input",
|
|
73908
|
+
"resource:conflict",
|
|
73909
|
+
"validation:payload_too_large",
|
|
73910
|
+
"billing:plan_limit_exceeded",
|
|
73911
|
+
"server:internal_error"
|
|
73912
|
+
],
|
|
73913
|
+
auth: "service",
|
|
73914
|
+
responseKind: "json"
|
|
73915
|
+
}
|
|
73916
|
+
};
|
|
73158
73917
|
({
|
|
73159
73918
|
...ACCOUNT_ENDPOINT_DEFINITIONS,
|
|
73160
73919
|
...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
|
|
@@ -73163,7 +73922,7 @@ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
73163
73922
|
...NEWSLETTER_ENDPOINT_DEFINITIONS,
|
|
73164
73923
|
...APPOINTMENT_ENDPOINT_DEFINITIONS,
|
|
73165
73924
|
...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
|
|
73166
|
-
}, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.workflowRunById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.routeById, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.formById, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEventFilters, { ...PUBLIC_WHOLE_SERIES_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicTeamMembers, TAG_TEMPLATES.publicWholeSeriesOfferings, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicPasses, TAG_TEMPLATES.publicMemberships, TAG_TEMPLATES.publicProducts, TAG_TEMPLATES.publicShop, TAG_TEMPLATES.publicCheckout, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, { ...EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS }, { ...SDK_ENDPOINT_DEFINITIONS }, { ...SDK_ENTRY_READ_ENDPOINTS };
|
|
73925
|
+
}, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.workflowRunById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.routeById, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.formById, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEventFilters, { ...PUBLIC_WHOLE_SERIES_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicTeamMembers, TAG_TEMPLATES.publicWholeSeriesOfferings, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicPasses, TAG_TEMPLATES.publicMemberships, TAG_TEMPLATES.publicProducts, TAG_TEMPLATES.publicShop, TAG_TEMPLATES.publicCheckout, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, { ...EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS }, { ...SDK_MEDIA_ENDPOINT_DEFINITIONS }, { ...SDK_ENDPOINT_DEFINITIONS }, { ...SDK_ENTRY_READ_ENDPOINTS };
|
|
73167
73926
|
z$1.object({ refreshToken: z$1.string().min(1).max(4096) }).strict();
|
|
73168
73927
|
z$1.object({
|
|
73169
73928
|
accessToken: z$1.string().min(1).max(4096),
|
|
@@ -73816,6 +74575,50 @@ z$1.object({
|
|
|
73816
74575
|
title: z$1.string(),
|
|
73817
74576
|
slug: z$1.string().min(1)
|
|
73818
74577
|
});
|
|
74578
|
+
z$1.object({
|
|
74579
|
+
signedUrl: z$1.string().url(),
|
|
74580
|
+
token: z$1.string().min(1),
|
|
74581
|
+
storagePath: z$1.string().min(1)
|
|
74582
|
+
}).strict();
|
|
74583
|
+
const backupManifestSchema = z$1.object({
|
|
74584
|
+
siteId: z$1.string().min(1),
|
|
74585
|
+
siteSlug: z$1.string().min(1),
|
|
74586
|
+
siteTitle: z$1.string(),
|
|
74587
|
+
exportedAt: z$1.string().min(1),
|
|
74588
|
+
tableCount: z$1.number().int().nonnegative(),
|
|
74589
|
+
totalRecords: z$1.number().int().nonnegative(),
|
|
74590
|
+
pageCount: z$1.number().int().nonnegative(),
|
|
74591
|
+
blockCount: z$1.number().int().nonnegative(),
|
|
74592
|
+
contentEntryCount: z$1.number().int().nonnegative(),
|
|
74593
|
+
formCount: z$1.number().int().nonnegative(),
|
|
74594
|
+
navigationMenuCount: z$1.number().int().nonnegative(),
|
|
74595
|
+
mediaCount: z$1.number().int().nonnegative(),
|
|
74596
|
+
mediaBytes: z$1.number().int().nonnegative()
|
|
74597
|
+
}).strict();
|
|
74598
|
+
z$1.object({
|
|
74599
|
+
ok: z$1.literal(true),
|
|
74600
|
+
manifest: backupManifestSchema,
|
|
74601
|
+
storagePath: z$1.string().min(1)
|
|
74602
|
+
}).strict();
|
|
74603
|
+
z$1.object({
|
|
74604
|
+
ok: z$1.literal(true),
|
|
74605
|
+
siteId: brandedIdSchema(parseSiteId, "site id"),
|
|
74606
|
+
slug: z$1.string().min(1),
|
|
74607
|
+
title: z$1.string(),
|
|
74608
|
+
counts: z$1.record(z$1.string(), z$1.object({
|
|
74609
|
+
created: z$1.number().int().nonnegative(),
|
|
74610
|
+
updated: z$1.number().int().nonnegative(),
|
|
74611
|
+
skipped: z$1.number().int().nonnegative(),
|
|
74612
|
+
failed: z$1.number().int().nonnegative()
|
|
74613
|
+
}).strict()),
|
|
74614
|
+
warnings: z$1.array(z$1.string()),
|
|
74615
|
+
errors: z$1.array(z$1.object({
|
|
74616
|
+
table: z$1.string(),
|
|
74617
|
+
id: z$1.string(),
|
|
74618
|
+
error: z$1.string()
|
|
74619
|
+
}).strict())
|
|
74620
|
+
}).strict();
|
|
74621
|
+
z$1.object({ deleted: z$1.boolean() }).strict();
|
|
73819
74622
|
const apiKeyIdSchema = brandedIdSchema(parseApiKeyId, "API key id");
|
|
73820
74623
|
const standardKeyIdsSchema = z$1.object({
|
|
73821
74624
|
live: apiKeyIdSchema.optional(),
|