@ticketlayer/elements 0.4.0 → 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 (161) hide show
  1. package/dist/cjs/{index-I9CoYv3k.js → index-DanPUX0s.js} +1 -1
  2. package/dist/cjs/index.cjs.js +2 -1
  3. package/dist/cjs/loader.cjs.js +2 -2
  4. package/dist/cjs/ticket-selector-BYVL13k_.js +3623 -0
  5. package/dist/cjs/ticketlayer-elements.cjs.js +2 -2
  6. package/dist/cjs/tl-buy-modal.cjs.entry.js +3 -3
  7. package/dist/cjs/tl-buy-tickets-button.cjs.entry.js +89 -74
  8. package/dist/cjs/tl-buy-tickets-wrapper.cjs.entry.js +61 -30
  9. package/dist/cjs/tl-cart-badge.cjs.entry.js +74 -61
  10. package/dist/cjs/tl-cart-drawer.cjs.entry.js +51 -23
  11. package/dist/cjs/tl-cart.cjs.entry.js +71 -103
  12. package/dist/cjs/tl-checkout-wrapper.cjs.entry.js +51 -29
  13. package/dist/cjs/tl-event-card.cjs.entry.js +43 -105
  14. package/dist/cjs/tl-event-list.cjs.entry.js +72 -35
  15. package/dist/cjs/tl-login.cjs.entry.js +74 -141
  16. package/dist/cjs/tl-my-orders.cjs.entry.js +52 -92
  17. package/dist/cjs/tl-occurrence-selector_2.cjs.entry.js +153 -921
  18. package/dist/cjs/tl-order-confirmation.cjs.entry.js +71 -107
  19. package/dist/cjs/tl-order-tickets_2.cjs.entry.js +48 -74
  20. package/dist/cjs/tl-promo-code.cjs.entry.js +50 -47
  21. package/dist/cjs/tl-seat-map.cjs.entry.js +3 -3
  22. package/dist/cjs/web-query-Ce73jB4k.js +16 -0
  23. package/dist/collection/components/tl-buy-modal/tl-buy-modal.js +2 -2
  24. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.css +37 -0
  25. package/dist/collection/components/tl-buy-tickets-button/tl-buy-tickets-button.js +105 -79
  26. package/dist/collection/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.js +73 -30
  27. package/dist/collection/components/tl-cart/tl-cart.js +82 -107
  28. package/dist/collection/components/tl-cart-badge/tl-cart-badge.js +62 -62
  29. package/dist/collection/components/tl-cart-drawer/tl-cart-drawer.js +56 -24
  30. package/dist/collection/components/tl-checkout-wrapper/tl-checkout-wrapper.js +61 -29
  31. package/dist/collection/components/tl-event-card/event-card-adapter.js +11 -23
  32. package/dist/collection/components/tl-event-card/tl-event-card.js +51 -84
  33. package/dist/collection/components/tl-event-list/tl-event-list.js +82 -38
  34. package/dist/collection/components/tl-login/tl-login.js +83 -147
  35. package/dist/collection/components/tl-my-orders/tl-my-orders.js +58 -96
  36. package/dist/collection/components/tl-occurrence-selector/tl-occurrence-selector.js +137 -516
  37. package/dist/collection/components/tl-order-confirmation/tl-order-confirmation.js +81 -115
  38. package/dist/collection/components/tl-order-tickets/tl-order-tickets.js +57 -92
  39. package/dist/collection/components/tl-promo-code/tl-promo-code.js +55 -50
  40. package/dist/collection/components/tl-seat-map/tl-seat-map.js +2 -2
  41. package/dist/collection/components/tl-ticket-selector/tl-ticket-selector.js +34 -20
  42. package/dist/collection/utils/order-summary.js +15 -135
  43. package/dist/collection/utils/testing/deep-text.js +29 -0
  44. package/dist/collection/utils/testing/fixtures.js +290 -0
  45. package/dist/collection/utils/testing/mock-sdk.js +13 -2
  46. package/dist/collection/utils/web-query.js +12 -0
  47. package/dist/collection/utils/web-storage.js +22 -0
  48. package/dist/components/ticket-selector.js +1 -0
  49. package/dist/components/tl-buy-modal.js +1 -1
  50. package/dist/components/tl-buy-tickets-button.js +1 -1
  51. package/dist/components/tl-buy-tickets-wrapper2.js +1 -1
  52. package/dist/components/tl-cart-badge.js +1 -1
  53. package/dist/components/tl-cart-drawer.js +1 -1
  54. package/dist/components/tl-cart2.js +1 -1
  55. package/dist/components/tl-checkout-wrapper2.js +1 -1
  56. package/dist/components/tl-event-card2.js +1 -1
  57. package/dist/components/tl-event-list.js +1 -1
  58. package/dist/components/tl-login2.js +1 -1
  59. package/dist/components/tl-my-orders.js +1 -1
  60. package/dist/components/tl-occurrence-selector2.js +1 -1
  61. package/dist/components/tl-order-confirmation.js +1 -1
  62. package/dist/components/tl-order-tickets2.js +1 -1
  63. package/dist/components/tl-promo-code.js +1 -1
  64. package/dist/components/tl-seat-map.js +1 -1
  65. package/dist/components/tl-ticket-selector2.js +1 -1
  66. package/dist/components/web-query.js +1 -0
  67. package/dist/elements.docs.json +34 -34
  68. package/dist/esm/{index-OoYV1faY.js → index-imfxp765.js} +1 -1
  69. package/dist/esm/index.js +2 -1
  70. package/dist/esm/loader.js +3 -3
  71. package/dist/esm/ticket-selector-BwChnHVC.js +3606 -0
  72. package/dist/esm/ticketlayer-elements.js +3 -3
  73. package/dist/esm/tl-buy-modal.entry.js +3 -3
  74. package/dist/esm/tl-buy-tickets-button.entry.js +90 -75
  75. package/dist/esm/tl-buy-tickets-wrapper.entry.js +62 -31
  76. package/dist/esm/tl-cart-badge.entry.js +75 -62
  77. package/dist/esm/tl-cart-drawer.entry.js +52 -24
  78. package/dist/esm/tl-cart.entry.js +72 -104
  79. package/dist/esm/tl-checkout-wrapper.entry.js +52 -30
  80. package/dist/esm/tl-event-card.entry.js +44 -106
  81. package/dist/esm/tl-event-list.entry.js +73 -36
  82. package/dist/esm/tl-login.entry.js +75 -142
  83. package/dist/esm/tl-my-orders.entry.js +53 -93
  84. package/dist/esm/tl-occurrence-selector_2.entry.js +152 -920
  85. package/dist/esm/tl-order-confirmation.entry.js +72 -108
  86. package/dist/esm/tl-order-tickets_2.entry.js +49 -75
  87. package/dist/esm/tl-promo-code.entry.js +51 -48
  88. package/dist/esm/tl-seat-map.entry.js +3 -3
  89. package/dist/esm/web-query-Bdbb2lk5.js +14 -0
  90. package/dist/ticketlayer-elements/index.esm.js +1 -1
  91. package/dist/ticketlayer-elements/p-45923d2c.entry.js +1 -0
  92. package/dist/ticketlayer-elements/p-492adc77.entry.js +1 -0
  93. package/dist/ticketlayer-elements/p-4cfc9e53.entry.js +1 -0
  94. package/dist/ticketlayer-elements/{p-ac264adc.entry.js → p-5135630e.entry.js} +1 -1
  95. package/dist/ticketlayer-elements/p-5bbce3b5.entry.js +1 -0
  96. package/dist/ticketlayer-elements/p-7d547721.entry.js +1 -0
  97. package/dist/ticketlayer-elements/p-7fe7b6ba.entry.js +1 -0
  98. package/dist/ticketlayer-elements/p-8122f0c1.entry.js +1 -0
  99. package/dist/ticketlayer-elements/p-98d40869.entry.js +1 -0
  100. package/dist/ticketlayer-elements/p-Bdbb2lk5.js +1 -0
  101. package/dist/ticketlayer-elements/p-BwChnHVC.js +1 -0
  102. package/dist/ticketlayer-elements/p-abec290a.entry.js +1 -0
  103. package/dist/ticketlayer-elements/p-ac79a3f6.entry.js +1 -0
  104. package/dist/ticketlayer-elements/p-d01f4429.entry.js +1 -0
  105. package/dist/ticketlayer-elements/p-d7ffaf46.entry.js +1 -0
  106. package/dist/ticketlayer-elements/p-e7170f3c.entry.js +1 -0
  107. package/dist/ticketlayer-elements/p-e787e862.entry.js +1 -0
  108. package/dist/ticketlayer-elements/p-f1ff931f.entry.js +1 -0
  109. package/dist/ticketlayer-elements/p-imfxp765.js +3 -0
  110. package/dist/ticketlayer-elements/ticketlayer-elements.css +1 -1
  111. package/dist/ticketlayer-elements/ticketlayer-elements.esm.js +1 -1
  112. package/dist/types/components/tl-buy-tickets-button/tl-buy-tickets-button.d.ts +40 -8
  113. package/dist/types/components/tl-buy-tickets-wrapper/tl-buy-tickets-wrapper.d.ts +20 -1
  114. package/dist/types/components/tl-cart/tl-cart.d.ts +40 -42
  115. package/dist/types/components/tl-cart-badge/tl-cart-badge.d.ts +22 -7
  116. package/dist/types/components/tl-cart-drawer/tl-cart-drawer.d.ts +28 -5
  117. package/dist/types/components/tl-checkout-wrapper/tl-checkout-wrapper.d.ts +18 -1
  118. package/dist/types/components/tl-event-card/event-card-adapter.d.ts +9 -20
  119. package/dist/types/components/tl-event-card/tl-event-card.d.ts +25 -28
  120. package/dist/types/components/tl-event-list/tl-event-list.d.ts +46 -6
  121. package/dist/types/components/tl-login/tl-login.d.ts +68 -22
  122. package/dist/types/components/tl-my-orders/tl-my-orders.d.ts +30 -12
  123. package/dist/types/components/tl-occurrence-selector/tl-occurrence-selector.d.ts +73 -161
  124. package/dist/types/components/tl-order-confirmation/tl-order-confirmation.d.ts +48 -15
  125. package/dist/types/components/tl-order-tickets/tl-order-tickets.d.ts +29 -31
  126. package/dist/types/components/tl-promo-code/tl-promo-code.d.ts +28 -10
  127. package/dist/types/components/tl-ticket/tl-ticket.d.ts +2 -11
  128. package/dist/types/components/tl-ticket-selector/tl-ticket-selector.d.ts +22 -4
  129. package/dist/types/components.d.ts +270 -62
  130. package/dist/types/utils/order-summary.d.ts +14 -97
  131. package/dist/types/utils/testing/deep-text.d.ts +13 -0
  132. package/dist/types/utils/testing/fixtures.d.ts +225 -0
  133. package/dist/types/utils/testing/mock-sdk.d.ts +14 -1
  134. package/dist/types/utils/web-query.d.ts +18 -0
  135. package/dist/types/utils/web-storage.d.ts +16 -0
  136. package/elements.catalogue.json +602 -50
  137. package/package.json +8 -6
  138. package/dist/cjs/money-BtmM2gt-.js +0 -118
  139. package/dist/cjs/order-summary-X3TlZxc2.js +0 -150
  140. package/dist/components/money.js +0 -1
  141. package/dist/components/order-summary.js +0 -1
  142. package/dist/esm/money-4ig0-WB6.js +0 -112
  143. package/dist/esm/order-summary-DiDtvfZM.js +0 -136
  144. package/dist/ticketlayer-elements/p-0814420f.entry.js +0 -1
  145. package/dist/ticketlayer-elements/p-0acabc26.entry.js +0 -1
  146. package/dist/ticketlayer-elements/p-109a3feb.entry.js +0 -1
  147. package/dist/ticketlayer-elements/p-1a4ba71c.entry.js +0 -1
  148. package/dist/ticketlayer-elements/p-318965c1.entry.js +0 -1
  149. package/dist/ticketlayer-elements/p-347c7fd1.entry.js +0 -1
  150. package/dist/ticketlayer-elements/p-3649c26d.entry.js +0 -1
  151. package/dist/ticketlayer-elements/p-40260cc9.entry.js +0 -1
  152. package/dist/ticketlayer-elements/p-4ig0-WB6.js +0 -1
  153. package/dist/ticketlayer-elements/p-55fd2f9f.entry.js +0 -1
  154. package/dist/ticketlayer-elements/p-62ee659b.entry.js +0 -1
  155. package/dist/ticketlayer-elements/p-7d4ecc2b.entry.js +0 -1
  156. package/dist/ticketlayer-elements/p-7f0abf95.entry.js +0 -1
  157. package/dist/ticketlayer-elements/p-BXpVGW51.js +0 -1
  158. package/dist/ticketlayer-elements/p-OoYV1faY.js +0 -3
  159. package/dist/ticketlayer-elements/p-a3c1b26c.entry.js +0 -1
  160. package/dist/ticketlayer-elements/p-aeb4c6fb.entry.js +0 -1
  161. package/dist/ticketlayer-elements/p-ba62541f.entry.js +0 -1
@@ -1,11 +1,16 @@
1
1
  import { h, Host } from "@stencil/core";
2
- import { getSDK, hasSDK } from "../../utils/sdk-connector";
2
+ import { checkoutWrapperCore, createElement, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
3
4
  /**
4
5
  * @component tl-checkout-wrapper
5
6
  *
6
7
  * Wraps any content and opens the Ticketlayer checkout modal when clicked.
7
8
  * Uses the SDK's modal manager which handles the iframe overlay.
8
9
  *
10
+ * The web harness for `checkoutWrapperCore`: it renders, it forwards, and it
11
+ * decides only which key press counts as activation. Everything else is in
12
+ * `@ticketlayer/elements-core` (AGENTS.md, TKT-11).
13
+ *
9
14
  * @slot - Content to display (typically a checkout button)
10
15
  *
11
16
  * @example
@@ -19,40 +24,57 @@ export class TlCheckoutWrapper {
19
24
  * Whether the wrapper is disabled
20
25
  */
21
26
  this.disabled = false;
27
+ this.element = createElement(checkoutWrapperCore, {
28
+ client: () => getSDK(),
29
+ clientReady: () => whenSDK(),
30
+ emit: (event, detail) => this.forward(event, detail),
31
+ });
32
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
33
+ this.view = this.element.view;
22
34
  this.handleClick = () => {
23
- if (this.disabled)
24
- return;
25
- if (!hasSDK()) {
26
- console.error('[tl-checkout-wrapper] SDK not initialized. Call setSDK(ticketlayer) first.');
27
- this.tlError.emit({ message: 'SDK not initialized' });
28
- return;
29
- }
30
- const sdk = getSDK();
31
- // Open the checkout modal via SDK
32
- sdk.checkout.openModal({
33
- onComplete: (order) => {
34
- this.tlComplete.emit(order);
35
- },
36
- onError: (err) => {
37
- this.tlError.emit({ message: err.message });
38
- },
39
- onClose: () => {
40
- this.tlClose.emit();
41
- },
42
- });
35
+ this.element.actions.activate();
43
36
  };
37
+ /** Enter and Space are a press on the web. The core knows nothing of keys. */
44
38
  this.handleKeyDown = (e) => {
45
39
  if (e.key === 'Enter' || e.key === ' ') {
46
40
  e.preventDefault();
47
- this.handleClick();
41
+ this.element.actions.activate();
48
42
  }
49
43
  };
50
44
  }
45
+ syncProps() {
46
+ this.element.setProps({ disabled: this.disabled });
47
+ }
48
+ connectedCallback() {
49
+ this.syncProps();
50
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
51
+ this.view = view;
52
+ }));
53
+ this.view = this.element.view;
54
+ // Nothing to do on mount, and mounted anyway: a harness calls mount()
55
+ // unconditionally and pairs it with its teardown (TKT-197).
56
+ this.teardown = this.element.mount();
57
+ }
58
+ disconnectedCallback() {
59
+ this.teardown?.();
60
+ this.teardown = undefined;
61
+ this.unsubscribe?.();
62
+ this.unsubscribe = undefined;
63
+ }
64
+ forward(event, detail) {
65
+ if (event === 'tlComplete')
66
+ this.tlComplete.emit(detail);
67
+ else if (event === 'tlError')
68
+ this.tlError.emit(detail);
69
+ else if (event === 'tlClose')
70
+ this.tlClose.emit();
71
+ }
51
72
  render() {
52
- return (h(Host, { key: 'ebaae5dd6710ffc5353ece4327012df0332fdbb7', class: {
73
+ const { interactive } = this.view.derived;
74
+ return (h(Host, { key: 'b261046c2627825258a5905662a79f436d713683', class: {
53
75
  'tl-checkout-wrapper': true,
54
- 'tl-checkout-wrapper--disabled': this.disabled,
55
- }, onClick: this.handleClick, onKeyDown: this.handleKeyDown, role: "button", tabindex: this.disabled ? -1 : 0, "aria-disabled": this.disabled ? 'true' : null }, h("slot", { key: '0118d9921df5af1ae2376fd4135bac487aac7ebd' })));
76
+ 'tl-checkout-wrapper--disabled': !interactive,
77
+ }, onClick: this.handleClick, onKeyDown: this.handleKeyDown, role: "button", tabindex: interactive ? 0 : -1, "aria-disabled": interactive ? null : 'true' }, h("slot", { key: '5be08d5007a755e3d88a06ae89c08e082e4ba052' })));
56
78
  }
57
79
  static get is() { return "tl-checkout-wrapper"; }
58
80
  static get originalStyleUrls() {
@@ -89,10 +111,15 @@ export class TlCheckoutWrapper {
89
111
  }
90
112
  };
91
113
  }
114
+ static get states() {
115
+ return {
116
+ "view": {}
117
+ };
118
+ }
92
119
  static get events() {
93
120
  return [{
94
121
  "method": "tlComplete",
95
- "name": "tl-complete",
122
+ "name": "tlComplete",
96
123
  "bubbles": true,
97
124
  "cancelable": true,
98
125
  "composed": true,
@@ -114,7 +141,7 @@ export class TlCheckoutWrapper {
114
141
  }
115
142
  }, {
116
143
  "method": "tlError",
117
- "name": "tl-error",
144
+ "name": "tlError",
118
145
  "bubbles": true,
119
146
  "cancelable": true,
120
147
  "composed": true,
@@ -129,7 +156,7 @@ export class TlCheckoutWrapper {
129
156
  }
130
157
  }, {
131
158
  "method": "tlClose",
132
- "name": "tl-close",
159
+ "name": "tlClose",
133
160
  "bubbles": true,
134
161
  "cancelable": true,
135
162
  "composed": true,
@@ -144,5 +171,10 @@ export class TlCheckoutWrapper {
144
171
  }
145
172
  }];
146
173
  }
147
- static get elementRef() { return "el"; }
174
+ static get watchers() {
175
+ return [{
176
+ "propName": "disabled",
177
+ "methodName": "syncProps"
178
+ }];
179
+ }
148
180
  }
@@ -1,23 +1,11 @@
1
- /** Adapt an event detail to the card shape (the list adapter lives in the SDK connector). */
2
- export function toEventCardEvent(detail) {
3
- const occurrences = detail.occurrences ?? [];
4
- const starts = occurrences
5
- .map((o) => o.startsAt)
6
- .filter((s) => !!s)
7
- .sort();
8
- const now = Date.now();
9
- const next = starts.find((s) => new Date(s).getTime() >= now) ?? starts[0];
10
- const soldOut = detail.status === 'sold_out' ||
11
- (occurrences.length > 0 && occurrences.every((o) => o.status === 'sold_out'));
12
- return {
13
- id: detail.id,
14
- name: detail.name,
15
- ...(detail.shortDescription ? { shortDescription: detail.shortDescription } : {}),
16
- ...(detail.imageUrl ? { imageUrl: detail.imageUrl } : {}),
17
- ...(detail.venueName
18
- ? { venue: { id: detail.venueId ?? '', name: detail.venueName, city: detail.venueCity ?? '' } }
19
- : {}),
20
- ...(next ? { nextOccurrence: { startsAt: next } } : {}),
21
- availabilityStatus: soldOut ? 'sold_out' : 'available',
22
- };
23
- }
1
+ /**
2
+ * Adapts the sales event detail (events manager get) to the shape
3
+ * tl-event-card renders.
4
+ *
5
+ * The adapter and its type moved to `@ticketlayer/elements-core` with the core
6
+ * split (TKT-69): picking the next occurrence and deciding whether a card reads
7
+ * sold out is behaviour, and behaviour lives in a core so every harness decides
8
+ * it the same way. This file stays as the import path the component and its
9
+ * spec already use.
10
+ */
11
+ export { toEventCardEvent } from '@ticketlayer/elements-core';
@@ -1,102 +1,68 @@
1
1
  import { h } from "@stencil/core";
2
- import { whenSDK } from "../../utils/sdk-connector";
3
- import { errorMessage } from "../../utils/order-summary";
4
- import { toEventCardEvent } from "./event-card-adapter";
2
+ import { createElement, eventCardCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
4
+ /**
5
+ * The web harness for `eventCardCore`.
6
+ *
7
+ * It renders and it forwards: which event is shown, how it is read, the status
8
+ * badge and the date are all in `@ticketlayer/elements-core` (AGENTS.md,
9
+ * TKT-11).
10
+ */
5
11
  export class TlEventCard {
6
12
  constructor() {
7
- this.loadedEvent = null;
8
- this.loading = false;
9
- this.error = null;
13
+ this.element = createElement(eventCardCore, {
14
+ client: () => getSDK(),
15
+ clientReady: () => whenSDK(),
16
+ emit: (event, detail) => this.forward(event, detail),
17
+ });
18
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
19
+ this.view = this.element.view;
10
20
  this.handleClick = () => {
11
- const event = this.current;
12
- if (!event)
13
- return;
14
- console.log('[tl-event-card] Clicked:', event.id);
15
- this.tlClick.emit({ eventId: event.id });
21
+ this.element.actions.click();
16
22
  };
17
23
  }
18
- watchEventId() {
19
- this.loadEvent();
20
- }
21
- async componentWillLoad() {
22
- if (!this.event && this.eventId) {
23
- await this.loadEvent();
24
- }
24
+ syncProps() {
25
+ this.element.setProps({ event: this.event, eventId: this.eventId });
25
26
  }
26
- async loadEvent() {
27
- if (this.event || !this.eventId)
28
- return;
29
- try {
30
- this.loading = true;
31
- this.error = null;
32
- const sdk = await whenSDK();
33
- const detail = (await sdk.getEventsManager().get(this.eventId));
34
- if (!detail)
35
- throw new Error('Event not found');
36
- this.loadedEvent = toEventCardEvent(detail);
37
- }
38
- catch (err) {
39
- console.error('[tl-event-card] Failed to load event:', err);
40
- this.error = errorMessage(err, 'Failed to load event');
41
- this.tlError.emit({ message: this.error });
42
- }
43
- finally {
44
- this.loading = false;
45
- }
46
- }
47
- get current() {
48
- return this.event ?? this.loadedEvent;
27
+ /** A new id is a new subject: tear the old mount down and start again. */
28
+ watchEventId() {
29
+ this.syncProps();
30
+ this.teardown?.();
31
+ this.teardown = this.element.mount();
49
32
  }
50
- formatDate(dateString) {
51
- const date = new Date(dateString);
52
- return date.toLocaleDateString('en-GB', {
53
- weekday: 'short',
54
- day: 'numeric',
55
- month: 'short',
56
- hour: '2-digit',
57
- minute: '2-digit',
58
- });
33
+ connectedCallback() {
34
+ this.syncProps();
35
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
36
+ this.view = view;
37
+ }));
38
+ this.view = this.element.view;
39
+ this.teardown = this.element.mount();
59
40
  }
60
- getStatusBadgeClass() {
61
- const baseClass = 'status-badge';
62
- switch (this.current?.availabilityStatus) {
63
- case 'available':
64
- return `${baseClass} status-available`;
65
- case 'limited':
66
- return `${baseClass} status-limited`;
67
- case 'sold_out':
68
- return `${baseClass} status-sold-out`;
69
- default:
70
- return baseClass;
71
- }
41
+ disconnectedCallback() {
42
+ this.teardown?.();
43
+ this.teardown = undefined;
44
+ this.unsubscribe?.();
45
+ this.unsubscribe = undefined;
72
46
  }
73
- getStatusLabel() {
74
- switch (this.current?.availabilityStatus) {
75
- case 'available':
76
- return 'Available';
77
- case 'limited':
78
- return 'Limited';
79
- case 'sold_out':
80
- return 'Sold Out';
81
- default:
82
- return '';
83
- }
47
+ forward(event, detail) {
48
+ if (event === 'tlClick')
49
+ this.tlClick.emit(detail);
50
+ else if (event === 'tlError')
51
+ this.tlError.emit(detail);
84
52
  }
85
53
  render() {
86
- if (this.loading) {
54
+ const { status, state, derived } = this.view;
55
+ if (status === 'loading') {
87
56
  return (h("div", { class: "event-card card-skeleton", "aria-busy": "true" }, h("div", { class: "image-container" }), h("div", { class: "content" }, h("div", { class: "skeleton-line short" }), h("div", { class: "skeleton-line long" }))));
88
57
  }
89
- if (this.error) {
90
- return h("div", { class: "event-card card-error" }, this.error);
58
+ if (status === 'error') {
59
+ return h("div", { class: "event-card card-error" }, state.error);
91
60
  }
92
- const event = this.current;
61
+ const event = derived.event;
93
62
  if (!event) {
94
63
  return null;
95
64
  }
96
- // The sales surface exposes a single `imageUrl` (card/listing thumbnail);
97
- // `thumbnailUrl` is an accepted alias if a host ever supplies a distinct one.
98
- const image = event.thumbnailUrl || event.imageUrl;
99
- return (h("div", { class: "event-card", onClick: this.handleClick }, h("div", { class: "image-container" }, image ? (h("img", { src: image, alt: event.name, class: "event-image" })) : (h("div", { class: "image-placeholder" }, h("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" })))), h("div", { class: "status-container" }, h("span", { class: this.getStatusBadgeClass() }, this.getStatusLabel()))), h("div", { class: "content" }, event.category && h("span", { class: "category" }, event.category), h("h3", { class: "title" }, event.name), event.nextOccurrence && (h("p", { class: "meta" }, h("svg", { class: "icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" })), this.formatDate(event.nextOccurrence.startsAt))), event.venue && (h("p", { class: "meta" }, h("svg", { class: "icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" }), h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z" })), event.venue.name, ", ", event.venue.city)), event.shortDescription && (h("p", { class: "description" }, event.shortDescription)))));
65
+ return (h("div", { class: "event-card", onClick: this.handleClick }, h("div", { class: "image-container" }, derived.image ? (h("img", { src: derived.image, alt: event.name, class: "event-image" })) : (h("div", { class: "image-placeholder" }, h("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" })))), h("div", { class: "status-container" }, h("span", { class: derived.statusBadgeClass }, derived.statusLabel))), h("div", { class: "content" }, event.category && h("span", { class: "category" }, event.category), h("h3", { class: "title" }, event.name), derived.dateLabel && (h("p", { class: "meta" }, h("svg", { class: "icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" })), derived.dateLabel)), derived.venueLabel && (h("p", { class: "meta" }, h("svg", { class: "icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" }), h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z" })), derived.venueLabel)), event.shortDescription && h("p", { class: "description" }, event.shortDescription))));
100
66
  }
101
67
  static get is() { return "tl-event-card"; }
102
68
  static get encapsulation() { return "shadow"; }
@@ -158,9 +124,7 @@ export class TlEventCard {
158
124
  }
159
125
  static get states() {
160
126
  return {
161
- "loadedEvent": {},
162
- "loading": {},
163
- "error": {}
127
+ "view": {}
164
128
  };
165
129
  }
166
130
  static get events() {
@@ -198,6 +162,9 @@ export class TlEventCard {
198
162
  }
199
163
  static get watchers() {
200
164
  return [{
165
+ "propName": "event",
166
+ "methodName": "syncProps"
167
+ }, {
201
168
  "propName": "eventId",
202
169
  "methodName": "watchEventId"
203
170
  }];
@@ -1,5 +1,15 @@
1
1
  import { h } from "@stencil/core";
2
- import { whenSDK } from "../../utils/sdk-connector";
2
+ import { createElement, eventListCore, } from "@ticketlayer/elements-core";
3
+ import { getSDK, whenSDK } from "../../utils/sdk-connector";
4
+ /**
5
+ * tl-event-list - what the channel is selling, as a grid of tl-event-card.
6
+ *
7
+ * The web harness for `eventListCore`: it renders and it forwards. What to ask
8
+ * for, what came back and every string are in `@ticketlayer/elements-core`
9
+ * (AGENTS.md, TKT-11).
10
+ *
11
+ * Usage: <tl-event-list limit="12" columns="3"></tl-event-list>
12
+ */
3
13
  export class TlEventList {
4
14
  constructor() {
5
15
  /**
@@ -10,54 +20,87 @@ export class TlEventList {
10
20
  * Columns for grid layout (1, 2, 3, or 4)
11
21
  */
12
22
  this.columns = 3;
13
- this.events = [];
14
- this.loading = true;
15
- this.error = null;
23
+ this.element = createElement(eventListCore, {
24
+ client: () => getSDK(),
25
+ clientReady: () => whenSDK(),
26
+ emit: (event, detail) => this.forward(event, detail),
27
+ });
28
+ /** The core's whole view. Assigning it is what makes Stencil re-render. */
29
+ this.view = this.element.view;
30
+ /** A card was clicked. The core has nothing to decide about it; pass it on. */
16
31
  this.handleEventClick = (event) => {
17
- console.log('[tl-event-list] Event clicked:', event.detail.eventId);
18
32
  this.tlEventClick.emit(event.detail);
19
33
  };
20
34
  }
35
+ /** A different question deserves a fresh answer. */
21
36
  watchFiltersChange() {
22
- this.loadEvents();
37
+ this.syncProps();
38
+ this.element.actions.load();
23
39
  }
24
- async componentWillLoad() {
25
- await this.loadEvents();
40
+ syncProps() {
41
+ this.element.setProps({ limit: this.limit, category: this.category, columns: this.columns });
26
42
  }
27
- async loadEvents() {
28
- try {
29
- console.log('[tl-event-list] Loading events via SDK...', { limit: this.limit, category: this.category });
30
- this.loading = true;
31
- this.error = null;
32
- const sdk = await whenSDK();
33
- const eventsManager = sdk.getEventsManager();
34
- const response = await eventsManager.list({
35
- limit: this.limit,
36
- category: this.category,
43
+ connectedCallback() {
44
+ this.syncProps();
45
+ this.unsubscribe ?? (this.unsubscribe = this.element.subscribe((view) => {
46
+ this.view = view;
47
+ }));
48
+ this.view = this.element.view;
49
+ this.teardown = this.element.mount();
50
+ }
51
+ /**
52
+ * Nothing is painted until the first listing has landed.
53
+ *
54
+ * Kept as found rather than improved, and it is the harness's to keep: the
55
+ * component awaited its read in `componentWillLoad`, and Stencil does not
56
+ * paint a component until that resolves, so a slow channel shows an empty
57
+ * box where the skeleton markup below would fit. Held by the spec.
58
+ *
59
+ * Expressed against the core's own status rather than against a read this
60
+ * harness starts, because the read is the core's (`connectedCallback` has
61
+ * already mounted it). Every later read draws the skeleton, exactly as
62
+ * before.
63
+ */
64
+ componentWillLoad() {
65
+ if (this.view.status !== 'loading')
66
+ return undefined;
67
+ return new Promise((resolve) => {
68
+ const off = this.element.subscribe((view) => {
69
+ if (view.status === 'loading')
70
+ return;
71
+ off();
72
+ resolve();
37
73
  });
38
- console.log('[tl-event-list] Events loaded:', response.items.length);
39
- this.events = response.items;
40
- }
41
- catch (err) {
42
- console.error('[tl-event-list] Failed to load events:', err);
43
- this.error = err instanceof Error ? err.message : 'Failed to load events';
44
- }
45
- finally {
46
- this.loading = false;
47
- }
74
+ });
75
+ }
76
+ /**
77
+ * Off screen: run the mount's teardown, and nothing more. `destroy()` is
78
+ * terminal and Stencil calls this for a move as well as a removal, so
79
+ * destroying here would leave a re-inserted element frozen.
80
+ */
81
+ disconnectedCallback() {
82
+ this.teardown?.();
83
+ this.teardown = undefined;
84
+ this.unsubscribe?.();
85
+ this.unsubscribe = undefined;
86
+ }
87
+ forward(event, detail) {
88
+ if (event === 'tlEventClick')
89
+ this.tlEventClick.emit(detail);
48
90
  }
49
91
  render() {
50
- const gridClass = `event-grid cols-${Math.min(Math.max(this.columns, 1), 4)}`;
51
- if (this.loading) {
92
+ const { status, state, strings, derived } = this.view;
93
+ const gridClass = `event-grid cols-${derived.columns}`;
94
+ if (status === 'loading') {
52
95
  return (h("div", { class: gridClass }, Array.from({ length: 6 }).map((_, i) => (h("div", { class: "skeleton-card", key: i }, h("div", { class: "skeleton-image" }), h("div", { class: "skeleton-content" }, h("div", { class: "skeleton-line short" }), h("div", { class: "skeleton-line medium" }), h("div", { class: "skeleton-line long" })))))));
53
96
  }
54
- if (this.error) {
55
- return (h("div", { class: "error-state" }, h("p", { class: "error-title" }, "Error loading events"), h("p", { class: "error-message" }, this.error)));
97
+ if (status === 'error') {
98
+ return (h("div", { class: "error-state" }, h("p", { class: "error-title" }, strings.errorTitle), h("p", { class: "error-message" }, state.error)));
56
99
  }
57
- if (this.events.length === 0) {
58
- return (h("div", { class: "empty-state" }, h("svg", { class: "empty-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" })), h("p", { class: "empty-title" }, "No events available"), h("p", { class: "empty-message" }, "Check back later for upcoming events.")));
100
+ if (status === 'empty') {
101
+ return (h("div", { class: "empty-state" }, h("svg", { class: "empty-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "1.5", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" })), h("p", { class: "empty-title" }, strings.emptyTitle), h("p", { class: "empty-message" }, strings.emptyMessage)));
59
102
  }
60
- return (h("div", { class: gridClass }, this.events.map((event) => (h("tl-event-card", { key: event.id, event: event, onTlClick: this.handleEventClick })))));
103
+ return (h("div", { class: gridClass }, derived.events.map((event) => (h("tl-event-card", { key: event.id, event: event, onTlClick: this.handleEventClick })))));
61
104
  }
62
105
  static get is() { return "tl-event-list"; }
63
106
  static get encapsulation() { return "shadow"; }
@@ -136,9 +179,7 @@ export class TlEventList {
136
179
  }
137
180
  static get states() {
138
181
  return {
139
- "events": {},
140
- "loading": {},
141
- "error": {}
182
+ "view": {}
142
183
  };
143
184
  }
144
185
  static get events() {
@@ -166,6 +207,9 @@ export class TlEventList {
166
207
  }, {
167
208
  "propName": "category",
168
209
  "methodName": "watchFiltersChange"
210
+ }, {
211
+ "propName": "columns",
212
+ "methodName": "syncProps"
169
213
  }];
170
214
  }
171
215
  }