@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.
Files changed (160) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/dist/{PreviewEditorSidebar-BU5ZlhQR.mjs → PreviewEditorSidebar-Bn6cONPa.mjs} +8 -8
  3. package/dist/{PreviewEditorUI-9XkHL-bC.mjs → PreviewEditorUI-LAwXZPSx.mjs} +3 -3
  4. package/dist/{PreviewQueryProvider-wbEpXryy.mjs → PreviewQueryProvider-ztQUdsNo.mjs} +1 -1
  5. package/dist/{SdkPreviewModeRuntime-CnWmj-2B.mjs → SdkPreviewModeRuntime-BbbGsI_v.mjs} +596 -164
  6. package/dist/{SiteChromeCustomizeContext-B6d5zkog.mjs → SiteChromeCustomizeContext-BbkNSt5a.mjs} +2888 -1404
  7. package/dist/_dts/ai/src/contracts/proposals.d.ts +12 -298
  8. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +21 -14
  9. package/dist/_dts/ai/src/designer/themePatch.d.ts +5 -10
  10. package/dist/_dts/api/src/accessAdmin.d.ts +1 -9
  11. package/dist/_dts/api/src/accountApi.d.ts +49 -2
  12. package/dist/_dts/api/src/contentRuntime.d.ts +1 -4
  13. package/dist/_dts/api/src/domainConnection.d.ts +41 -0
  14. package/dist/_dts/api/src/endpoints.d.ts +194 -181
  15. package/dist/_dts/api/src/index.d.ts +4 -3
  16. package/dist/_dts/api/src/media.d.ts +19 -2
  17. package/dist/_dts/api/src/sdkContracts.d.ts +2 -15
  18. package/dist/_dts/api/src/sdkMediaContracts.d.ts +40 -0
  19. package/dist/_dts/api/src/sdkMediaEndpointDefinitions.d.ts +61 -0
  20. package/dist/_dts/api/src/sdkThemeWire.d.ts +15 -4
  21. package/dist/_dts/api/src/siteInfrastructure.d.ts +17 -23
  22. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +7 -21
  23. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +27 -3
  24. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +37 -17
  25. package/dist/_dts/api/src/types.d.ts +1 -1
  26. package/dist/_dts/blocks/src/PageRenderer.d.ts +2 -2
  27. package/dist/_dts/blocks/src/index.d.ts +1 -1
  28. package/dist/_dts/blocks/src/pageRenderAdmission.d.ts +9 -0
  29. package/dist/_dts/blocks/src/pageRendererBlockSupport.d.ts +75 -0
  30. package/dist/_dts/blocks/src/pageRendererBoundaries.d.ts +2 -0
  31. package/dist/_dts/blocks/src/server.d.ts +1 -1
  32. package/dist/_dts/blocks/src/system/blocks/event-registration.d.ts +1 -1
  33. package/dist/_dts/blocks/src/system/runtime/api/client.d.ts +2 -1
  34. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.d.ts +3 -3
  35. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.state.d.ts +34 -22
  36. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/ErrorStep.d.ts +3 -5
  37. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +2 -0
  38. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +1 -1
  39. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +2 -0
  40. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorPresentation.d.ts +5 -0
  41. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +25 -0
  42. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationWizardContext.d.ts +4 -0
  43. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +7 -3
  44. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +2 -0
  45. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +5 -2
  46. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useWaitlistClaimRestoration.d.ts +2 -0
  47. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/capacity.d.ts +164 -0
  48. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/cta.d.ts +29 -0
  49. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/dates.d.ts +47 -0
  50. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/index.d.ts +14 -0
  51. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/people.d.ts +6 -0
  52. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/pricing.d.ts +31 -0
  53. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/OfferingTitle.d.ts +11 -0
  54. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekAgendaView.d.ts +1 -1
  55. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +1 -1
  56. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/index.d.ts +3 -1
  57. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/listingLayout.d.ts +23 -0
  58. package/dist/_dts/commerce/src/purchase/event-registration-errors.d.ts +57 -0
  59. package/dist/_dts/commerce/src/purchase/index.d.ts +1 -0
  60. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +8 -40
  61. package/dist/_dts/db/src/index.d.ts +1 -0
  62. package/dist/_dts/db/src/utils/postgrestFilter.d.ts +2 -0
  63. package/dist/_dts/media-core/src/bulkDeletePlanning.d.ts +4 -0
  64. package/dist/_dts/media-core/src/fileDomain.d.ts +3 -0
  65. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +1 -0
  66. package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +1 -0
  67. package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +3 -2
  68. package/dist/_dts/preview-next/src/client/preview/siteChromeBrandPreferences.d.ts +1 -1
  69. package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlHistoryState.d.ts +2 -0
  70. package/dist/_dts/preview-next/src/client/preview/siteChromeQuickControlSaveRequest.d.ts +51 -0
  71. package/dist/_dts/preview-next/src/client/preview/siteChromeSavePlan.d.ts +24 -2
  72. package/dist/_dts/preview-next/src/client/preview/siteStylePreviewPlanner.d.ts +23 -1
  73. package/dist/_dts/preview-next/src/client/preview/usePersistedHeaderPreferences.d.ts +8 -0
  74. package/dist/_dts/sdk/src/public-api/contracts.d.ts +2 -1
  75. package/dist/_dts/sdk/src/version.d.ts +1 -1
  76. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  77. package/dist/_dts/theme-core/src/gradients/types.d.ts +5 -0
  78. package/dist/_dts/theme-core/src/header/interactionPalette.d.ts +25 -2
  79. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +3 -3
  80. package/dist/_dts/theme-core/src/schema.d.ts +12 -11
  81. package/dist/_dts/theme-core/src/site-styles/compilationPlan.d.ts +43 -3
  82. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +10 -0
  83. package/dist/_dts/theme-core/src/site-styles/headerBrandPresentation.d.ts +63 -0
  84. package/dist/_dts/theme-core/src/site-styles/headerBytesComparison.d.ts +30 -0
  85. package/dist/_dts/theme-core/src/site-styles/headerInteractionPalette.d.ts +50 -1
  86. package/dist/_dts/theme-core/src/site-styles/headerLayoutCatalog.d.ts +48 -9
  87. package/dist/_dts/theme-core/src/site-styles/headerPreferences.d.ts +162 -0
  88. package/dist/_dts/theme-core/src/site-styles/headerPreferencesPersistence.d.ts +73 -0
  89. package/dist/_dts/theme-core/src/site-styles/headerRecompile.d.ts +118 -0
  90. package/dist/_dts/theme-core/src/site-styles/headerStyleCompiler.d.ts +10 -1
  91. package/dist/_dts/theme-core/src/site-styles/headerStyleDefaults.d.ts +1 -1
  92. package/dist/_dts/theme-core/src/site-styles/headerStyleRecipe.d.ts +7 -0
  93. package/dist/_dts/theme-core/src/site-styles/index.d.ts +5 -0
  94. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +1 -1
  95. package/dist/_dts/theme-core/src/site-styles/siteChromeBackgrounds.d.ts +20 -1
  96. package/dist/_dts/theme-core/src/site-styles/siteChromeNavPreferences.d.ts +43 -2
  97. package/dist/client/{BookingConsentAgreement-DCch21a6.mjs → BookingConsentAgreement--_fUCZe-.mjs} +2 -2
  98. package/dist/client/{DiscountCodeDisclosure-DIj1T59K.mjs → DiscountCodeDisclosure-BMipSoDJ.mjs} +1 -1
  99. package/dist/client/{EventCombined.client-BWVNlXTx.mjs → EventCombined.client-CwJOxn7A.mjs} +154 -178
  100. package/dist/client/{EventListing.client-DVFCDe3i.mjs → EventListing.client-eVwiylPW.mjs} +2 -2
  101. package/dist/client/{LeafletStaticMap-BY5mIHw9.mjs → LeafletStaticMap-IKenna5u.mjs} +1 -1
  102. package/dist/client/{accordion-CK29E8rG.mjs → accordion-MGAO3PuE.mjs} +1 -1
  103. package/dist/client/{bookingForm-DFvnet-N.mjs → bookingForm-CudlQPUu.mjs} +5 -5
  104. package/dist/client/bookings.mjs +418 -79
  105. package/dist/client/capacity-BW00PO7s.mjs +271 -0
  106. package/dist/client/{carousel-BVU5NQlb.mjs → carousel-BrbGJ2Jl.mjs} +1 -1
  107. package/dist/client/{cart-BNItcZVa.mjs → cart-Bq5eU0xZ.mjs} +3 -3
  108. package/dist/client/{checkout-Bu6iCdkP.mjs → checkout-BAFVRdrK.mjs} +3 -3
  109. package/dist/client/client.mjs +2919 -1918
  110. package/dist/client/{context-BImarOAq.mjs → context-DCkiHVdD.mjs} +8 -5
  111. package/dist/client/dates-2RPXHQy4.mjs +264 -0
  112. package/dist/client/{eventCalendar-DS2h-UXD.mjs → eventCalendar-B0O-hPF7.mjs} +10 -17
  113. package/dist/client/{eventCombined-Drt4Oq9p.mjs → eventCombined-DCkDaoDI.mjs} +3 -3
  114. package/dist/client/{eventDetails-Bl2vWgTX.mjs → eventDetails-D8GG2dnD.mjs} +5 -3
  115. package/dist/client/{eventListing-CS7PHY7v.mjs → eventListing-3ssV_bxz.mjs} +3 -3
  116. package/dist/client/{eventRegistration-D-j8eNy2.mjs → eventRegistration-bPQo3kVw.mjs} +562 -309
  117. package/dist/client/{form-RMpoLTyr.mjs → form-qMv8RDkk.mjs} +2 -2
  118. package/dist/client/{headerSection-BYE3Lb3k.mjs → headerSection-CH0Sc5hX.mjs} +2 -2
  119. package/dist/client/hooks.mjs +289 -32
  120. package/dist/client/{locationMap-BxSr7Kkh.mjs → locationMap-0_xlBnNI.mjs} +2 -2
  121. package/dist/client/{newsletterForm-DerbXwFC.mjs → newsletterForm-NUW7m9RI.mjs} +2 -2
  122. package/dist/client/{productDetail-4SZCLhAf.mjs → productDetail-FBEOM7jW.mjs} +3 -3
  123. package/dist/client/{productList-D7AP4GNq.mjs → productList-R0OSGhnn.mjs} +3 -3
  124. package/dist/client/{propsDecoder-pzBeCh14.mjs → propsDecoder-pYvx_Srl.mjs} +88 -380
  125. package/dist/client/rendering/client.mjs +2616 -1637
  126. package/dist/client/rendering/islands.mjs +17 -17
  127. package/dist/client/rendering.mjs +2710 -1702
  128. package/dist/client/{scrollToBlock-CduAKf8T.mjs → scrollToBlock-ByQv96M2.mjs} +1 -1
  129. package/dist/client/{shop-Cp5p_kGe.mjs → shop-RUG9DmRE.mjs} +6 -6
  130. package/dist/client/{shop-commerce-DicXYaYX.mjs → shop-commerce-B76Jz23B.mjs} +5 -5
  131. package/dist/client/{shop-commerce-store-CGWnpY4K.mjs → shop-commerce-store-QuQNdDsD.mjs} +1 -1
  132. package/dist/client/{shopShared-BDf9ljkm.mjs → shopShared-vXU1omwJ.mjs} +3 -3
  133. package/dist/client/{useEventFilters-DeeyGYuk.mjs → useEventFilters-ByuU6HLA.mjs} +33 -18
  134. package/dist/preview-next/before-render.mjs +79 -51
  135. package/dist/preview-next/client/runtime.mjs +4 -4
  136. package/dist/preview-next/keepAlive.mjs +79 -51
  137. package/dist/preview-next/middleware.mjs +79 -51
  138. package/dist/{previewAuthStore-DbqBol0V.mjs → previewAuthStore-L_cKW8Sb.mjs} +79 -51
  139. package/dist/{sdk-runtime-19MO27F0.mjs → sdk-runtime-sUMr5d70.mjs} +2 -2
  140. package/dist/{separator-DaPLtJ1f.mjs → separator-BIHkie5i.mjs} +1 -1
  141. package/dist/server/components.mjs +2077 -1274
  142. package/dist/server/config-validation.mjs +289 -32
  143. package/dist/server/config.mjs +289 -32
  144. package/dist/server/data.mjs +289 -32
  145. package/dist/server/index.mjs +80 -52
  146. package/dist/server/next.mjs +1822 -1111
  147. package/dist/server/page-converter.mjs +167 -31
  148. package/dist/server/prebuild.mjs +1 -1
  149. package/dist/server/rendering/server.mjs +1988 -1286
  150. package/dist/server/rendering.mjs +1943 -1182
  151. package/dist/server/routing.mjs +351 -86
  152. package/dist/server/server.mjs +293 -128
  153. package/dist/server/theme-bridge.mjs +52 -8
  154. package/dist/server/theme.mjs +27 -8
  155. package/dist/styles/index.css +15 -0
  156. package/package.json +1 -1
  157. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCapacity.d.ts +0 -48
  158. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/pricing.d.ts +0 -17
  159. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/utils.d.ts +0 -100
  160. /package/dist/_dts/blocks/src/system/runtime/nodes/events/{shared → domain}/destination.d.ts +0 -0
@@ -0,0 +1,271 @@
1
+ //#region ../blocks/src/system/runtime/nodes/events/domain/capacity.ts
2
+ /**
3
+ * Compute remaining spots for an event from `availableSpots` with a fallback
4
+ * to `capacity - registeredCount`. Returns `{ available: null, isFull: false }`
5
+ * for uncapped events so call sites can render no capacity indicator at all.
6
+ *
7
+ * The single source of truth for capacity math across event cards, agenda
8
+ * rows, and timetable cells — keep wording variations in the call sites.
9
+ */
10
+ function getEventAvailability(event) {
11
+ if (event.capacity == null) return {
12
+ available: null,
13
+ isFull: false
14
+ };
15
+ const available = event.availableSpots ?? event.capacity - event.registeredCount;
16
+ if (available <= 0) return {
17
+ available: 0,
18
+ isFull: true
19
+ };
20
+ return {
21
+ available,
22
+ isFull: false
23
+ };
24
+ }
25
+ /**
26
+ * Pick the capacity row a booking decision should read: the selected
27
+ * attendance mode's availability when the visitor has chosen one and the
28
+ * event publishes it, otherwise the event-level aggregate.
29
+ *
30
+ * The mode row is used wholesale. A mode with `capacity: null` on a capped
31
+ * event is uncapped *by design*: the registration write path applies the
32
+ * same rule via `getEffectiveOccurrenceCapacityForAttendanceMode`, so falling
33
+ * back to the event's `availableSpots` here would block bookings the server
34
+ * accepts. (Waitlist claims are still occurrence-scoped; see issue 1377.)
35
+ */
36
+ function selectEventCapacitySource(event, attendanceMode) {
37
+ if (!attendanceMode) return event;
38
+ return (event.attendanceModes ?? []).find((mode) => mode.mode === attendanceMode) ?? event;
39
+ }
40
+ /**
41
+ * Whether the visitor's next step is the waitlist: the source is full *and*
42
+ * a waitlist is open. The one place this is decided — cards, rows, the
43
+ * wizard's steps and its action bar all route through here so they agree,
44
+ * including for producers that omit `availableSpots` and rely on the
45
+ * `capacity - registeredCount` fallback.
46
+ */
47
+ function canJoinEventWaitlist(event, options) {
48
+ const source = selectEventCapacitySource(event, options?.attendanceMode);
49
+ return getEventAvailability(source).isFull && source.waitlistEnabled === true;
50
+ }
51
+ /**
52
+ * Remaining places at or below which an event reads as scarce and the exact
53
+ * count is worth showing. Above it, no number is shown at all: "147 places
54
+ * left" is not something a visitor acts on, and it is the figure most likely
55
+ * to be stale on a cached page.
56
+ *
57
+ * max( min(3, ceil(capacity * 0.25)), ceil(capacity * 0.05) )
58
+ *
59
+ * The 25% arm stops small groups shouting early (3 of 4 is not urgency); the
60
+ * 5% floor stops large events staying silent until three remain.
61
+ * capacity 4 -> 1, 12 -> 3, 40 -> 3, 100 -> 5, 200 -> 10.
62
+ */
63
+ function lowCapacityThreshold(capacity) {
64
+ return Math.max(Math.min(3, Math.ceil(capacity * .25)), Math.ceil(capacity * .05));
65
+ }
66
+ function getRemainingPlaces(source) {
67
+ const { available, isFull } = getEventAvailability(source);
68
+ if (available === null || source.capacity === null) return { kind: "unlimited" };
69
+ if (isFull) return {
70
+ kind: "full",
71
+ waitlist: canJoinEventWaitlist(source)
72
+ };
73
+ if (available <= lowCapacityThreshold(source.capacity)) return {
74
+ kind: "low",
75
+ remaining: available
76
+ };
77
+ return { kind: "open" };
78
+ }
79
+ /**
80
+ * The one place a remaining-places count becomes text. Returns `null` when
81
+ * nothing should be shown: uncapped events, and capped events above the
82
+ * scarcity threshold. Surfaces that need their own full-state wording pass
83
+ * it in rather than re-deriving the count.
84
+ */
85
+ function formatRemainingPlaces(source, wording = {}) {
86
+ const remaining = getRemainingPlaces(source);
87
+ switch (remaining.kind) {
88
+ case "unlimited":
89
+ case "open": return null;
90
+ case "low": return formatPlacesLeft(remaining.remaining, wording.compact === true);
91
+ case "full": return remaining.waitlist ? wording.waitlist ?? "Waitlist" : wording.full ?? "Full";
92
+ }
93
+ }
94
+ function formatPlacesLeft(count, compact) {
95
+ if (compact) return `${count} left`;
96
+ return count === 1 ? "1 place left" : `${count} places left`;
97
+ }
98
+ function formatTicketTypeRemaining(remainingCapacity) {
99
+ if (remainingCapacity === null || remainingCapacity > 3) return null;
100
+ if (remainingCapacity <= 0) return "None left for this ticket type";
101
+ return `${formatPlacesLeft(remainingCapacity, false)} for this ticket type`;
102
+ }
103
+ /** Total capacity as copy ("12 places"), for detail surfaces that state the size of an event. */
104
+ function formatTotalCapacity(capacity) {
105
+ if (capacity === null) return null;
106
+ return capacity === 1 ? "1 place" : `${capacity} places`;
107
+ }
108
+ function getEventAvailabilityStatus(event) {
109
+ const source = selectMostOpenCapacitySource(event);
110
+ const remaining = getRemainingPlaces(source);
111
+ const label = formatEventRemainingPlaces(event, { compact: true });
112
+ switch (remaining.kind) {
113
+ case "unlimited": return {
114
+ status: "available",
115
+ available: null,
116
+ isFull: false,
117
+ label
118
+ };
119
+ case "open": return {
120
+ status: "available",
121
+ available: getEventAvailability(source).available,
122
+ isFull: false,
123
+ label
124
+ };
125
+ case "low": return {
126
+ status: "filling",
127
+ available: remaining.remaining,
128
+ isFull: false,
129
+ label
130
+ };
131
+ case "full": return {
132
+ status: "waitlist",
133
+ available: 0,
134
+ isFull: true,
135
+ label
136
+ };
137
+ }
138
+ }
139
+ /**
140
+ * Which capacity figure wins when they disagree.
141
+ *
142
+ * For a hybrid event that publishes both attendance-mode rows, **the mode
143
+ * rows are authoritative and the event-level aggregate is only a summary**.
144
+ * The registration write path admits or refuses per mode
145
+ * (`getEffectiveOccurrenceCapacityForAttendanceMode` on the server), so the
146
+ * aggregate can read full while a mode still has room, and can read open
147
+ * while the mode the visitor wants is full. Believing the aggregate on a
148
+ * card is how a "places available" event turns into a dead end at step two.
149
+ *
150
+ * Everything else — single-mode events, events that publish no rows, and the
151
+ * aggregate-only helpers {@link getEventAvailability} /
152
+ * {@link getRemainingPlaces} that existing callers rely on — keeps reading
153
+ * the aggregate, unchanged.
154
+ */
155
+ function findHybridModeRows(event) {
156
+ if (event.deliveryMode !== "hybrid") return null;
157
+ const rows = event.attendanceModes ?? [];
158
+ const inPerson = rows.find((row) => row.mode === "in_person");
159
+ const online = rows.find((row) => row.mode === "online");
160
+ return inPerson && online ? {
161
+ inPerson,
162
+ online
163
+ } : null;
164
+ }
165
+ /** Openness rank; higher means more room. A full mode with a waitlist beats one without. */
166
+ function remainingPlacesRank(remaining) {
167
+ switch (remaining.kind) {
168
+ case "unlimited": return 4;
169
+ case "open": return 3;
170
+ case "low": return 2;
171
+ case "full": return remaining.waitlist ? 1 : 0;
172
+ }
173
+ }
174
+ function isMoreOpen(candidate, than) {
175
+ const rank = remainingPlacesRank(candidate);
176
+ const otherRank = remainingPlacesRank(than);
177
+ if (rank !== otherRank) return rank > otherRank;
178
+ return candidate.kind === "low" && than.kind === "low" ? candidate.remaining > than.remaining : false;
179
+ }
180
+ /** True when `candidate` has less room left than `than`. */
181
+ function isScarcer(candidate, than) {
182
+ return isMoreOpen(than, candidate);
183
+ }
184
+ /**
185
+ * The capacity row a whole-event decision should read: the most open
186
+ * attendance mode of a hybrid event, otherwise the aggregate. A hybrid event
187
+ * is sold out only when both of its modes are, and its waitlist is reachable
188
+ * when either full mode has one open.
189
+ */
190
+ function selectMostOpenCapacitySource(event) {
191
+ const rows = findHybridModeRows(event);
192
+ if (!rows) return event;
193
+ return isMoreOpen(getRemainingPlaces(rows.online), getRemainingPlaces(rows.inPerson)) ? rows.online : rows.inPerson;
194
+ }
195
+ /** True when no attendance mode has room. Hybrid-aware; use this for sold-out decisions. */
196
+ function isEventFull(event) {
197
+ return getEventAvailability(selectMostOpenCapacitySource(event)).isFull;
198
+ }
199
+ /**
200
+ * True when the event is full on every mode and at least one of those full
201
+ * modes has a waitlist open. An event with a mode still bookable is not a
202
+ * waitlist prospect: it should send the visitor to that mode instead.
203
+ */
204
+ function canJoinEventWaitlistOnAnyMode(event) {
205
+ return canJoinEventWaitlist(selectMostOpenCapacitySource(event));
206
+ }
207
+ const ATTENDANCE_MODE_LABEL = {
208
+ in_person: "In person",
209
+ online: "Online"
210
+ };
211
+ /** Wording for a mode with room to spare, where the count is deliberately withheld. */
212
+ const PLACES_AVAILABLE = "Places available";
213
+ /**
214
+ * The browse-surface label for an event's remaining places. Single-mode
215
+ * events get exactly the aggregate label {@link formatRemainingPlaces}
216
+ * produces, and so does a hybrid event whose two modes tell the same story.
217
+ * When the modes differ, both sides are named: "available" on a card is a
218
+ * promise the registration wizard would otherwise have to break.
219
+ *
220
+ * Hybrid examples, in person full with 3 online places left:
221
+ * default -> "Online: 3 places left · In person: Full"
222
+ * { full: 'Sold out' } -> "Online: 3 places left · In person: Sold out"
223
+ * { compact: true } -> "Online: 3 left"
224
+ */
225
+ function formatEventRemainingPlaces(event, wording = {}) {
226
+ const rows = findHybridModeRows(event);
227
+ if (!rows) return formatRemainingPlaces(event, wording);
228
+ const inPerson = formatRemainingPlaces(rows.inPerson, wording);
229
+ const online = formatRemainingPlaces(rows.online, wording);
230
+ if (inPerson === online) return inPerson;
231
+ if (wording.compact === true) return formatCompactHybridPlaces(rows, wording);
232
+ return formatBothHybridSides(rows, {
233
+ inPerson,
234
+ online
235
+ });
236
+ }
237
+ /**
238
+ * Both sides, the one with room first: that is the half a visitor can act on,
239
+ * and it should not arrive after a full stop's worth of bad news.
240
+ */
241
+ function formatBothHybridSides(rows, labels) {
242
+ 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(" · ");
243
+ }
244
+ function namedModePlaces(mode, label) {
245
+ return `${ATTENDANCE_MODE_LABEL[mode]}: ${label ?? PLACES_AVAILABLE}`;
246
+ }
247
+ /**
248
+ * Pills and timetable cells have room for one short phrase, so they name the
249
+ * single mode that carries news rather than listing both: the scarce side
250
+ * when there is a count to show, and otherwise the side that has closed —
251
+ * "In person: Full", never "Online only", which reads as a statement about
252
+ * how the event is delivered rather than about its remaining places.
253
+ */
254
+ function formatCompactHybridPlaces(rows, wording) {
255
+ const inPerson = getRemainingPlaces(rows.inPerson);
256
+ const online = getRemainingPlaces(rows.online);
257
+ if (inPerson.kind === "full" && online.kind === "full") return formatRemainingPlaces(inPerson.waitlist ? rows.inPerson : rows.online, wording);
258
+ if (inPerson.kind === "full" || online.kind === "full") {
259
+ const fullSide = inPerson.kind === "full" ? "in_person" : "online";
260
+ const openSide = inPerson.kind === "full" ? "online" : "in_person";
261
+ const openLabel = formatRemainingPlaces(rowFor(rows, openSide), wording);
262
+ return openLabel !== null ? namedModePlaces(openSide, openLabel) : namedModePlaces(fullSide, formatRemainingPlaces(rowFor(rows, fullSide), wording));
263
+ }
264
+ const scarcerSide = isScarcer(online, inPerson) ? "online" : "in_person";
265
+ return namedModePlaces(scarcerSide, formatRemainingPlaces(rowFor(rows, scarcerSide), wording));
266
+ }
267
+ function rowFor(rows, mode) {
268
+ return mode === "online" ? rows.online : rows.inPerson;
269
+ }
270
+ //#endregion
271
+ export { formatTicketTypeRemaining as a, getEventAvailabilityStatus as c, selectEventCapacitySource as d, selectMostOpenCapacitySource as f, formatRemainingPlaces as i, getRemainingPlaces as l, canJoinEventWaitlistOnAnyMode as n, formatTotalCapacity as o, formatEventRemainingPlaces as r, getEventAvailability as s, canJoinEventWaitlist as t, isEventFull as u };
@@ -1,4 +1,4 @@
1
- import { t as defineIslandPropsDecoder } from "./propsDecoder-pzBeCh14.mjs";
1
+ import { t as defineIslandPropsDecoder } from "./propsDecoder-pYvx_Srl.mjs";
2
2
  import { t as mountReactIsland } from "./reactMount-SY8AVRof.mjs";
3
3
  import { n as normalizeCarouselProps, t as CarouselNodeClient } from "./carousel.client-59G_WMr4.mjs";
4
4
  import "react";
@@ -1,6 +1,6 @@
1
- import { t as defineIslandPropsDecoder } from "./propsDecoder-pzBeCh14.mjs";
2
- import { t as CartController } from "./shop-commerce-DicXYaYX.mjs";
3
- import { t as mountShopBlockIsland } from "./shopShared-BDf9ljkm.mjs";
1
+ import { t as defineIslandPropsDecoder } from "./propsDecoder-pYvx_Srl.mjs";
2
+ import { t as CartController } from "./shop-commerce-B76Jz23B.mjs";
3
+ import { t as mountShopBlockIsland } from "./shopShared-vXU1omwJ.mjs";
4
4
  import "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region ../blocks/src/system/runtime/nodes/cart.client.tsx
@@ -1,6 +1,6 @@
1
- import { t as defineIslandPropsDecoder } from "./propsDecoder-pzBeCh14.mjs";
2
- import { n as CheckoutController } from "./shop-commerce-DicXYaYX.mjs";
3
- import { t as mountShopBlockIsland } from "./shopShared-BDf9ljkm.mjs";
1
+ import { t as defineIslandPropsDecoder } from "./propsDecoder-pYvx_Srl.mjs";
2
+ import { n as CheckoutController } from "./shop-commerce-B76Jz23B.mjs";
3
+ import { t as mountShopBlockIsland } from "./shopShared-vXU1omwJ.mjs";
4
4
  import "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region ../blocks/src/system/runtime/nodes/checkout.client.tsx