@ticketlayer/elements 0.2.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/cjs/index-DfzcrE9-.js +3598 -0
  4. package/dist/cjs/index.cjs.js +16 -0
  5. package/dist/cjs/loader.cjs.js +12 -0
  6. package/dist/cjs/order-summary-ZMkJ5bez.js +116 -0
  7. package/dist/cjs/sdk-connector-ClDF3013.js +108 -0
  8. package/dist/cjs/ticketlayer-elements.cjs.js +24 -0
  9. package/dist/cjs/tl-buy-modal.cjs.entry.js +127 -0
  10. package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +131 -0
  11. package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +58 -0
  12. package/dist/cjs/tl-cart-badge.cjs.entry.js +109 -0
  13. package/dist/cjs/tl-cart-drawer.cjs.entry.js +67 -0
  14. package/dist/cjs/tl-cart.cjs.entry.js +148 -0
  15. package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +57 -0
  16. package/dist/cjs/tl-event-card.cjs.entry.js +140 -0
  17. package/dist/cjs/tl-event-list.cjs.entry.js +80 -0
  18. package/dist/cjs/tl-login.cjs.entry.js +184 -0
  19. package/dist/cjs/tl-my-orders.cjs.entry.js +123 -0
  20. package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +754 -0
  21. package/dist/cjs/tl-order-confirmation.cjs.entry.js +146 -0
  22. package/dist/cjs/tl-order-tickets_2.cjs.entry.js +3198 -0
  23. package/dist/cjs/tl-promo-code.cjs.entry.js +75 -0
  24. package/dist/cjs/tl-seat-map.cjs.entry.js +147 -0
  25. package/dist/collection/collection-manifest.json +29 -0
  26. package/dist/collection/components/tl-buy-modal/tl-buy-modal.css +264 -0
  27. package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +249 -0
  28. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +120 -0
  29. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +412 -0
  30. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.css +23 -0
  31. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +192 -0
  32. package/dist/collection/components/tl-cart/tl-cart.css +251 -0
  33. package/dist/collection/components/tl-cart/tl-cart.js +314 -0
  34. package/dist/collection/components/tl-cart-badge/tl-cart-badge.css +138 -0
  35. package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +217 -0
  36. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.css +98 -0
  37. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +185 -0
  38. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.css +23 -0
  39. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +148 -0
  40. package/dist/collection/components/tl-event-card/event-card-adapter.js +23 -0
  41. package/dist/collection/components/tl-event-card/tl-event-card.css +175 -0
  42. package/dist/collection/components/tl-event-card/tl-event-card.js +205 -0
  43. package/dist/collection/components/tl-event-list/tl-event-list.css +134 -0
  44. package/dist/collection/components/tl-event-list/tl-event-list.js +171 -0
  45. package/dist/collection/components/tl-login/tl-login.css +133 -0
  46. package/dist/collection/components/tl-login/tl-login.js +309 -0
  47. package/dist/collection/components/tl-my-orders/tl-my-orders.css +204 -0
  48. package/dist/collection/components/tl-my-orders/tl-my-orders.js +221 -0
  49. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.css +658 -0
  50. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +707 -0
  51. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.css +176 -0
  52. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +298 -0
  53. package/dist/collection/components/tl-order-tickets/tl-order-tickets.css +30 -0
  54. package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +122 -0
  55. package/dist/collection/components/tl-promo-code/tl-promo-code.css +106 -0
  56. package/dist/collection/components/tl-promo-code/tl-promo-code.js +181 -0
  57. package/dist/collection/components/tl-seat-map/tl-seat-map.css +85 -0
  58. package/dist/collection/components/tl-seat-map/tl-seat-map.js +366 -0
  59. package/dist/collection/components/tl-ticket/tl-ticket.css +90 -0
  60. package/dist/collection/components/tl-ticket/tl-ticket.js +129 -0
  61. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.css +205 -0
  62. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +332 -0
  63. package/dist/collection/index.js +10 -0
  64. package/dist/collection/utils/order-summary.js +101 -0
  65. package/dist/collection/utils/sdk-connector.js +100 -0
  66. package/dist/collection/utils/testing/mock-sdk.js +95 -0
  67. package/dist/components/index.d.ts +35 -0
  68. package/dist/components/index.js +1 -0
  69. package/dist/components/order-summary.js +1 -0
  70. package/dist/components/sdk-connector.js +1 -0
  71. package/dist/components/tl-buy-modal.d.ts +11 -0
  72. package/dist/components/tl-buy-modal.js +1 -0
  73. package/dist/components/tl-buy-tickets-button.d.ts +11 -0
  74. package/dist/components/tl-buy-tickets-button.js +1 -0
  75. package/dist/components/tl-buy-tickets-wrapper.d.ts +11 -0
  76. package/dist/components/tl-buy-tickets-wrapper.js +1 -0
  77. package/dist/components/tl-buy-tickets-wrapper2.js +1 -0
  78. package/dist/components/tl-cart-badge.d.ts +11 -0
  79. package/dist/components/tl-cart-badge.js +1 -0
  80. package/dist/components/tl-cart-drawer.d.ts +11 -0
  81. package/dist/components/tl-cart-drawer.js +1 -0
  82. package/dist/components/tl-cart.d.ts +11 -0
  83. package/dist/components/tl-cart.js +1 -0
  84. package/dist/components/tl-cart2.js +1 -0
  85. package/dist/components/tl-checkout-wrapper.d.ts +11 -0
  86. package/dist/components/tl-checkout-wrapper.js +1 -0
  87. package/dist/components/tl-checkout-wrapper2.js +1 -0
  88. package/dist/components/tl-event-card.d.ts +11 -0
  89. package/dist/components/tl-event-card.js +1 -0
  90. package/dist/components/tl-event-card2.js +1 -0
  91. package/dist/components/tl-event-list.d.ts +11 -0
  92. package/dist/components/tl-event-list.js +1 -0
  93. package/dist/components/tl-login.d.ts +11 -0
  94. package/dist/components/tl-login.js +1 -0
  95. package/dist/components/tl-login2.js +1 -0
  96. package/dist/components/tl-my-orders.d.ts +11 -0
  97. package/dist/components/tl-my-orders.js +1 -0
  98. package/dist/components/tl-occurrence-selector.d.ts +11 -0
  99. package/dist/components/tl-occurrence-selector.js +1 -0
  100. package/dist/components/tl-occurrence-selector2.js +1 -0
  101. package/dist/components/tl-order-confirmation.d.ts +11 -0
  102. package/dist/components/tl-order-confirmation.js +1 -0
  103. package/dist/components/tl-order-tickets.d.ts +11 -0
  104. package/dist/components/tl-order-tickets.js +1 -0
  105. package/dist/components/tl-order-tickets2.js +1 -0
  106. package/dist/components/tl-promo-code.d.ts +11 -0
  107. package/dist/components/tl-promo-code.js +1 -0
  108. package/dist/components/tl-seat-map.d.ts +11 -0
  109. package/dist/components/tl-seat-map.js +1 -0
  110. package/dist/components/tl-ticket-selector.d.ts +11 -0
  111. package/dist/components/tl-ticket-selector.js +1 -0
  112. package/dist/components/tl-ticket-selector2.js +1 -0
  113. package/dist/components/tl-ticket.d.ts +11 -0
  114. package/dist/components/tl-ticket.js +1 -0
  115. package/dist/components/tl-ticket2.js +1 -0
  116. package/dist/elements.docs.d.ts +454 -0
  117. package/dist/elements.docs.json +3008 -0
  118. package/dist/esm/index-Ba-RZTFI.js +3588 -0
  119. package/dist/esm/index.js +4 -0
  120. package/dist/esm/loader.js +10 -0
  121. package/dist/esm/order-summary-BGekdjN9.js +103 -0
  122. package/dist/esm/sdk-connector-CqhvfyKV.js +102 -0
  123. package/dist/esm/ticketlayer-elements.js +20 -0
  124. package/dist/esm/tl-buy-modal.entry.js +125 -0
  125. package/dist/esm/tl-buy-tickets-button.entry.js +129 -0
  126. package/dist/esm/tl-buy-tickets-wrapper.entry.js +56 -0
  127. package/dist/esm/tl-cart-badge.entry.js +107 -0
  128. package/dist/esm/tl-cart-drawer.entry.js +65 -0
  129. package/dist/esm/tl-cart.entry.js +146 -0
  130. package/dist/esm/tl-checkout-wrapper.entry.js +55 -0
  131. package/dist/esm/tl-event-card.entry.js +138 -0
  132. package/dist/esm/tl-event-list.entry.js +78 -0
  133. package/dist/esm/tl-login.entry.js +182 -0
  134. package/dist/esm/tl-my-orders.entry.js +121 -0
  135. package/dist/esm/tl-occurrence-selector_2.entry.js +751 -0
  136. package/dist/esm/tl-order-confirmation.entry.js +144 -0
  137. package/dist/esm/tl-order-tickets_2.entry.js +3195 -0
  138. package/dist/esm/tl-promo-code.entry.js +73 -0
  139. package/dist/esm/tl-seat-map.entry.js +145 -0
  140. package/dist/index.cjs.js +1 -0
  141. package/dist/index.js +1 -0
  142. package/dist/ticketlayer-elements/index.esm.js +1 -0
  143. package/dist/ticketlayer-elements/p-0291df4c.entry.js +1 -0
  144. package/dist/ticketlayer-elements/p-10f01a1f.entry.js +1 -0
  145. package/dist/ticketlayer-elements/p-11273f8f.entry.js +1 -0
  146. package/dist/ticketlayer-elements/p-340ab4f5.entry.js +1 -0
  147. package/dist/ticketlayer-elements/p-41d22d7c.entry.js +1 -0
  148. package/dist/ticketlayer-elements/p-82752e3b.entry.js +1 -0
  149. package/dist/ticketlayer-elements/p-976770f3.entry.js +1 -0
  150. package/dist/ticketlayer-elements/p-9a903ff6.entry.js +1 -0
  151. package/dist/ticketlayer-elements/p-9c9072c8.entry.js +1 -0
  152. package/dist/ticketlayer-elements/p-9d8163d6.entry.js +1 -0
  153. package/dist/ticketlayer-elements/p-BGekdjN9.js +1 -0
  154. package/dist/ticketlayer-elements/p-Ba-RZTFI.js +2 -0
  155. package/dist/ticketlayer-elements/p-CqhvfyKV.js +1 -0
  156. package/dist/ticketlayer-elements/p-a5c90b68.entry.js +1 -0
  157. package/dist/ticketlayer-elements/p-b6d479ad.entry.js +1 -0
  158. package/dist/ticketlayer-elements/p-dd43f015.entry.js +1 -0
  159. package/dist/ticketlayer-elements/p-eb640760.entry.js +1 -0
  160. package/dist/ticketlayer-elements/p-ee3aa227.entry.js +1 -0
  161. package/dist/ticketlayer-elements/p-fb9444c4.entry.js +1 -0
  162. package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -0
  163. package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -0
  164. package/dist/types/components/tl-buy-modal/tl-buy-modal.d.ts +54 -0
  165. package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +61 -0
  166. package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +52 -0
  167. package/dist/types/components/tl-cart/tl-cart.d.ts +80 -0
  168. package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +36 -0
  169. package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +35 -0
  170. package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +39 -0
  171. package/dist/types/components/tl-event-card/event-card-adapter.d.ts +23 -0
  172. package/dist/types/components/tl-event-card/tl-event-card.d.ts +55 -0
  173. package/dist/types/components/tl-event-list/tl-event-list.d.ts +30 -0
  174. package/dist/types/components/tl-login/tl-login.d.ts +59 -0
  175. package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +39 -0
  176. package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +208 -0
  177. package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +54 -0
  178. package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +21 -0
  179. package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +39 -0
  180. package/dist/types/components/tl-seat-map/tl-seat-map.d.ts +68 -0
  181. package/dist/types/components/tl-ticket/tl-ticket.d.ts +28 -0
  182. package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +78 -0
  183. package/dist/types/components.d.ts +1629 -0
  184. package/dist/types/index.d.ts +14 -0
  185. package/dist/types/stencil-public-runtime.d.ts +1839 -0
  186. package/dist/types/utils/order-summary.d.ts +67 -0
  187. package/dist/types/utils/sdk-connector.d.ts +113 -0
  188. package/dist/types/utils/testing/mock-sdk.d.ts +36 -0
  189. package/elements.catalogue.json +968 -0
  190. package/loader/cdn.js +1 -0
  191. package/loader/index.cjs.js +1 -0
  192. package/loader/index.d.ts +24 -0
  193. package/loader/index.es2017.js +1 -0
  194. package/loader/index.js +2 -0
  195. package/package.json +101 -0
@@ -0,0 +1,707 @@
1
+ import { h } from "@stencil/core";
2
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
3
+ export class TlOccurrenceSelector {
4
+ constructor() {
5
+ /**
6
+ * Layout orientation (auto = horizontal on desktop, vertical on mobile)
7
+ */
8
+ this.orientation = 'auto';
9
+ /**
10
+ * Loading state
11
+ */
12
+ this.loading = true;
13
+ /**
14
+ * Error message
15
+ */
16
+ this.error = null;
17
+ /**
18
+ * Fetched occurrences
19
+ */
20
+ this.occurrences = [];
21
+ /**
22
+ * Current month being displayed in calendar view
23
+ */
24
+ this.currentMonth = new Date();
25
+ /**
26
+ * Currently selected date in calendar view
27
+ */
28
+ this.selectedDate = null;
29
+ /**
30
+ * Computed display mode based on occurrences
31
+ */
32
+ this.computedDisplayMode = 'list';
33
+ /**
34
+ * Container width for responsive layout
35
+ */
36
+ this.containerWidth = 0;
37
+ }
38
+ async componentWillLoad() {
39
+ await this.waitForSDKAndFetch();
40
+ }
41
+ /**
42
+ * Wait for SDK to be ready, then fetch occurrences
43
+ */
44
+ async waitForSDKAndFetch() {
45
+ if (!this.eventId) {
46
+ this.error = 'Event ID is required';
47
+ this.loading = false;
48
+ return;
49
+ }
50
+ try {
51
+ await whenSDK();
52
+ await this.fetchOccurrences();
53
+ }
54
+ catch (err) {
55
+ this.error = err instanceof Error ? err.message : 'SDK not initialized.';
56
+ this.loading = false;
57
+ }
58
+ }
59
+ componentDidLoad() {
60
+ // Set up resize observer for responsive layout
61
+ if (this.containerRef && typeof ResizeObserver !== 'undefined') {
62
+ this.resizeObserver = new ResizeObserver((entries) => {
63
+ for (const entry of entries) {
64
+ this.containerWidth = entry.contentRect.width;
65
+ }
66
+ });
67
+ this.resizeObserver.observe(this.containerRef);
68
+ }
69
+ }
70
+ disconnectedCallback() {
71
+ if (this.resizeObserver) {
72
+ this.resizeObserver.disconnect();
73
+ }
74
+ }
75
+ async onEventIdChange() {
76
+ await this.fetchOccurrences();
77
+ }
78
+ /**
79
+ * Fetch occurrences from the SDK
80
+ */
81
+ async fetchOccurrences() {
82
+ this.loading = true;
83
+ this.error = null;
84
+ try {
85
+ const sdk = getSDK();
86
+ const eventsManager = sdk.getEventsManager();
87
+ const event = await eventsManager.get(this.eventId);
88
+ if (!event) {
89
+ throw new Error('Event not found');
90
+ }
91
+ // Map EventOccurrence to TlOccurrence
92
+ this.occurrences = (event.occurrences || []).map((occ) => ({
93
+ id: occ.id,
94
+ startsAt: occ.startsAt,
95
+ endsAt: occ.endsAt,
96
+ status: occ.availabilityStatus,
97
+ availableCapacity: occ.availableCapacity,
98
+ soldOut: occ.availabilityStatus === 'sold_out',
99
+ }));
100
+ this.computeDisplayMode();
101
+ this.computeInitialSelection();
102
+ }
103
+ catch (err) {
104
+ console.error('[tl-occurrence-selector] Failed to fetch occurrences:', err);
105
+ this.error = err instanceof Error ? err.message : 'Failed to load occurrences';
106
+ this.occurrences = [];
107
+ }
108
+ finally {
109
+ this.loading = false;
110
+ }
111
+ }
112
+ /**
113
+ * Compute the display mode based on occurrence count and date distribution
114
+ */
115
+ computeDisplayMode() {
116
+ if (this.displayMode) {
117
+ this.computedDisplayMode = this.displayMode;
118
+ return;
119
+ }
120
+ const count = this.occurrences.length;
121
+ if (count <= 5) {
122
+ this.computedDisplayMode = 'list';
123
+ return;
124
+ }
125
+ // Check if all occurrences are on the same calendar date
126
+ const uniqueDates = new Set();
127
+ for (const occ of this.occurrences) {
128
+ const date = new Date(occ.startsAt);
129
+ uniqueDates.add(date.toDateString());
130
+ }
131
+ if (uniqueDates.size === 1) {
132
+ this.computedDisplayMode = 'slots';
133
+ }
134
+ else {
135
+ this.computedDisplayMode = 'calendar';
136
+ }
137
+ }
138
+ /**
139
+ * Compute the initial date selection for calendar/slots views
140
+ */
141
+ computeInitialSelection() {
142
+ if (this.occurrences.length === 0)
143
+ return;
144
+ const today = new Date();
145
+ today.setHours(0, 0, 0, 0);
146
+ // Get all dates with occurrences
147
+ const datesWithOccurrences = this.getDatesWithOccurrences();
148
+ // Find the first date that is today or in the future
149
+ let selectedDate = null;
150
+ for (const dateStr of Array.from(datesWithOccurrences.keys()).sort()) {
151
+ const date = new Date(dateStr);
152
+ if (date >= today) {
153
+ selectedDate = date;
154
+ break;
155
+ }
156
+ }
157
+ // If no future dates, select the last available date
158
+ if (!selectedDate && datesWithOccurrences.size > 0) {
159
+ const sortedDates = Array.from(datesWithOccurrences.keys()).sort();
160
+ selectedDate = new Date(sortedDates[sortedDates.length - 1]);
161
+ }
162
+ if (selectedDate) {
163
+ this.selectedDate = selectedDate;
164
+ this.currentMonth = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1);
165
+ }
166
+ }
167
+ /**
168
+ * Get a map of dates to occurrences
169
+ */
170
+ getDatesWithOccurrences() {
171
+ const map = new Map();
172
+ for (const occ of this.occurrences) {
173
+ const date = new Date(occ.startsAt);
174
+ date.setHours(0, 0, 0, 0);
175
+ const dateStr = date.toDateString();
176
+ if (!map.has(dateStr)) {
177
+ map.set(dateStr, []);
178
+ }
179
+ map.get(dateStr).push(occ);
180
+ }
181
+ return map;
182
+ }
183
+ /**
184
+ * Get the first and last occurrence dates
185
+ */
186
+ getDateRange() {
187
+ if (this.occurrences.length === 0)
188
+ return null;
189
+ let first = new Date(this.occurrences[0].startsAt);
190
+ let last = new Date(this.occurrences[0].startsAt);
191
+ for (const occ of this.occurrences) {
192
+ const date = new Date(occ.startsAt);
193
+ if (date < first)
194
+ first = date;
195
+ if (date > last)
196
+ last = date;
197
+ }
198
+ return { first, last };
199
+ }
200
+ /**
201
+ * Check if all occurrences fit in one week (Monday-Sunday)
202
+ */
203
+ isSingleWeek() {
204
+ const dateRange = this.getDateRange();
205
+ if (!dateRange)
206
+ return false;
207
+ // Get Monday of the week containing the first date
208
+ const firstMonday = new Date(dateRange.first);
209
+ const dayOfWeek = firstMonday.getDay();
210
+ const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Monday = 0 days
211
+ firstMonday.setDate(firstMonday.getDate() - daysToSubtract);
212
+ firstMonday.setHours(0, 0, 0, 0);
213
+ // Get Sunday of that week
214
+ const sunday = new Date(firstMonday);
215
+ sunday.setDate(sunday.getDate() + 6);
216
+ sunday.setHours(23, 59, 59, 999);
217
+ // Check if last date is within this week
218
+ return dateRange.last <= sunday;
219
+ }
220
+ /**
221
+ * Check if all occurrences are in the same month
222
+ */
223
+ isSingleMonth() {
224
+ const dateRange = this.getDateRange();
225
+ if (!dateRange)
226
+ return false;
227
+ return (dateRange.first.getMonth() === dateRange.last.getMonth() &&
228
+ dateRange.first.getFullYear() === dateRange.last.getFullYear());
229
+ }
230
+ /**
231
+ * Check if we should use horizontal layout
232
+ */
233
+ isHorizontalLayout() {
234
+ if (this.orientation === 'horizontal')
235
+ return true;
236
+ if (this.orientation === 'vertical')
237
+ return false;
238
+ // Auto: use horizontal if container is wide enough (768px)
239
+ return this.containerWidth >= 768;
240
+ }
241
+ /**
242
+ * Format a date for display
243
+ */
244
+ formatDate(date) {
245
+ return date.toLocaleDateString('en-GB', {
246
+ weekday: 'long',
247
+ day: 'numeric',
248
+ month: 'long',
249
+ year: 'numeric',
250
+ });
251
+ }
252
+ /**
253
+ * Format a time for display
254
+ */
255
+ formatTime(dateString) {
256
+ const date = new Date(dateString);
257
+ return date.toLocaleTimeString('en-GB', {
258
+ hour: '2-digit',
259
+ minute: '2-digit',
260
+ });
261
+ }
262
+ /**
263
+ * Format a full date and time for list view
264
+ */
265
+ formatDateTime(dateString) {
266
+ const date = new Date(dateString);
267
+ return {
268
+ date: date.toLocaleDateString('en-GB', {
269
+ weekday: 'long',
270
+ day: 'numeric',
271
+ month: 'long',
272
+ year: 'numeric',
273
+ }),
274
+ time: date.toLocaleTimeString('en-GB', {
275
+ hour: '2-digit',
276
+ minute: '2-digit',
277
+ }),
278
+ };
279
+ }
280
+ /**
281
+ * Handle occurrence selection
282
+ */
283
+ handleSelect(occurrence) {
284
+ if (occurrence.soldOut || occurrence.availableCapacity === 0)
285
+ return;
286
+ console.log('[tl-occurrence-selector] Selected:', occurrence.id);
287
+ this.tlSelect.emit({ occurrence });
288
+ }
289
+ /**
290
+ * Handle date selection in calendar view
291
+ */
292
+ handleDateSelect(date) {
293
+ this.selectedDate = date;
294
+ // If date is in a different month, navigate to that month
295
+ if (date.getMonth() !== this.currentMonth.getMonth() ||
296
+ date.getFullYear() !== this.currentMonth.getFullYear()) {
297
+ this.currentMonth = new Date(date.getFullYear(), date.getMonth(), 1);
298
+ }
299
+ }
300
+ /**
301
+ * Navigate to previous month
302
+ */
303
+ handlePrevMonth() {
304
+ const dateRange = this.getDateRange();
305
+ if (!dateRange)
306
+ return;
307
+ const newMonth = new Date(this.currentMonth);
308
+ newMonth.setMonth(newMonth.getMonth() - 1);
309
+ // Don't go before the first occurrence month
310
+ const firstMonth = new Date(dateRange.first.getFullYear(), dateRange.first.getMonth(), 1);
311
+ if (newMonth >= firstMonth) {
312
+ this.currentMonth = newMonth;
313
+ }
314
+ }
315
+ /**
316
+ * Navigate to next month
317
+ */
318
+ handleNextMonth() {
319
+ const dateRange = this.getDateRange();
320
+ if (!dateRange)
321
+ return;
322
+ const newMonth = new Date(this.currentMonth);
323
+ newMonth.setMonth(newMonth.getMonth() + 1);
324
+ // Don't go past the last occurrence month
325
+ const lastMonth = new Date(dateRange.last.getFullYear(), dateRange.last.getMonth(), 1);
326
+ if (newMonth <= lastMonth) {
327
+ this.currentMonth = newMonth;
328
+ }
329
+ }
330
+ /**
331
+ * Check if prev month button should be disabled
332
+ */
333
+ isPrevMonthDisabled() {
334
+ const dateRange = this.getDateRange();
335
+ if (!dateRange)
336
+ return true;
337
+ const firstMonth = new Date(dateRange.first.getFullYear(), dateRange.first.getMonth(), 1);
338
+ return this.currentMonth <= firstMonth;
339
+ }
340
+ /**
341
+ * Check if next month button should be disabled
342
+ */
343
+ isNextMonthDisabled() {
344
+ const dateRange = this.getDateRange();
345
+ if (!dateRange)
346
+ return true;
347
+ const lastMonth = new Date(dateRange.last.getFullYear(), dateRange.last.getMonth(), 1);
348
+ return this.currentMonth >= lastMonth;
349
+ }
350
+ /**
351
+ * Get calendar grid days for the current month
352
+ */
353
+ getCalendarDays() {
354
+ // If all occurrences fit in one week, only show that week
355
+ if (this.isSingleWeek()) {
356
+ const dateRange = this.getDateRange();
357
+ if (!dateRange)
358
+ return [];
359
+ // Get Monday of the week containing the first date
360
+ const firstMonday = new Date(dateRange.first);
361
+ const dayOfWeek = firstMonday.getDay();
362
+ const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
363
+ firstMonday.setDate(firstMonday.getDate() - daysToSubtract);
364
+ firstMonday.setHours(0, 0, 0, 0);
365
+ // Generate 7 days (Monday to Sunday)
366
+ const days = [];
367
+ const current = new Date(firstMonday);
368
+ for (let i = 0; i < 7; i++) {
369
+ days.push(new Date(current));
370
+ current.setDate(current.getDate() + 1);
371
+ }
372
+ return days;
373
+ }
374
+ // Otherwise show full month grid
375
+ const year = this.currentMonth.getFullYear();
376
+ const month = this.currentMonth.getMonth();
377
+ // Start of month
378
+ const monthStart = new Date(year, month, 1);
379
+ // End of month
380
+ const monthEnd = new Date(year, month + 1, 0);
381
+ // Get the start of the week containing the first day (Monday = 1)
382
+ const startDay = monthStart.getDay();
383
+ const daysToSubtract = startDay === 0 ? 6 : startDay - 1; // Monday = 0 days to subtract
384
+ const calendarStart = new Date(monthStart);
385
+ calendarStart.setDate(calendarStart.getDate() - daysToSubtract);
386
+ // Get the end of the week containing the last day
387
+ const endDay = monthEnd.getDay();
388
+ const daysToAdd = endDay === 0 ? 0 : 7 - endDay;
389
+ const calendarEnd = new Date(monthEnd);
390
+ calendarEnd.setDate(calendarEnd.getDate() + daysToAdd);
391
+ // Generate all days
392
+ const days = [];
393
+ const current = new Date(calendarStart);
394
+ while (current <= calendarEnd) {
395
+ days.push(new Date(current));
396
+ current.setDate(current.getDate() + 1);
397
+ }
398
+ return days;
399
+ }
400
+ /**
401
+ * Check if a date has occurrences
402
+ */
403
+ hasOccurrencesOnDate(date) {
404
+ const dateStr = date.toDateString();
405
+ return this.getDatesWithOccurrences().has(dateStr);
406
+ }
407
+ /**
408
+ * Check if all occurrences on a date are sold out
409
+ */
410
+ isDateSoldOut(date) {
411
+ const dateStr = date.toDateString();
412
+ const occurrences = this.getDatesWithOccurrences().get(dateStr);
413
+ if (!occurrences)
414
+ return false;
415
+ return occurrences.every(occ => occ.soldOut || occ.availableCapacity === 0);
416
+ }
417
+ /**
418
+ * Get occurrences for the selected date
419
+ */
420
+ getSelectedDateOccurrences() {
421
+ if (!this.selectedDate)
422
+ return [];
423
+ const dateStr = this.selectedDate.toDateString();
424
+ const occurrences = this.getDatesWithOccurrences().get(dateStr) || [];
425
+ // Sort by start time
426
+ return occurrences.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
427
+ }
428
+ /**
429
+ * Get CSS custom properties from theme
430
+ */
431
+ getThemeStyles() {
432
+ if (!this.theme)
433
+ return {};
434
+ const styles = {};
435
+ if (this.theme.primaryColor) {
436
+ styles['--tl-primary'] = this.theme.primaryColor;
437
+ }
438
+ if (this.theme.primaryForeground) {
439
+ styles['--tl-primary-foreground'] = this.theme.primaryForeground;
440
+ }
441
+ if (this.theme.backgroundColor) {
442
+ styles['--tl-background'] = this.theme.backgroundColor;
443
+ }
444
+ if (this.theme.foregroundColor) {
445
+ styles['--tl-foreground'] = this.theme.foregroundColor;
446
+ }
447
+ if (this.theme.mutedColor) {
448
+ styles['--tl-muted'] = this.theme.mutedColor;
449
+ }
450
+ if (this.theme.mutedForeground) {
451
+ styles['--tl-muted-foreground'] = this.theme.mutedForeground;
452
+ }
453
+ if (this.theme.borderColor) {
454
+ styles['--tl-border'] = this.theme.borderColor;
455
+ }
456
+ if (this.theme.borderRadius) {
457
+ styles['--tl-radius'] = this.theme.borderRadius;
458
+ }
459
+ if (this.theme.fontFamily) {
460
+ styles['--tl-font-family'] = this.theme.fontFamily;
461
+ }
462
+ return styles;
463
+ }
464
+ // =========================================================================
465
+ // RENDER METHODS
466
+ // =========================================================================
467
+ /**
468
+ * Render the list view (≤5 occurrences)
469
+ */
470
+ renderListView() {
471
+ return (h("div", { class: "list-view" }, this.occurrences.map((occurrence) => {
472
+ const isSoldOut = occurrence.soldOut || occurrence.availableCapacity === 0;
473
+ const { date, time } = this.formatDateTime(occurrence.startsAt);
474
+ return (h("button", { key: occurrence.id, class: `occurrence-item ${isSoldOut ? 'sold-out' : ''}`, onClick: () => this.handleSelect(occurrence), disabled: isSoldOut }, h("div", { class: "occurrence-info" }, h("div", { class: "occurrence-date" }, date), h("div", { class: "occurrence-time" }, time, occurrence.endsAt && ` - ${this.formatTime(occurrence.endsAt)}`)), h("div", { class: "occurrence-status" }, isSoldOut ? (h("span", { class: "status-sold-out" }, "Sold Out")) : occurrence.availableCapacity ? (h("span", { class: "status-available" }, occurrence.availableCapacity, " available")) : (h("span", { class: "status-arrow" }, "\u2192")))));
475
+ })));
476
+ }
477
+ /**
478
+ * Render the time slots view (>5 occurrences, same day)
479
+ */
480
+ renderSlotsView() {
481
+ // Sort occurrences by time
482
+ const sortedOccurrences = [...this.occurrences].sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
483
+ // Get the date from the first occurrence
484
+ const dateLabel = sortedOccurrences.length > 0
485
+ ? this.formatDate(new Date(sortedOccurrences[0].startsAt))
486
+ : '';
487
+ return (h("div", { class: "slots-view" }, dateLabel && h("div", { class: "slots-date-header" }, dateLabel), h("div", { class: "time-slots-grid" }, sortedOccurrences.map((occurrence) => {
488
+ const isSoldOut = occurrence.soldOut || occurrence.availableCapacity === 0;
489
+ return (h("button", { key: occurrence.id, class: `time-slot ${isSoldOut ? 'sold-out' : ''}`, onClick: () => this.handleSelect(occurrence), disabled: isSoldOut }, h("span", { class: "slot-time" }, this.formatTime(occurrence.startsAt)), isSoldOut && h("span", { class: "slot-sold-out" }, "Sold Out")));
490
+ }))));
491
+ }
492
+ /**
493
+ * Render the calendar header
494
+ */
495
+ renderCalendarHeader() {
496
+ const monthLabel = this.currentMonth.toLocaleDateString('en-GB', {
497
+ month: 'long',
498
+ year: 'numeric',
499
+ });
500
+ const showNavigation = !this.isSingleMonth();
501
+ return (h("div", { class: "calendar-header" }, h("span", { class: "month-label" }, monthLabel), showNavigation && (h("div", { class: "calendar-nav-buttons" }, h("button", { class: "nav-button", onClick: () => this.handlePrevMonth(), disabled: this.isPrevMonthDisabled(), "aria-label": "Previous month" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("path", { d: "M15 18l-6-6 6-6" }))), h("button", { class: "nav-button", onClick: () => this.handleNextMonth(), disabled: this.isNextMonthDisabled(), "aria-label": "Next month" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("path", { d: "M9 18l6-6-6-6" })))))));
502
+ }
503
+ /**
504
+ * Render the calendar grid
505
+ */
506
+ renderCalendarGrid() {
507
+ const days = this.getCalendarDays();
508
+ const weekDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
509
+ const today = new Date();
510
+ today.setHours(0, 0, 0, 0);
511
+ return (h("div", { class: "calendar-grid" }, weekDays.map((day) => (h("div", { class: "weekday-header", key: day }, day))), days.map((date) => {
512
+ const isCurrentMonth = date.getMonth() === this.currentMonth.getMonth();
513
+ const hasOccurrences = this.hasOccurrencesOnDate(date);
514
+ const isSoldOut = this.isDateSoldOut(date);
515
+ const isSelected = this.selectedDate && date.toDateString() === this.selectedDate.toDateString();
516
+ const isToday = date.toDateString() === today.toDateString();
517
+ const isPast = date < today;
518
+ return (h("button", { key: date.toISOString(), class: `calendar-day
519
+ ${!isCurrentMonth ? 'other-month' : ''}
520
+ ${hasOccurrences ? 'has-occurrences' : ''}
521
+ ${isSoldOut ? 'sold-out' : ''}
522
+ ${isSelected ? 'selected' : ''}
523
+ ${isToday ? 'today' : ''}
524
+ ${isPast ? 'past' : ''}`, onClick: () => hasOccurrences && this.handleDateSelect(date), disabled: !hasOccurrences }, h("span", { class: "day-number" }, date.getDate()), hasOccurrences && h("span", { class: "occurrence-dot" })));
525
+ })));
526
+ }
527
+ /**
528
+ * Render the selected date's time slots
529
+ */
530
+ renderSelectedDateSlots() {
531
+ const occurrences = this.getSelectedDateOccurrences();
532
+ if (!this.selectedDate || occurrences.length === 0) {
533
+ return (h("div", { class: "selected-date-slots empty" }, h("p", null, "Select a date to see available times")));
534
+ }
535
+ const dateLabel = this.formatDate(this.selectedDate);
536
+ return (h("div", { class: "selected-date-slots" }, h("div", { class: "selected-date-header" }, dateLabel), h("div", { class: "time-slots-list" }, occurrences.map((occurrence) => {
537
+ const isSoldOut = occurrence.soldOut || occurrence.availableCapacity === 0;
538
+ return (h("button", { key: occurrence.id, class: `time-slot-item ${isSoldOut ? 'sold-out' : ''}`, onClick: () => this.handleSelect(occurrence), disabled: isSoldOut }, h("span", { class: "slot-time" }, this.formatTime(occurrence.startsAt)), h("div", { class: "slot-status" }, isSoldOut ? (h("span", { class: "status-sold-out" }, "Sold Out")) : occurrence.availableCapacity ? (h("span", { class: "status-available" }, occurrence.availableCapacity, " available")) : (h("span", { class: "status-arrow" }, "\u2192")))));
539
+ }))));
540
+ }
541
+ /**
542
+ * Render the calendar view with slots panel
543
+ */
544
+ renderCalendarView() {
545
+ const isHorizontal = this.isHorizontalLayout();
546
+ return (h("div", { class: `calendar-view ${isHorizontal ? 'horizontal' : 'vertical'}` }, h("div", { class: "calendar-panel" }, this.renderCalendarHeader(), this.renderCalendarGrid()), h("div", { class: "slots-panel" }, this.renderSelectedDateSlots())));
547
+ }
548
+ /**
549
+ * Render skeleton loading state
550
+ */
551
+ renderSkeleton() {
552
+ // Default to calendar skeleton as it's the most common
553
+ return (h("div", { class: "loading-container" }, h("div", { class: "skeleton-calendar" }, h("div", { class: "skeleton skeleton-calendar-header" }), h("div", { class: "skeleton-calendar-grid" }, Array.from({ length: 35 }, (_, i) => (h("div", { key: i, class: "skeleton skeleton-calendar-day" })))))));
554
+ }
555
+ render() {
556
+ const themeStyles = this.getThemeStyles();
557
+ return (h("div", { key: '40758aaa06e5f062de92675766e4531995f74bc7', class: "occurrence-selector", ref: (el) => (this.containerRef = el), style: themeStyles }, this.loading && this.renderSkeleton(), !this.loading && this.error && (h("div", { key: '6d2f5b9c07f65d4941bfb4870729fe3248cb277e', class: "error-container" }, h("svg", { key: 'daaf1f48dcff80ae7cf94ae0da1d66d9c9b7fb0f', class: "error-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("circle", { key: '0a9f494a5cf7f79146e56fc44469a3ca9e5238ae', cx: "12", cy: "12", r: "10" }), h("path", { key: '67d7b453508c847593136bd60e34ea0c38d30cda', d: "M12 8v4m0 4h.01" })), h("p", { key: 'c1cc92585ed4451b686350b6d718c3ba06b72dd3', class: "error-message" }, this.error), h("button", { key: '4ec9d048af704af12a800385b9868501973ba477', class: "retry-button", onClick: () => this.fetchOccurrences() }, "Retry"))), !this.loading && !this.error && (this.occurrences.length === 0 ? (h("div", { class: "no-occurrences" }, h("p", null, "No dates available for this event."))) : (h("div", { class: "selector-content" }, this.computedDisplayMode === 'list' && this.renderListView(), this.computedDisplayMode === 'slots' && this.renderSlotsView(), this.computedDisplayMode === 'calendar' && this.renderCalendarView())))));
558
+ }
559
+ static get is() { return "tl-occurrence-selector"; }
560
+ static get encapsulation() { return "shadow"; }
561
+ static get originalStyleUrls() {
562
+ return {
563
+ "$": ["tl-occurrence-selector.css"]
564
+ };
565
+ }
566
+ static get styleUrls() {
567
+ return {
568
+ "$": ["tl-occurrence-selector.css"]
569
+ };
570
+ }
571
+ static get properties() {
572
+ return {
573
+ "eventId": {
574
+ "type": "string",
575
+ "mutable": false,
576
+ "complexType": {
577
+ "original": "string",
578
+ "resolved": "string",
579
+ "references": {}
580
+ },
581
+ "required": true,
582
+ "optional": false,
583
+ "docs": {
584
+ "tags": [],
585
+ "text": "Event ID to fetch occurrences for"
586
+ },
587
+ "getter": false,
588
+ "setter": false,
589
+ "reflect": false,
590
+ "attribute": "event-id"
591
+ },
592
+ "displayMode": {
593
+ "type": "string",
594
+ "mutable": false,
595
+ "complexType": {
596
+ "original": "DisplayMode",
597
+ "resolved": "\"calendar\" | \"list\" | \"slots\" | undefined",
598
+ "references": {
599
+ "DisplayMode": {
600
+ "location": "global",
601
+ "id": "global::DisplayMode"
602
+ }
603
+ }
604
+ },
605
+ "required": false,
606
+ "optional": true,
607
+ "docs": {
608
+ "tags": [],
609
+ "text": "Force a specific display mode (auto-detected if not set)"
610
+ },
611
+ "getter": false,
612
+ "setter": false,
613
+ "reflect": false,
614
+ "attribute": "display-mode"
615
+ },
616
+ "orientation": {
617
+ "type": "string",
618
+ "mutable": false,
619
+ "complexType": {
620
+ "original": "Orientation",
621
+ "resolved": "\"auto\" | \"horizontal\" | \"vertical\"",
622
+ "references": {
623
+ "Orientation": {
624
+ "location": "global",
625
+ "id": "global::Orientation"
626
+ }
627
+ }
628
+ },
629
+ "required": false,
630
+ "optional": false,
631
+ "docs": {
632
+ "tags": [],
633
+ "text": "Layout orientation (auto = horizontal on desktop, vertical on mobile)"
634
+ },
635
+ "getter": false,
636
+ "setter": false,
637
+ "reflect": false,
638
+ "attribute": "orientation",
639
+ "defaultValue": "'auto'"
640
+ },
641
+ "theme": {
642
+ "type": "unknown",
643
+ "mutable": false,
644
+ "complexType": {
645
+ "original": "TlOccurrenceSelectorTheme",
646
+ "resolved": "TlOccurrenceSelectorTheme | undefined",
647
+ "references": {
648
+ "TlOccurrenceSelectorTheme": {
649
+ "location": "local",
650
+ "path": "/home/runner/work/live-elements/live-elements/src/components/tl-occurrence-selector/tl-occurrence-selector.tsx",
651
+ "id": "src/components/tl-occurrence-selector/tl-occurrence-selector.tsx::TlOccurrenceSelectorTheme"
652
+ }
653
+ }
654
+ },
655
+ "required": false,
656
+ "optional": true,
657
+ "docs": {
658
+ "tags": [],
659
+ "text": "Theme configuration"
660
+ },
661
+ "getter": false,
662
+ "setter": false
663
+ }
664
+ };
665
+ }
666
+ static get states() {
667
+ return {
668
+ "loading": {},
669
+ "error": {},
670
+ "occurrences": {},
671
+ "currentMonth": {},
672
+ "selectedDate": {},
673
+ "computedDisplayMode": {},
674
+ "containerWidth": {}
675
+ };
676
+ }
677
+ static get events() {
678
+ return [{
679
+ "method": "tlSelect",
680
+ "name": "tlSelect",
681
+ "bubbles": true,
682
+ "cancelable": true,
683
+ "composed": true,
684
+ "docs": {
685
+ "tags": [],
686
+ "text": "Emitted when an occurrence is selected"
687
+ },
688
+ "complexType": {
689
+ "original": "{ occurrence: TlOccurrence }",
690
+ "resolved": "{ occurrence: TlOccurrence; }",
691
+ "references": {
692
+ "TlOccurrence": {
693
+ "location": "local",
694
+ "path": "/home/runner/work/live-elements/live-elements/src/components/tl-occurrence-selector/tl-occurrence-selector.tsx",
695
+ "id": "src/components/tl-occurrence-selector/tl-occurrence-selector.tsx::TlOccurrence"
696
+ }
697
+ }
698
+ }
699
+ }];
700
+ }
701
+ static get watchers() {
702
+ return [{
703
+ "propName": "eventId",
704
+ "methodName": "onEventIdChange"
705
+ }];
706
+ }
707
+ }