@ticketlayer/elements 0.3.3 → 1.0.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 (179) hide show
  1. package/README.md +3 -2
  2. package/dist/cjs/{index-BpfoKd9Q.js → index-DanPUX0s.js} +1 -1
  3. package/dist/cjs/index.cjs.js +2 -1
  4. package/dist/cjs/loader.cjs.js +2 -2
  5. package/dist/cjs/ticket-selector-BYVL13k_.js +3623 -0
  6. package/dist/cjs/ticketlayer-elements.cjs.js +2 -2
  7. package/dist/cjs/tl-buy-modal.cjs.entry.js +4 -4
  8. package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +89 -71
  9. package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +62 -31
  10. package/dist/cjs/tl-cart-badge.cjs.entry.js +75 -62
  11. package/dist/cjs/tl-cart-drawer.cjs.entry.js +52 -24
  12. package/dist/cjs/tl-cart.cjs.entry.js +72 -98
  13. package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +52 -30
  14. package/dist/cjs/tl-event-card.cjs.entry.js +44 -105
  15. package/dist/cjs/tl-event-list.cjs.entry.js +73 -36
  16. package/dist/cjs/tl-login.cjs.entry.js +75 -141
  17. package/dist/cjs/tl-my-orders.cjs.entry.js +53 -92
  18. package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +205 -641
  19. package/dist/cjs/tl-order-confirmation.cjs.entry.js +72 -105
  20. package/dist/cjs/tl-order-tickets_2.cjs.entry.js +50 -76
  21. package/dist/cjs/tl-promo-code.cjs.entry.js +51 -47
  22. package/dist/cjs/tl-seat-map.cjs.entry.js +4 -4
  23. package/dist/cjs/web-query-Ce73jB4k.js +16 -0
  24. package/dist/collection/components/tl-buy-modal/tl-buy-modal.css +12 -19
  25. package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +2 -2
  26. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +41 -4
  27. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +105 -76
  28. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.css +2 -2
  29. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +73 -30
  30. package/dist/collection/components/tl-cart/tl-cart.css +52 -51
  31. package/dist/collection/components/tl-cart/tl-cart.js +82 -101
  32. package/dist/collection/components/tl-cart-badge/tl-cart-badge.css +11 -11
  33. package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +62 -62
  34. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.css +6 -6
  35. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +56 -24
  36. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.css +2 -2
  37. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +61 -29
  38. package/dist/collection/components/tl-event-card/event-card-adapter.js +11 -23
  39. package/dist/collection/components/tl-event-card/tl-event-card.css +22 -22
  40. package/dist/collection/components/tl-event-card/tl-event-card.js +51 -84
  41. package/dist/collection/components/tl-event-list/tl-event-list.css +17 -17
  42. package/dist/collection/components/tl-event-list/tl-event-list.js +82 -38
  43. package/dist/collection/components/tl-login/tl-login.css +26 -26
  44. package/dist/collection/components/tl-login/tl-login.js +83 -147
  45. package/dist/collection/components/tl-my-orders/tl-my-orders.css +40 -40
  46. package/dist/collection/components/tl-my-orders/tl-my-orders.js +58 -96
  47. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.css +54 -65
  48. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +137 -516
  49. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.css +38 -38
  50. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +81 -113
  51. package/dist/collection/components/tl-order-tickets/tl-order-tickets.css +9 -9
  52. package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +57 -92
  53. package/dist/collection/components/tl-promo-code/tl-promo-code.css +25 -25
  54. package/dist/collection/components/tl-promo-code/tl-promo-code.js +55 -50
  55. package/dist/collection/components/tl-seat-map/tl-seat-map.css +10 -10
  56. package/dist/collection/components/tl-seat-map/tl-seat-map.js +2 -2
  57. package/dist/collection/components/tl-ticket/tl-ticket.css +13 -13
  58. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.css +74 -49
  59. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +95 -135
  60. package/dist/collection/utils/cart-totals.js +9 -0
  61. package/dist/collection/utils/order-summary.js +15 -99
  62. package/dist/collection/utils/testing/deep-text.js +29 -0
  63. package/dist/collection/utils/testing/fixtures.js +290 -0
  64. package/dist/collection/utils/testing/mock-sdk.js +13 -2
  65. package/dist/collection/utils/web-query.js +12 -0
  66. package/dist/collection/utils/web-storage.js +22 -0
  67. package/dist/components/ticket-selector.js +1 -0
  68. package/dist/components/tl-buy-modal.js +1 -1
  69. package/dist/components/tl-buy-tickets-button.js +1 -1
  70. package/dist/components/tl-buy-tickets-wrapper2.js +1 -1
  71. package/dist/components/tl-cart-badge.js +1 -1
  72. package/dist/components/tl-cart-drawer.js +1 -1
  73. package/dist/components/tl-cart2.js +1 -1
  74. package/dist/components/tl-checkout-wrapper2.js +1 -1
  75. package/dist/components/tl-event-card2.js +1 -1
  76. package/dist/components/tl-event-list.js +1 -1
  77. package/dist/components/tl-login2.js +1 -1
  78. package/dist/components/tl-my-orders.js +1 -1
  79. package/dist/components/tl-occurrence-selector2.js +1 -1
  80. package/dist/components/tl-order-confirmation.js +1 -1
  81. package/dist/components/tl-order-tickets2.js +1 -1
  82. package/dist/components/tl-promo-code.js +1 -1
  83. package/dist/components/tl-seat-map.js +1 -1
  84. package/dist/components/tl-ticket-selector2.js +1 -1
  85. package/dist/components/tl-ticket2.js +1 -1
  86. package/dist/components/web-query.js +1 -0
  87. package/dist/elements.docs.json +36 -36
  88. package/dist/esm/{index-CNX8hZa8.js → index-imfxp765.js} +1 -1
  89. package/dist/esm/index.js +2 -1
  90. package/dist/esm/loader.js +3 -3
  91. package/dist/esm/ticket-selector-BwChnHVC.js +3606 -0
  92. package/dist/esm/ticketlayer-elements.js +3 -3
  93. package/dist/esm/tl-buy-modal.entry.js +4 -4
  94. package/dist/esm/tl-buy-tickets-button.entry.js +90 -72
  95. package/dist/esm/tl-buy-tickets-wrapper.entry.js +63 -32
  96. package/dist/esm/tl-cart-badge.entry.js +76 -63
  97. package/dist/esm/tl-cart-drawer.entry.js +53 -25
  98. package/dist/esm/tl-cart.entry.js +73 -99
  99. package/dist/esm/tl-checkout-wrapper.entry.js +53 -31
  100. package/dist/esm/tl-event-card.entry.js +45 -106
  101. package/dist/esm/tl-event-list.entry.js +74 -37
  102. package/dist/esm/tl-login.entry.js +76 -142
  103. package/dist/esm/tl-my-orders.entry.js +54 -93
  104. package/dist/esm/tl-occurrence-selector_2.entry.js +206 -642
  105. package/dist/esm/tl-order-confirmation.entry.js +73 -106
  106. package/dist/esm/tl-order-tickets_2.entry.js +51 -77
  107. package/dist/esm/tl-promo-code.entry.js +52 -48
  108. package/dist/esm/tl-seat-map.entry.js +4 -4
  109. package/dist/esm/web-query-Bdbb2lk5.js +14 -0
  110. package/dist/ticketlayer-elements/index.esm.js +1 -1
  111. package/dist/ticketlayer-elements/p-45923d2c.entry.js +1 -0
  112. package/dist/ticketlayer-elements/p-492adc77.entry.js +1 -0
  113. package/dist/ticketlayer-elements/p-4cfc9e53.entry.js +1 -0
  114. package/dist/ticketlayer-elements/p-5135630e.entry.js +1 -0
  115. package/dist/ticketlayer-elements/p-5bbce3b5.entry.js +1 -0
  116. package/dist/ticketlayer-elements/p-7d547721.entry.js +1 -0
  117. package/dist/ticketlayer-elements/p-7fe7b6ba.entry.js +1 -0
  118. package/dist/ticketlayer-elements/p-8122f0c1.entry.js +1 -0
  119. package/dist/ticketlayer-elements/p-98d40869.entry.js +1 -0
  120. package/dist/ticketlayer-elements/p-Bdbb2lk5.js +1 -0
  121. package/dist/ticketlayer-elements/p-BwChnHVC.js +1 -0
  122. package/dist/ticketlayer-elements/p-abec290a.entry.js +1 -0
  123. package/dist/ticketlayer-elements/p-ac79a3f6.entry.js +1 -0
  124. package/dist/ticketlayer-elements/p-d01f4429.entry.js +1 -0
  125. package/dist/ticketlayer-elements/p-d7ffaf46.entry.js +1 -0
  126. package/dist/ticketlayer-elements/p-e7170f3c.entry.js +1 -0
  127. package/dist/ticketlayer-elements/p-e787e862.entry.js +1 -0
  128. package/dist/ticketlayer-elements/p-f1ff931f.entry.js +1 -0
  129. package/dist/ticketlayer-elements/p-imfxp765.js +3 -0
  130. package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -1
  131. package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -1
  132. package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +40 -8
  133. package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +20 -1
  134. package/dist/types/components/tl-cart/tl-cart.d.ts +40 -32
  135. package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +22 -7
  136. package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +28 -5
  137. package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +18 -1
  138. package/dist/types/components/tl-event-card/event-card-adapter.d.ts +9 -20
  139. package/dist/types/components/tl-event-card/tl-event-card.d.ts +25 -28
  140. package/dist/types/components/tl-event-list/tl-event-list.d.ts +46 -6
  141. package/dist/types/components/tl-login/tl-login.d.ts +68 -22
  142. package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +30 -12
  143. package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +73 -161
  144. package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +48 -15
  145. package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +29 -31
  146. package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +28 -10
  147. package/dist/types/components/tl-ticket/tl-ticket.d.ts +2 -11
  148. package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +61 -32
  149. package/dist/types/components.d.ts +302 -62
  150. package/dist/types/utils/cart-totals.d.ts +10 -0
  151. package/dist/types/utils/order-summary.d.ts +16 -65
  152. package/dist/types/utils/testing/deep-text.d.ts +13 -0
  153. package/dist/types/utils/testing/fixtures.d.ts +225 -0
  154. package/dist/types/utils/testing/mock-sdk.d.ts +14 -1
  155. package/dist/types/utils/web-query.d.ts +18 -0
  156. package/dist/types/utils/web-storage.d.ts +16 -0
  157. package/elements.catalogue.json +676 -55
  158. package/package.json +14 -6
  159. package/dist/cjs/order-summary-ZMkJ5bez.js +0 -116
  160. package/dist/components/order-summary.js +0 -1
  161. package/dist/esm/order-summary-BGekdjN9.js +0 -103
  162. package/dist/ticketlayer-elements/p-0b1d16f8.entry.js +0 -1
  163. package/dist/ticketlayer-elements/p-1471f6d8.entry.js +0 -1
  164. package/dist/ticketlayer-elements/p-1c51cbc1.entry.js +0 -1
  165. package/dist/ticketlayer-elements/p-5169f68b.entry.js +0 -1
  166. package/dist/ticketlayer-elements/p-895ba1f3.entry.js +0 -1
  167. package/dist/ticketlayer-elements/p-8d5722e1.entry.js +0 -1
  168. package/dist/ticketlayer-elements/p-917d8e09.entry.js +0 -1
  169. package/dist/ticketlayer-elements/p-9629029a.entry.js +0 -1
  170. package/dist/ticketlayer-elements/p-BGekdjN9.js +0 -1
  171. package/dist/ticketlayer-elements/p-CNX8hZa8.js +0 -3
  172. package/dist/ticketlayer-elements/p-a22714d0.entry.js +0 -1
  173. package/dist/ticketlayer-elements/p-bf73044b.entry.js +0 -1
  174. package/dist/ticketlayer-elements/p-e02079fa.entry.js +0 -1
  175. package/dist/ticketlayer-elements/p-e04ace15.entry.js +0 -1
  176. package/dist/ticketlayer-elements/p-e6fab1d5.entry.js +0 -1
  177. package/dist/ticketlayer-elements/p-f826b753.entry.js +0 -1
  178. package/dist/ticketlayer-elements/p-fa639d88.entry.js +0 -1
  179. package/dist/ticketlayer-elements/p-fbaca948.entry.js +0 -1
@@ -1,33 +1,33 @@
1
1
  :host {
2
2
  /* Map SDK variables (--tl-*) to component variables (--tl-color-*) */
3
3
  /* This allows the component to inherit theme from SDK */
4
- --tl-color-primary: var(--tl-primary, #6366f1);
5
- --tl-color-primary-foreground: var(--tl-primary-foreground, #ffffff);
6
- --tl-color-background: var(--tl-background, #ffffff);
7
- --tl-color-foreground: var(--tl-foreground, #111827);
8
- --tl-color-muted: var(--tl-muted, #f3f4f6);
9
- --tl-color-muted-foreground: var(--tl-muted-foreground, #6b7280);
10
- --tl-color-border: var(--tl-border, #e5e7eb);
11
- --tl-color-destructive: var(--tl-error, #ef4444);
4
+ --tl-color-primary: var(--tl-primary);
5
+ --tl-color-primary-foreground: var(--tl-primary-foreground);
6
+ --tl-color-background: var(--tl-background);
7
+ --tl-color-foreground: var(--tl-foreground);
8
+ --tl-color-muted: var(--tl-muted);
9
+ --tl-color-muted-foreground: var(--tl-muted-foreground);
10
+ --tl-color-border: var(--tl-border);
11
+ --tl-color-destructive: var(--tl-error);
12
12
 
13
13
  display: block;
14
- font-family: var(--tl-font-family, system-ui, sans-serif);
14
+ font-family: var(--tl-font-family);
15
15
  }
16
16
 
17
17
  .ticket-selector {
18
- background: var(--tl-color-background, #ffffff);
18
+ background: var(--tl-color-background);
19
19
  }
20
20
 
21
21
  /* Skeleton loading */
22
22
  .skeleton-container {
23
- padding: var(--tl-spacing-4, 1rem);
23
+ padding: var(--tl-spacing-4);
24
24
  }
25
25
 
26
26
  .skeleton-row {
27
27
  height: 60px;
28
- background: var(--tl-color-muted, #f3f4f6);
29
- border-radius: var(--tl-radius-md, 0.5rem);
30
- margin-bottom: var(--tl-spacing-3, 0.75rem);
28
+ background: var(--tl-color-muted);
29
+ border-radius: var(--tl-radius-md);
30
+ margin-bottom: var(--tl-spacing-3);
31
31
  animation: pulse 1.5s ease-in-out infinite;
32
32
  }
33
33
 
@@ -38,8 +38,8 @@
38
38
 
39
39
  /* Error state */
40
40
  .error-state {
41
- padding: var(--tl-spacing-4, 1rem);
42
- color: var(--tl-color-destructive, #ef4444);
41
+ padding: var(--tl-spacing-4);
42
+ color: var(--tl-color-destructive);
43
43
  }
44
44
 
45
45
  .error-message {
@@ -49,7 +49,7 @@
49
49
 
50
50
  /* Ticket list */
51
51
  .ticket-list {
52
- margin-bottom: var(--tl-spacing-4, 1rem);
52
+ margin-bottom: var(--tl-spacing-4);
53
53
  }
54
54
 
55
55
  /* Ticket row */
@@ -57,8 +57,8 @@
57
57
  display: flex;
58
58
  align-items: center;
59
59
  justify-content: space-between;
60
- padding: var(--tl-spacing-3, 0.75rem) 0;
61
- border-bottom: 1px solid var(--tl-color-border, #e5e7eb);
60
+ padding: var(--tl-spacing-3) 0;
61
+ border-bottom: 1px solid var(--tl-color-border);
62
62
  }
63
63
 
64
64
  .ticket-row.sold-out {
@@ -73,13 +73,13 @@
73
73
  .ticket-header {
74
74
  display: flex;
75
75
  align-items: center;
76
- gap: var(--tl-spacing-2, 0.5rem);
76
+ gap: var(--tl-spacing-2);
77
77
  flex-wrap: wrap;
78
78
  }
79
79
 
80
80
  .ticket-name {
81
81
  font-weight: 600;
82
- color: var(--tl-color-foreground, #111827);
82
+ color: var(--tl-color-foreground);
83
83
  font-size: 15px;
84
84
  }
85
85
 
@@ -91,43 +91,43 @@
91
91
  }
92
92
 
93
93
  .status-badge.limited {
94
- background-color: var(--tl-color-warning-bg, #fef3c7);
95
- color: var(--tl-color-warning, #d97706);
94
+ background-color: var(--tl-color-warning-bg);
95
+ color: var(--tl-color-warning);
96
96
  }
97
97
 
98
98
  .status-badge.sold-out {
99
- background-color: var(--tl-color-destructive-bg, #fee2e2);
100
- color: var(--tl-color-destructive, #ef4444);
99
+ background-color: var(--tl-color-destructive-bg);
100
+ color: var(--tl-color-destructive);
101
101
  }
102
102
 
103
103
  .ticket-description {
104
- margin: var(--tl-spacing-1, 0.25rem) 0 0;
104
+ margin: var(--tl-spacing-1) 0 0;
105
105
  font-size: 14px;
106
- color: var(--tl-color-muted-foreground, #6b7280);
106
+ color: var(--tl-color-muted-foreground);
107
107
  }
108
108
 
109
109
  .ticket-price {
110
- margin: var(--tl-spacing-1, 0.25rem) 0 0;
110
+ margin: var(--tl-spacing-1) 0 0;
111
111
  font-size: 14px;
112
112
  font-weight: 600;
113
- color: var(--tl-color-foreground, #111827);
113
+ color: var(--tl-color-foreground);
114
114
  }
115
115
 
116
116
  /* Quantity controls */
117
117
  .quantity-controls {
118
118
  display: flex;
119
119
  align-items: center;
120
- gap: var(--tl-spacing-2, 0.5rem);
121
- margin-left: var(--tl-spacing-4, 1rem);
120
+ gap: var(--tl-spacing-2);
121
+ margin-left: var(--tl-spacing-4);
122
122
  }
123
123
 
124
124
  .qty-btn {
125
125
  width: 32px;
126
126
  height: 32px;
127
- border-radius: var(--tl-radius-sm, 0.25rem);
128
- border: 1px solid var(--tl-color-border, #d1d5db);
129
- background: var(--tl-color-background, #ffffff);
130
- color: var(--tl-color-foreground, #111827);
127
+ border-radius: var(--tl-radius-sm);
128
+ border: 1px solid var(--tl-color-border);
129
+ background: var(--tl-color-background);
130
+ color: var(--tl-color-foreground);
131
131
  font-size: 18px;
132
132
  font-weight: 500;
133
133
  cursor: pointer;
@@ -138,8 +138,8 @@
138
138
  }
139
139
 
140
140
  .qty-btn:hover:not(:disabled) {
141
- background-color: var(--tl-color-muted, #f3f4f6);
142
- border-color: var(--tl-color-border-hover, #9ca3af);
141
+ background-color: var(--tl-color-muted);
142
+ border-color: var(--tl-color-border-hover);
143
143
  }
144
144
 
145
145
  .qty-btn:disabled {
@@ -152,7 +152,7 @@
152
152
  text-align: center;
153
153
  font-weight: 600;
154
154
  font-size: 16px;
155
- color: var(--tl-color-foreground, #111827);
155
+ color: var(--tl-color-foreground);
156
156
  }
157
157
 
158
158
  /* Success message */
@@ -160,12 +160,12 @@
160
160
  display: flex;
161
161
  align-items: center;
162
162
  justify-content: center;
163
- gap: var(--tl-spacing-2, 0.5rem);
164
- padding: var(--tl-spacing-3, 0.75rem);
165
- background-color: var(--tl-color-success, #10b981);
163
+ gap: var(--tl-spacing-2);
164
+ padding: var(--tl-spacing-3);
165
+ background-color: var(--tl-color-success);
166
166
  color: #ffffff;
167
- border-radius: var(--tl-radius-md, 0.5rem);
168
- margin-bottom: var(--tl-spacing-3, 0.75rem);
167
+ border-radius: var(--tl-radius-md);
168
+ margin-bottom: var(--tl-spacing-3);
169
169
  font-weight: 500;
170
170
  font-size: 14px;
171
171
  }
@@ -178,11 +178,11 @@
178
178
  /* Add to cart button */
179
179
  .add-to-cart-btn {
180
180
  width: 100%;
181
- padding: var(--tl-spacing-3, 0.75rem) var(--tl-spacing-4, 1rem);
182
- border-radius: var(--tl-radius-md, 0.5rem);
181
+ padding: var(--tl-spacing-3) var(--tl-spacing-4);
182
+ border-radius: var(--tl-radius-md);
183
183
  border: none;
184
- background-color: var(--tl-color-primary, #6366f1);
185
- color: var(--tl-color-primary-foreground, #ffffff);
184
+ background-color: var(--tl-color-primary);
185
+ color: var(--tl-color-primary-foreground);
186
186
  font-weight: 600;
187
187
  font-size: 16px;
188
188
  cursor: pointer;
@@ -199,7 +199,32 @@
199
199
 
200
200
  .add-to-cart-btn.disabled,
201
201
  .add-to-cart-btn:disabled {
202
- background-color: var(--tl-color-muted, #d1d5db);
203
- color: var(--tl-color-muted-foreground, #6b7280);
202
+ background-color: var(--tl-color-muted);
203
+ color: var(--tl-color-muted-foreground);
204
204
  cursor: not-allowed;
205
205
  }
206
+
207
+ /* Selection totals: only rendered when the surface quotes a fee, so the
208
+ buyer sees the booking fee before the tickets reach the cart. */
209
+ .selection-totals {
210
+ padding: var(--tl-spacing-3) 0;
211
+ border-top: 1px solid var(--tl-color-border);
212
+ }
213
+
214
+ .selection-totals .total-row {
215
+ display: flex;
216
+ justify-content: space-between;
217
+ align-items: center;
218
+ padding: var(--tl-spacing-1) 0;
219
+ font-size: 14px;
220
+ color: var(--tl-color-muted-foreground);
221
+ }
222
+
223
+ .selection-totals .total-row.total {
224
+ font-size: 16px;
225
+ font-weight: 700;
226
+ color: var(--tl-color-foreground);
227
+ padding-top: var(--tl-spacing-2);
228
+ border-top: 1px solid var(--tl-color-border);
229
+ margin-top: var(--tl-spacing-2);
230
+ }
@@ -1,5 +1,15 @@
1
1
  import { h } from "@stencil/core";
2
- import { getSDK, hasSDK, whenSDK } from "../../utils/sdk-connector";
2
+ import { createElement, ticketSelectorCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
4
+ /**
5
+ * The web harness for `ticketSelectorCore`.
6
+ *
7
+ * It renders and it forwards: every decision, every string and every sum is in
8
+ * `@ticketlayer/elements-core` (AGENTS.md, TKT-11). What is left here is the
9
+ * Stencil surface a host writes HTML against - the tag, the attributes, the
10
+ * three `tl*` events - plus the one thing that is genuinely web: finding the
11
+ * client the page published on `window.Ticketlayer`.
12
+ */
3
13
  export class TlTicketSelector {
4
14
  constructor() {
5
15
  /**
@@ -10,157 +20,106 @@ export class TlTicketSelector {
10
20
  * Button label
11
21
  */
12
22
  this.buttonLabel = 'Add to Cart';
13
- this.ticketTypes = [];
14
- this.quantities = {};
15
- this.loading = true;
16
- this.error = null;
17
- this.adding = false;
18
- this.addSuccess = false;
23
+ this.element = createElement(ticketSelectorCore, {
24
+ client: () => getSDK(),
25
+ // What "the client is up" means on the web. The core waits on it in its
26
+ // own mount; the harness no longer decides when to load (TKT-197).
27
+ clientReady: () => whenSDK(),
28
+ emit: (event, detail) => this.forward(event, detail),
29
+ });
30
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
31
+ this.view = this.element.view;
19
32
  }
20
- watchPropsChange() {
21
- this.loadTicketTypes();
33
+ /**
34
+ * The fee the surface has quoted for this selection. Core state, exposed here
35
+ * because it is set from outside the element and not from an attribute: there
36
+ * is no quote operation on the buyer-facing surface yet, so it stays null and
37
+ * no fee row is rendered.
38
+ */
39
+ get feeQuote() {
40
+ return this.element.state.feeQuote;
22
41
  }
23
- async componentWillLoad() {
24
- await this.waitForSDKAndLoad();
42
+ set feeQuote(quote) {
43
+ this.element.actions.setFeeQuote(quote);
25
44
  }
26
45
  /**
27
- * Wait for SDK to be ready, then load ticket types
46
+ * A new event or occurrence is a new subject: the element is torn down and
47
+ * mounted again, so whatever the core set up for the old one is undone
48
+ * before anything is set up for the new one.
28
49
  */
29
- async waitForSDKAndLoad() {
30
- if (!this.eventId || !this.occurrenceId) {
31
- return;
32
- }
33
- try {
34
- await whenSDK();
35
- await this.loadTicketTypes();
36
- }
37
- catch (err) {
38
- this.error = err instanceof Error ? err.message : 'SDK not initialized.';
39
- this.loading = false;
40
- }
50
+ watchPropsChange() {
51
+ this.syncProps();
52
+ this.remount();
41
53
  }
42
- async loadTicketTypes() {
43
- try {
44
- console.log('[tl-ticket-selector] Loading ticket types...', {
45
- eventId: this.eventId,
46
- occurrenceId: this.occurrenceId
47
- });
48
- this.loading = true;
49
- this.error = null;
50
- const sdk = getSDK();
51
- const eventsManager = sdk.getEventsManager();
52
- const types = await eventsManager.getTicketTypes(this.eventId, this.occurrenceId);
53
- console.log('[tl-ticket-selector] Ticket types loaded:', types.length);
54
- this.ticketTypes = types;
55
- // Initialise quantities to 0
56
- const initialQuantities = {};
57
- types.forEach((t) => {
58
- initialQuantities[t.id] = 0;
59
- });
60
- this.quantities = initialQuantities;
61
- }
62
- catch (err) {
63
- console.error('[tl-ticket-selector] Failed to load ticket types:', err);
64
- this.error = err instanceof Error ? err.message : 'Failed to load tickets';
65
- this.tlError.emit({ message: this.error });
66
- }
67
- finally {
68
- this.loading = false;
69
- }
54
+ syncProps() {
55
+ this.element.setProps({
56
+ eventId: this.eventId,
57
+ occurrenceId: this.occurrenceId,
58
+ showButton: this.showButton,
59
+ buttonLabel: this.buttonLabel,
60
+ });
70
61
  }
71
- handleQuantityChange(ticketTypeId, delta) {
72
- const ticketType = this.ticketTypes.find(t => t.id === ticketTypeId);
73
- if (!ticketType)
74
- return;
75
- const currentQty = this.quantities[ticketTypeId] || 0;
76
- const newQty = Math.max(0, Math.min(ticketType.maxPerOrder, currentQty + delta));
77
- this.quantities = {
78
- ...this.quantities,
79
- [ticketTypeId]: newQty,
80
- };
81
- const total = this.calculateTotal();
82
- this.tlQuantityChange.emit({ ticketTypeId, quantity: newQty, total });
62
+ /**
63
+ * On screen: render what the core says and put the core to work. What "to
64
+ * work" means is the core's own business - for this element, waiting for the
65
+ * client and reading the ticket types.
66
+ *
67
+ * `connectedCallback` and not `componentWillLoad` because this runs again
68
+ * when the element is moved in the DOM, and that is exactly when a mount and
69
+ * its teardown have to pair up.
70
+ */
71
+ connectedCallback() {
72
+ this.syncProps();
73
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
74
+ this.view = view;
75
+ }));
76
+ this.view = this.element.view;
77
+ this.teardown = this.element.mount();
83
78
  }
84
- calculateTotal() {
85
- return this.ticketTypes.reduce((sum, tt) => {
86
- return sum + (this.quantities[tt.id] || 0) * tt.price;
87
- }, 0);
79
+ /**
80
+ * Off screen: run the mount's teardown, and nothing more. `destroy()` is
81
+ * terminal and Stencil calls this for a move as well as a removal, so
82
+ * destroying here would leave a re-inserted element frozen. The instance
83
+ * holds no listener and no subscription once these two have run.
84
+ */
85
+ disconnectedCallback() {
86
+ this.teardown?.();
87
+ this.teardown = undefined;
88
+ this.unsubscribe?.();
89
+ this.unsubscribe = undefined;
88
90
  }
89
- getTotalItems() {
90
- return Object.values(this.quantities).reduce((sum, qty) => sum + qty, 0);
91
+ remount() {
92
+ this.teardown?.();
93
+ this.teardown = this.element.mount();
91
94
  }
92
- async handleAddToCart() {
93
- const itemsToAdd = Object.entries(this.quantities)
94
- .filter(([_, qty]) => qty > 0)
95
- .map(([ticketTypeId, quantity]) => ({ ticketTypeId, quantity }));
96
- if (itemsToAdd.length === 0)
97
- return;
98
- this.adding = true;
99
- this.addSuccess = false;
100
- try {
101
- if (hasSDK()) {
102
- const sdk = getSDK();
103
- const cartManager = sdk.getCartManager?.();
104
- if (cartManager) {
105
- for (const item of itemsToAdd) {
106
- await cartManager.addItem(item.ticketTypeId, item.quantity, this.occurrenceId);
107
- }
108
- console.log('[tl-ticket-selector] Items added to cart');
109
- }
110
- }
111
- // Reset quantities after adding
112
- const resetQuantities = {};
113
- this.ticketTypes.forEach(t => {
114
- resetQuantities[t.id] = 0;
115
- });
116
- this.quantities = resetQuantities;
117
- this.addSuccess = true;
118
- // Clear success message after 3 seconds
119
- setTimeout(() => {
120
- this.addSuccess = false;
121
- }, 3000);
122
- this.tlAddToCart.emit({ items: itemsToAdd });
95
+ forward(event, detail) {
96
+ if (event === 'tlAddToCart')
97
+ this.tlAddToCart.emit(detail);
98
+ else if (event === 'tlQuantityChange') {
99
+ this.tlQuantityChange.emit(detail);
123
100
  }
124
- catch (err) {
125
- console.error('[tl-ticket-selector] Failed to add to cart:', err);
126
- this.error = err instanceof Error ? err.message : 'Failed to add to cart';
127
- this.tlError.emit({ message: this.error });
128
- }
129
- finally {
130
- this.adding = false;
131
- }
132
- }
133
- formatPrice(priceInPence, currency = 'GBP') {
134
- return new Intl.NumberFormat('en-GB', {
135
- style: 'currency',
136
- currency,
137
- }).format(priceInPence / 100);
101
+ else if (event === 'tlError')
102
+ this.tlError.emit(detail);
138
103
  }
139
104
  renderSkeleton() {
140
105
  return (h("div", { class: "skeleton-container" }, h("div", { class: "skeleton-row" }), h("div", { class: "skeleton-row" }), h("div", { class: "skeleton-row" })));
141
106
  }
142
107
  renderError() {
143
- return (h("div", { class: "error-state" }, h("p", { class: "error-message" }, this.error)));
108
+ return (h("div", { class: "error-state" }, h("p", { class: "error-message" }, this.view.state.error)));
144
109
  }
145
- renderTicketRow(tt) {
146
- const isSoldOut = tt.availabilityStatus === 'sold_out';
147
- const qty = this.quantities[tt.id] || 0;
148
- return (h("div", { class: `ticket-row ${isSoldOut ? 'sold-out' : ''}`, key: tt.id }, h("div", { class: "ticket-info" }, h("div", { class: "ticket-header" }, h("span", { class: "ticket-name" }, tt.name), tt.availabilityStatus === 'limited' && (h("span", { class: "status-badge limited" }, "Limited")), isSoldOut && (h("span", { class: "status-badge sold-out" }, "Sold Out"))), tt.description && (h("p", { class: "ticket-description" }, tt.description)), h("p", { class: "ticket-price" }, this.formatPrice(tt.price, tt.currency))), h("div", { class: "quantity-controls" }, h("button", { class: "qty-btn", onClick: () => this.handleQuantityChange(tt.id, -1), disabled: isSoldOut || qty === 0, "aria-label": "Decrease quantity" }, "\u2212"), h("span", { class: "qty-value" }, qty), h("button", { class: "qty-btn", onClick: () => this.handleQuantityChange(tt.id, 1), disabled: isSoldOut || qty >= tt.maxPerOrder, "aria-label": "Increase quantity" }, "+"))));
110
+ renderTicketRow(row) {
111
+ const strings = this.view.strings;
112
+ return (h("div", { class: `ticket-row ${row.soldOut ? 'sold-out' : ''}`, key: row.id }, h("div", { class: "ticket-info" }, h("div", { class: "ticket-header" }, h("span", { class: "ticket-name" }, row.name), row.limited && h("span", { class: "status-badge limited" }, strings.limited), row.soldOut && h("span", { class: "status-badge sold-out" }, strings.soldOut)), row.description && h("p", { class: "ticket-description" }, row.description), h("p", { class: "ticket-price" }, row.priceLabel)), h("div", { class: "quantity-controls" }, h("button", { class: "qty-btn", onClick: () => this.element.actions.changeQuantity(row.id, -1), disabled: !row.canDecrease, "aria-label": strings.decrease }, "\u2212"), h("span", { class: "qty-value" }, row.quantity), h("button", { class: "qty-btn", onClick: () => this.element.actions.changeQuantity(row.id, 1), disabled: !row.canIncrease, "aria-label": strings.increase }, "+"))));
149
113
  }
150
114
  render() {
151
- if (this.loading) {
115
+ const { status, state, strings, derived } = this.view;
116
+ if (status === 'loading') {
152
117
  return this.renderSkeleton();
153
118
  }
154
- if (this.error) {
119
+ if (status === 'error') {
155
120
  return this.renderError();
156
121
  }
157
- const totalItems = this.getTotalItems();
158
- const totalPrice = this.calculateTotal();
159
- return (h("div", { class: "ticket-selector" }, h("div", { class: "ticket-list" }, this.ticketTypes.map(tt => this.renderTicketRow(tt))), this.addSuccess && (h("div", { class: "success-message" }, h("svg", { class: "success-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 13l4 4L19 7" })), "Added to cart!")), this.showButton && (h("button", { class: `add-to-cart-btn ${totalItems === 0 ? 'disabled' : ''}`, onClick: () => this.handleAddToCart(), disabled: totalItems === 0 || this.adding }, this.adding
160
- ? 'Adding...'
161
- : totalItems === 0
162
- ? 'Select Tickets'
163
- : `${this.buttonLabel} - ${this.formatPrice(totalPrice)}`))));
122
+ return (h("div", { class: "ticket-selector" }, h("div", { class: "ticket-list" }, derived.rows.map((row) => this.renderTicketRow(row))), derived.showTotals && (h("div", { class: "selection-totals" }, h("div", { class: "total-row" }, h("span", null, strings.ticketsRow), h("span", null, derived.subtotalLabel)), h("div", { class: "total-row fee" }, h("span", null, derived.totals.feeLabel), h("span", null, derived.feeLabel)), h("div", { class: "total-row total" }, h("span", null, strings.totalRow), h("span", null, derived.totalLabel)))), state.addSuccess && (h("div", { class: "success-message" }, h("svg", { class: "success-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 13l4 4L19 7" })), strings.added)), this.showButton && (h("button", { class: `add-to-cart-btn ${derived.totalItems === 0 ? 'disabled' : ''}`, onClick: () => this.element.actions.addToCart(), disabled: derived.actionDisabled }, derived.actionLabel))));
164
123
  }
165
124
  static get is() { return "tl-ticket-selector"; }
166
125
  static get encapsulation() { return "shadow"; }
@@ -258,12 +217,7 @@ export class TlTicketSelector {
258
217
  }
259
218
  static get states() {
260
219
  return {
261
- "ticketTypes": {},
262
- "quantities": {},
263
- "loading": {},
264
- "error": {},
265
- "adding": {},
266
- "addSuccess": {}
220
+ "view": {}
267
221
  };
268
222
  }
269
223
  static get events() {
@@ -327,6 +281,12 @@ export class TlTicketSelector {
327
281
  }, {
328
282
  "propName": "occurrenceId",
329
283
  "methodName": "watchPropsChange"
284
+ }, {
285
+ "propName": "showButton",
286
+ "methodName": "syncProps"
287
+ }, {
288
+ "propName": "buttonLabel",
289
+ "methodName": "syncProps"
330
290
  }];
331
291
  }
332
292
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The money rows a cart-shaped view renders.
3
+ *
4
+ * The rule and the arithmetic moved to `@ticketlayer/elements-core` with the
5
+ * core split (TKT-68): it is behaviour, and behaviour lives in a core so every
6
+ * harness derives the same figures. This file stays as the import path the
7
+ * components and their specs already use.
8
+ */
9
+ export { cartTotals, expressesFee, minorUnits, selectionTotals, FEE_LABEL_FALLBACK, } from '@ticketlayer/elements-core';
@@ -1,101 +1,17 @@
1
- /** Money arrives as minor-unit integers or decimal strings ("50.00"). */
2
- export function formatPrice(value, currency = 'GBP') {
3
- if (value === null || value === undefined || value === '')
4
- return '';
5
- const fmt = new Intl.NumberFormat('en-GB', { style: 'currency', currency });
6
- if (typeof value === 'number')
7
- return fmt.format(value / 100);
8
- const n = Number(value);
9
- return Number.isNaN(n) ? String(value) : fmt.format(n);
10
- }
11
- export function formatDateTime(iso) {
12
- return new Date(iso).toLocaleDateString('en-GB', {
13
- weekday: 'short',
14
- day: 'numeric',
15
- month: 'short',
16
- year: 'numeric',
17
- hour: '2-digit',
18
- minute: '2-digit',
19
- });
20
- }
21
- export function formatDate(iso) {
22
- return new Date(iso).toLocaleDateString('en-GB', {
23
- day: 'numeric',
24
- month: 'short',
25
- year: 'numeric',
26
- });
27
- }
28
- /** Line items that are tickets (fees, taxes and discounts have their own type). */
29
- export function ticketItems(order) {
30
- return (order.items ?? []).filter((i) => !i.type || i.type === 'ticket');
31
- }
32
- export function ticketCount(order) {
33
- const fromItems = ticketItems(order).reduce((sum, i) => sum + (Number(i.quantity) || 0), 0);
34
- return fromItems || order.tickets?.length || 0;
35
- }
36
- /** Event name as the order view exposes it: the first ticket line's name. */
37
- export function orderEventName(order) {
38
- return order.eventName || ticketItems(order)[0]?.name || order.items?.[0]?.name || null;
39
- }
40
- export function orderCustomerEmail(order, customer) {
41
- const fromOrder = order?.customerEmail || order?.customer?.email;
42
- if (fromOrder)
43
- return fromOrder;
44
- const c = customer;
45
- return c?.email || null;
46
- }
47
- export function statusLabel(status) {
48
- if (!status)
49
- return '';
50
- return status.replace(/_/g, ' ').replace(/^\w/, (m) => m.toUpperCase());
51
- }
52
- export function statusTone(status) {
53
- switch (status) {
54
- case 'confirmed':
55
- return 'success';
56
- case 'completed':
57
- case 'fulfilled':
58
- return 'info';
59
- case 'pending':
60
- case 'processing':
61
- return 'warning';
62
- case 'cancelled':
63
- case 'expired':
64
- case 'failed':
65
- return 'error';
66
- default:
67
- return 'muted';
68
- }
69
- }
70
- export function errorMessage(err, fallback) {
71
- return err instanceof Error && err.message ? err.message : fallback;
72
- }
73
- /** True for the "not signed in" family of API errors (401/403, NO_SESSION, NOT_AUTHENTICATED). */
74
- export function isAuthError(err) {
75
- const e = err;
76
- if (!e || typeof e !== 'object')
77
- return false;
78
- return e.status === 401 || e.status === 403 || e.code === 'NO_SESSION' || e.code === 'NOT_AUTHENTICATED';
79
- }
80
1
  /**
81
- * Find the event and start time for an occurrence id. The order view only
82
- * carries the occurrence id, so this walks the channel's listings (cached by
83
- * live-api) until one owns the occurrence. Best effort: null when not found.
2
+ * The order helpers, as the components have always imported them.
3
+ *
4
+ * Everything here now lives in `@ticketlayer/elements-core`
5
+ * (`src/orders.ts`): the order shape, the money rows, the status tone, the
6
+ * date and price formatting and the occurrence lookup all moved across with
7
+ * the split of `tl-my-orders` and `tl-order-confirmation` (TKT-69), so a
8
+ * native harness answers the same questions the same way. This module stays as
9
+ * the import path, unchanged for callers, because `tl-order-tickets` and
10
+ * `tl-ticket` are not split yet and read these helpers from here.
11
+ *
12
+ * `resolveOccurrence` is the one signature that moved: it takes the core's
13
+ * `ElementClient` rather than this package's `TicketlayerSDK`. A
14
+ * `TicketlayerSDK` satisfies that shape, so every existing call still
15
+ * compiles, which is why it is re-exported rather than wrapped.
84
16
  */
85
- export async function resolveOccurrence(sdk, occurrenceId, maxEvents = 12) {
86
- const events = sdk.getEventsManager();
87
- const listing = await events.list({ limit: maxEvents });
88
- const candidates = listing.items.slice(0, maxEvents);
89
- for (const candidate of candidates) {
90
- const detail = (await events.get(candidate.id).catch(() => null));
91
- const occ = detail?.occurrences?.find((o) => o.id === occurrenceId);
92
- if (occ) {
93
- return {
94
- eventId: detail?.id ?? candidate.id,
95
- eventName: detail?.name ?? candidate.name ?? '',
96
- startsAt: occ.startsAt ?? null,
97
- };
98
- }
99
- }
100
- return null;
101
- }
17
+ export { errorMessage, formatDate, formatDateTime, formatPrice, isAuthError, orderCustomerEmail, orderEventName, orderTotals, resolveOccurrence, statusLabel, statusTone, ticketCount, ticketItems, totalsAgree, } from '@ticketlayer/elements-core';