@ticketlayer/elements-core 0.1.0 → 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 (62) hide show
  1. package/README.md +141 -4
  2. package/dist/cjs/cores/buy-tickets-button.js +213 -0
  3. package/dist/cjs/cores/buy-tickets-wrapper.js +106 -0
  4. package/dist/cjs/cores/cart-badge.js +150 -0
  5. package/dist/cjs/cores/cart-drawer.js +122 -0
  6. package/dist/cjs/cores/cart.js +247 -0
  7. package/dist/cjs/cores/checkout-wrapper.js +75 -0
  8. package/dist/cjs/cores/event-card.js +209 -0
  9. package/dist/cjs/cores/event-list.js +109 -0
  10. package/dist/cjs/cores/index.js +64 -1
  11. package/dist/cjs/cores/login.js +309 -0
  12. package/dist/cjs/cores/my-orders.js +221 -0
  13. package/dist/cjs/cores/occurrence-selector.js +528 -0
  14. package/dist/cjs/cores/order-confirmation.js +301 -0
  15. package/dist/cjs/cores/order-tickets.js +172 -0
  16. package/dist/cjs/cores/promo-code.js +136 -0
  17. package/dist/cjs/cores/ticket-selector.js +30 -0
  18. package/dist/cjs/define.js +76 -0
  19. package/dist/cjs/index.js +17 -1
  20. package/dist/cjs/manifest.js +5 -0
  21. package/dist/cjs/orders.js +169 -0
  22. package/dist/esm/client.d.ts +106 -0
  23. package/dist/esm/cores/buy-tickets-button.d.ts +55 -0
  24. package/dist/esm/cores/buy-tickets-button.js +210 -0
  25. package/dist/esm/cores/buy-tickets-wrapper.d.ts +27 -0
  26. package/dist/esm/cores/buy-tickets-wrapper.js +103 -0
  27. package/dist/esm/cores/cart-badge.d.ts +48 -0
  28. package/dist/esm/cores/cart-badge.js +147 -0
  29. package/dist/esm/cores/cart-drawer.d.ts +44 -0
  30. package/dist/esm/cores/cart-drawer.js +119 -0
  31. package/dist/esm/cores/cart.d.ts +105 -0
  32. package/dist/esm/cores/cart.js +244 -0
  33. package/dist/esm/cores/checkout-wrapper.d.ts +22 -0
  34. package/dist/esm/cores/checkout-wrapper.js +72 -0
  35. package/dist/esm/cores/event-card.d.ts +80 -0
  36. package/dist/esm/cores/event-card.js +205 -0
  37. package/dist/esm/cores/event-list.d.ts +35 -0
  38. package/dist/esm/cores/event-list.js +106 -0
  39. package/dist/esm/cores/index.d.ts +28 -0
  40. package/dist/esm/cores/index.js +42 -0
  41. package/dist/esm/cores/login.d.ts +125 -0
  42. package/dist/esm/cores/login.js +306 -0
  43. package/dist/esm/cores/my-orders.d.ts +63 -0
  44. package/dist/esm/cores/my-orders.js +218 -0
  45. package/dist/esm/cores/occurrence-selector.d.ts +170 -0
  46. package/dist/esm/cores/occurrence-selector.js +525 -0
  47. package/dist/esm/cores/order-confirmation.d.ts +99 -0
  48. package/dist/esm/cores/order-confirmation.js +298 -0
  49. package/dist/esm/cores/order-tickets.d.ts +46 -0
  50. package/dist/esm/cores/order-tickets.js +169 -0
  51. package/dist/esm/cores/promo-code.d.ts +56 -0
  52. package/dist/esm/cores/promo-code.js +132 -0
  53. package/dist/esm/cores/ticket-selector.js +30 -0
  54. package/dist/esm/define.d.ts +146 -0
  55. package/dist/esm/define.js +75 -0
  56. package/dist/esm/index.d.ts +5 -3
  57. package/dist/esm/index.js +2 -1
  58. package/dist/esm/manifest.d.ts +12 -0
  59. package/dist/esm/manifest.js +5 -0
  60. package/dist/esm/orders.d.ts +110 -0
  61. package/dist/esm/orders.js +154 -0
  62. package/package.json +2 -2
@@ -1,7 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NO_QUERY = exports.NO_STORAGE = void 0;
3
4
  exports.defineElement = defineElement;
4
5
  exports.createElement = createElement;
6
+ /** The store a core gets when its harness supplies none: it forgets. */
7
+ exports.NO_STORAGE = {
8
+ get: () => null,
9
+ set: () => undefined,
10
+ };
11
+ /** The parameters a core gets when its harness supplies none: there are none. */
12
+ exports.NO_QUERY = {
13
+ get: () => null,
14
+ };
5
15
  const CATEGORIES = [
6
16
  'discovery',
7
17
  'purchase',
@@ -71,6 +81,8 @@ function createElement(core, options = {}) {
71
81
  style: 'currency',
72
82
  currency: currency || fallbackCurrency,
73
83
  }).format(amountInMinorUnits / 100);
84
+ const storage = options.storage ?? exports.NO_STORAGE;
85
+ const query = options.query ?? exports.NO_QUERY;
74
86
  const viewContext = () => ({ props, state, strings, formatMoney });
75
87
  const buildView = () => {
76
88
  const ctx = viewContext();
@@ -89,6 +101,15 @@ function createElement(core, options = {}) {
89
101
  for (const listener of [...listeners])
90
102
  listener(view);
91
103
  };
104
+ const whenClient = async () => {
105
+ if (options.clientReady)
106
+ await options.clientReady();
107
+ const client = resolveClient();
108
+ if (!client) {
109
+ throw new Error(`[elements-core] ${core.tag}: no client. A harness hands the core the client the host published.`);
110
+ }
111
+ return client;
112
+ };
92
113
  const actionContext = {
93
114
  get props() {
94
115
  return props;
@@ -126,6 +147,9 @@ function createElement(core, options = {}) {
126
147
  return null;
127
148
  }
128
149
  },
150
+ storage,
151
+ query,
152
+ whenClient,
129
153
  after(ms, run) {
130
154
  if (destroyed)
131
155
  return;
@@ -138,6 +162,52 @@ function createElement(core, options = {}) {
138
162
  },
139
163
  };
140
164
  const actions = core.actions(actionContext);
165
+ // The live mount, as an identity rather than a boolean: a teardown closes
166
+ // over the token it was made for, so a stale one tears nothing down.
167
+ let mountToken = null;
168
+ let cleanups = [];
169
+ const teardownMount = (token) => {
170
+ if (mountToken !== token)
171
+ return;
172
+ mountToken = null;
173
+ const pending = cleanups;
174
+ cleanups = [];
175
+ for (const cleanup of pending.reverse())
176
+ cleanup();
177
+ };
178
+ const mount = () => {
179
+ if (destroyed)
180
+ return () => { };
181
+ if (mountToken)
182
+ teardownMount(mountToken);
183
+ const token = {};
184
+ mountToken = token;
185
+ const live = () => mountToken === token && !destroyed;
186
+ const returned = core.mount?.({
187
+ ...actionContext,
188
+ get props() {
189
+ return props;
190
+ },
191
+ get state() {
192
+ return state;
193
+ },
194
+ actions,
195
+ active: live,
196
+ onTeardown(cleanup) {
197
+ if (live())
198
+ cleanups.push(cleanup);
199
+ else
200
+ cleanup();
201
+ },
202
+ });
203
+ if (typeof returned === 'function') {
204
+ if (live())
205
+ cleanups.push(returned);
206
+ else
207
+ returned();
208
+ }
209
+ return () => teardownMount(token);
210
+ };
141
211
  return {
142
212
  core,
143
213
  get props() {
@@ -165,7 +235,13 @@ function createElement(core, options = {}) {
165
235
  listeners.delete(listener);
166
236
  };
167
237
  },
238
+ mount,
239
+ get mounted() {
240
+ return mountToken !== null;
241
+ },
168
242
  destroy() {
243
+ if (mountToken)
244
+ teardownMount(mountToken);
169
245
  destroyed = true;
170
246
  for (const handle of timers)
171
247
  clearTimeout(handle);
package/dist/cjs/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.FEE_LABEL_FALLBACK = exports.selectionTotals = exports.minorUnits = exports.expressesFee = exports.cartTotals = exports.manifestOf = exports.defineElement = exports.createElement = void 0;
17
+ exports.totalsAgree = exports.ticketItems = exports.ticketCount = exports.statusTone = exports.statusLabel = exports.resolveOccurrence = exports.orderTotals = exports.orderEventName = exports.orderCustomerEmail = exports.formatPrice = exports.formatDateTime = exports.formatDate = exports.errorMessage = exports.FEE_LABEL_FALLBACK = exports.selectionTotals = exports.minorUnits = exports.expressesFee = exports.cartTotals = exports.manifestOf = exports.NO_STORAGE = exports.NO_QUERY = exports.defineElement = exports.createElement = void 0;
18
18
  /**
19
19
  * `@ticketlayer/elements-core`: the headless cores the Ticketlayer Elements
20
20
  * render.
@@ -33,6 +33,8 @@ exports.FEE_LABEL_FALLBACK = exports.selectionTotals = exports.minorUnits = expo
33
33
  var define_js_1 = require("./define.js");
34
34
  Object.defineProperty(exports, "createElement", { enumerable: true, get: function () { return define_js_1.createElement; } });
35
35
  Object.defineProperty(exports, "defineElement", { enumerable: true, get: function () { return define_js_1.defineElement; } });
36
+ Object.defineProperty(exports, "NO_QUERY", { enumerable: true, get: function () { return define_js_1.NO_QUERY; } });
37
+ Object.defineProperty(exports, "NO_STORAGE", { enumerable: true, get: function () { return define_js_1.NO_STORAGE; } });
36
38
  var manifest_js_1 = require("./manifest.js");
37
39
  Object.defineProperty(exports, "manifestOf", { enumerable: true, get: function () { return manifest_js_1.manifestOf; } });
38
40
  var money_js_1 = require("./money.js");
@@ -41,4 +43,18 @@ Object.defineProperty(exports, "expressesFee", { enumerable: true, get: function
41
43
  Object.defineProperty(exports, "minorUnits", { enumerable: true, get: function () { return money_js_1.minorUnits; } });
42
44
  Object.defineProperty(exports, "selectionTotals", { enumerable: true, get: function () { return money_js_1.selectionTotals; } });
43
45
  Object.defineProperty(exports, "FEE_LABEL_FALLBACK", { enumerable: true, get: function () { return money_js_1.FEE_LABEL_FALLBACK; } });
46
+ var orders_js_1 = require("./orders.js");
47
+ Object.defineProperty(exports, "errorMessage", { enumerable: true, get: function () { return orders_js_1.errorMessage; } });
48
+ Object.defineProperty(exports, "formatDate", { enumerable: true, get: function () { return orders_js_1.formatDate; } });
49
+ Object.defineProperty(exports, "formatDateTime", { enumerable: true, get: function () { return orders_js_1.formatDateTime; } });
50
+ Object.defineProperty(exports, "formatPrice", { enumerable: true, get: function () { return orders_js_1.formatPrice; } });
51
+ Object.defineProperty(exports, "orderCustomerEmail", { enumerable: true, get: function () { return orders_js_1.orderCustomerEmail; } });
52
+ Object.defineProperty(exports, "orderEventName", { enumerable: true, get: function () { return orders_js_1.orderEventName; } });
53
+ Object.defineProperty(exports, "orderTotals", { enumerable: true, get: function () { return orders_js_1.orderTotals; } });
54
+ Object.defineProperty(exports, "resolveOccurrence", { enumerable: true, get: function () { return orders_js_1.resolveOccurrence; } });
55
+ Object.defineProperty(exports, "statusLabel", { enumerable: true, get: function () { return orders_js_1.statusLabel; } });
56
+ Object.defineProperty(exports, "statusTone", { enumerable: true, get: function () { return orders_js_1.statusTone; } });
57
+ Object.defineProperty(exports, "ticketCount", { enumerable: true, get: function () { return orders_js_1.ticketCount; } });
58
+ Object.defineProperty(exports, "ticketItems", { enumerable: true, get: function () { return orders_js_1.ticketItems; } });
59
+ Object.defineProperty(exports, "totalsAgree", { enumerable: true, get: function () { return orders_js_1.totalsAgree; } });
44
60
  __exportStar(require("./cores/index.js"), exports);
@@ -8,6 +8,7 @@ function manifestOf(core) {
8
8
  name,
9
9
  attr: spec.attr,
10
10
  type: spec.type,
11
+ tsType: spec.tsType ?? null,
11
12
  default: spec.default ?? null,
12
13
  required: !!spec.required,
13
14
  docs: spec.docs,
@@ -28,6 +29,7 @@ function manifestOf(core) {
28
29
  strings: { ...core.strings },
29
30
  statuses: [...core.statuses],
30
31
  actions: Object.keys(dryRunActions(core)).sort(),
32
+ mounts: typeof core.mount === 'function',
31
33
  };
32
34
  }
33
35
  /**
@@ -50,5 +52,8 @@ function dryRunActions(core) {
50
52
  client: refuse('client'),
51
53
  optionalClient: refuse('optionalClient'),
52
54
  after: refuse('after'),
55
+ storage: { get: refuse('storage.get'), set: refuse('storage.set') },
56
+ query: { get: refuse('query.get') },
57
+ whenClient: refuse('whenClient'),
53
58
  });
54
59
  }
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatPrice = formatPrice;
4
+ exports.formatDateTime = formatDateTime;
5
+ exports.formatDate = formatDate;
6
+ exports.ticketItems = ticketItems;
7
+ exports.ticketCount = ticketCount;
8
+ exports.orderEventName = orderEventName;
9
+ exports.orderCustomerEmail = orderCustomerEmail;
10
+ exports.statusLabel = statusLabel;
11
+ exports.statusTone = statusTone;
12
+ exports.errorMessage = errorMessage;
13
+ exports.resolveOccurrence = resolveOccurrence;
14
+ exports.orderTotals = orderTotals;
15
+ exports.totalsAgree = totalsAgree;
16
+ /**
17
+ * The order shape, and what every harness has to work out from it.
18
+ *
19
+ * The sales order view carries line items, totals and tickets but no event
20
+ * name, no occurrence date and often no customer email, so these derive what
21
+ * they can from the order and resolve the rest through the client. They lived
22
+ * in `packages/elements/src/utils/order-summary.ts`, where only the web could
23
+ * reach them; they moved here with the split of `tl-my-orders` and
24
+ * `tl-order-confirmation` (TKT-69) so the same rules answer on every runtime.
25
+ * That module re-exports them, so every existing import still resolves and
26
+ * `tl-order-tickets` and `tl-ticket`, which are not split yet, read the same
27
+ * helpers they always did.
28
+ *
29
+ * `en-GB` is written out in the date and money helpers rather than taken from
30
+ * the instance's locale. That is what the components did and this is a
31
+ * behaviour-identical move; `ctx.formatMoney` is the locale-aware formatter and
32
+ * these are deliberately not it, because an order's money arrives as a minor
33
+ * unit integer OR a decimal string and `formatMoney` takes only the first.
34
+ */
35
+ const money_js_1 = require("./money.js");
36
+ /** Money arrives as minor-unit integers or decimal strings ("50.00"). */
37
+ function formatPrice(value, currency = 'GBP') {
38
+ if (value === null || value === undefined || value === '')
39
+ return '';
40
+ const fmt = new Intl.NumberFormat('en-GB', { style: 'currency', currency });
41
+ if (typeof value === 'number')
42
+ return fmt.format(value / 100);
43
+ const n = Number(value);
44
+ return Number.isNaN(n) ? String(value) : fmt.format(n);
45
+ }
46
+ function formatDateTime(iso) {
47
+ return new Date(iso).toLocaleDateString('en-GB', {
48
+ weekday: 'short',
49
+ day: 'numeric',
50
+ month: 'short',
51
+ year: 'numeric',
52
+ hour: '2-digit',
53
+ minute: '2-digit',
54
+ });
55
+ }
56
+ function formatDate(iso) {
57
+ return new Date(iso).toLocaleDateString('en-GB', {
58
+ day: 'numeric',
59
+ month: 'short',
60
+ year: 'numeric',
61
+ });
62
+ }
63
+ /** Line items that are tickets (fees, taxes and discounts have their own type). */
64
+ function ticketItems(order) {
65
+ return (order.items ?? []).filter((i) => !i.type || i.type === 'ticket');
66
+ }
67
+ function ticketCount(order) {
68
+ const fromItems = ticketItems(order).reduce((sum, i) => sum + (Number(i.quantity) || 0), 0);
69
+ return fromItems || order.tickets?.length || 0;
70
+ }
71
+ /** Event name as the order view exposes it: the first ticket line's name. */
72
+ function orderEventName(order) {
73
+ return order.eventName || ticketItems(order)[0]?.name || order.items?.[0]?.name || null;
74
+ }
75
+ function orderCustomerEmail(order, customer) {
76
+ const fromOrder = order?.customerEmail || order?.customer?.email;
77
+ if (fromOrder)
78
+ return fromOrder;
79
+ const c = customer;
80
+ return c?.email || null;
81
+ }
82
+ function statusLabel(status) {
83
+ if (!status)
84
+ return '';
85
+ return status.replace(/_/g, ' ').replace(/^\w/, (m) => m.toUpperCase());
86
+ }
87
+ function statusTone(status) {
88
+ switch (status) {
89
+ case 'confirmed':
90
+ return 'success';
91
+ case 'completed':
92
+ case 'fulfilled':
93
+ return 'info';
94
+ case 'pending':
95
+ case 'processing':
96
+ return 'warning';
97
+ case 'cancelled':
98
+ case 'expired':
99
+ case 'failed':
100
+ return 'error';
101
+ default:
102
+ return 'muted';
103
+ }
104
+ }
105
+ function errorMessage(err, fallback) {
106
+ return err instanceof Error && err.message ? err.message : fallback;
107
+ }
108
+ /**
109
+ * Find the event and start time for an occurrence id. The order view only
110
+ * carries the occurrence id, so this walks the channel's listings (cached by
111
+ * live-api) until one owns the occurrence. Best effort: null when not found,
112
+ * and null on a client whose events manager cannot list or fetch, which is an
113
+ * older connector rather than a failure.
114
+ */
115
+ async function resolveOccurrence(client, occurrenceId, maxEvents = 12) {
116
+ const events = client.getEventsManager();
117
+ if (!events?.list || !events.get)
118
+ return null;
119
+ const listing = await events.list({ limit: maxEvents });
120
+ const items = (listing?.items ?? []);
121
+ const candidates = items.slice(0, maxEvents);
122
+ for (const candidate of candidates) {
123
+ const detail = (await events.get(candidate.id).catch(() => null));
124
+ const occ = detail?.occurrences?.find((o) => o.id === occurrenceId);
125
+ if (occ) {
126
+ return {
127
+ eventId: detail?.id ?? candidate.id,
128
+ eventName: detail?.name ?? candidate.name ?? '',
129
+ startsAt: occ.startsAt ?? null,
130
+ };
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ function orderTotals(order) {
136
+ const o = order || {};
137
+ const items = Array.isArray(o.items) ? o.items : [];
138
+ const feeLines = items.filter((i) => i.type === 'fee');
139
+ const discountLines = items.filter((i) => i.type === 'discount');
140
+ const tickets = ticketItems(o);
141
+ const feeFromLines = feeLines.length
142
+ ? feeLines.reduce((sum, i) => sum + ((0, money_js_1.minorUnits)(i.subtotal) ?? 0), 0)
143
+ : null;
144
+ const fees = feeFromLines ?? (0, money_js_1.minorUnits)(o.totalFees) ?? 0;
145
+ const declaredSubtotal = (0, money_js_1.minorUnits)(o.subtotal);
146
+ const subtotal = tickets.length
147
+ ? tickets.reduce((sum, i) => sum + ((0, money_js_1.minorUnits)(i.subtotal) ?? 0), 0)
148
+ : (declaredSubtotal ?? 0) - fees;
149
+ const discountFromLines = discountLines.length
150
+ ? discountLines.reduce((sum, i) => sum + Math.abs((0, money_js_1.minorUnits)(i.subtotal) ?? 0), 0)
151
+ : null;
152
+ const discount = (0, money_js_1.minorUnits)(o.totalDiscounts) ?? discountFromLines ?? 0;
153
+ const tax = (0, money_js_1.minorUnits)(o.totalTax) ?? 0;
154
+ const named = feeLines.some((i) => typeof i.name === 'string' && !!i.name);
155
+ return {
156
+ currency: o.currency || 'GBP',
157
+ subtotal,
158
+ fees,
159
+ feeLabel: feeLines.map((i) => i.name).find((n) => !!n) || money_js_1.FEE_LABEL_FALLBACK,
160
+ showFee: (0, money_js_1.expressesFee)(feeFromLines ?? (0, money_js_1.minorUnits)(o.totalFees), named),
161
+ tax,
162
+ discount,
163
+ total: (0, money_js_1.minorUnits)(o.total) ?? subtotal + fees + tax - discount,
164
+ };
165
+ }
166
+ /** The rows add up to the total the order reports. Asserted in the specs. */
167
+ function totalsAgree(totals) {
168
+ return totals.subtotal + totals.fees + totals.tax - totals.discount === totals.total;
169
+ }
@@ -14,12 +14,118 @@
14
14
  */
15
15
  export interface ElementEventsManager {
16
16
  getTicketTypes(eventId: string, occurrenceId: string): Promise<unknown[]>;
17
+ /** One event's detail. Absent on a connector that only sells. */
18
+ get?(eventId: string): Promise<unknown>;
19
+ /**
20
+ * The channel's listing. Absent on a connector that only sells, so every
21
+ * call site checks: `tl-event-list` renders it, and `resolveOccurrence`
22
+ * walks it to put an event name on an order.
23
+ */
24
+ list?(options?: {
25
+ limit?: number;
26
+ category?: string;
27
+ }): Promise<unknown>;
17
28
  }
18
29
  export interface ElementCartManager {
19
30
  addItem(ticketTypeId: string, quantity: number, occurrenceId: string): Promise<unknown>;
31
+ /** The cart as it stands. Absent on a connector that only writes. */
32
+ get?(): Promise<unknown>;
33
+ removeItem?(itemId: string): Promise<unknown>;
34
+ /**
35
+ * Subscribe to a cart event, returning the unsubscribe.
36
+ *
37
+ * Separate from {@link ElementClient.on} on purpose, and not a duplicate of
38
+ * it: `@ticketlayer/live`'s connector adapts the payload on the cart
39
+ * manager's bus (it adds `itemCount` and the derived money rows) and passes
40
+ * it through untouched on the client's. A cart core reads those figures, so
41
+ * it subscribes here, and `tl-cart-drawer`, which only listens for a UI
42
+ * intent, subscribes on the client.
43
+ */
44
+ on?(event: string, callback: (data: unknown) => void): (() => void) | void;
45
+ }
46
+ /** What a presale or promo code redemption gives back. */
47
+ export interface ElementPresaleRedemption {
48
+ benefit: string;
49
+ alreadyHeld: boolean;
50
+ }
51
+ export interface ElementPresaleManager {
52
+ redeem(code: string): Promise<ElementPresaleRedemption>;
53
+ }
54
+ /** What a core may read and do about the customer's own orders. */
55
+ export interface ElementOrdersManager {
56
+ /** The signed-in customer's orders. */
57
+ mine(): Promise<unknown>;
58
+ /** One order, with its tickets. Order-access gated by the session. */
59
+ get(orderId: string): Promise<unknown>;
60
+ }
61
+ /**
62
+ * Magic-link sign-in. `customer` and `authenticated` are read as properties
63
+ * rather than called, because the connector exposes them as live getters over
64
+ * the session and a core reads them again after every `auth:changed`.
65
+ */
66
+ export interface ElementAuthManager {
67
+ readonly customer: unknown | null;
68
+ readonly authenticated: boolean;
69
+ requestMagicLink(contact: {
70
+ email?: string;
71
+ phone?: string;
72
+ }): Promise<unknown>;
73
+ exchange(code: string): Promise<unknown>;
74
+ logout(): Promise<void>;
75
+ }
76
+ /** What a completed purchase hands back to the element that opened the modal. */
77
+ export interface ElementOrderResult {
78
+ orderId: string;
79
+ orderNumber: string;
80
+ }
81
+ /**
82
+ * What a core passes when it asks the host to open a modal.
83
+ *
84
+ * The callbacks are how the modal reports back: the element that opened it is
85
+ * the one that announces the outcome to the page, which is why the wrapper
86
+ * elements have events at all.
87
+ */
88
+ export interface ElementModalOptions {
89
+ /** Open straight on one occurrence, skipping the occurrence selector. */
90
+ occurrenceId?: string;
91
+ onComplete?(order: ElementOrderResult): void;
92
+ onError?(error: Error): void;
93
+ onClose?(): void;
94
+ }
95
+ /** The fluent per-event accessor: `client.event(id).openModal(...)`. */
96
+ export interface ElementEventContext {
97
+ openModal(options?: ElementModalOptions): void;
98
+ }
99
+ /** The checkout modal verbs. */
100
+ export interface ElementCheckoutContext {
101
+ openModal(options?: ElementModalOptions): void;
102
+ closeModal(): void;
20
103
  }
21
104
  export interface ElementClient {
22
105
  getEventsManager(): ElementEventsManager;
23
106
  /** Absent on an older connector, so every call site checks. */
24
107
  getCartManager?(): ElementCartManager | null | undefined;
108
+ /** Presale and promo codes. Absent on an older connector. */
109
+ getPresaleManager?(): ElementPresaleManager | null | undefined;
110
+ /** The customer's orders and their tickets. Absent on an older connector. */
111
+ getOrdersManager?(): ElementOrdersManager | null | undefined;
112
+ /** Magic-link sign-in. Absent on an older connector. */
113
+ getAuthManager?(): ElementAuthManager | null | undefined;
114
+ /**
115
+ * The purchase modal for one event, as a UI intent the host's client relays.
116
+ * Absent on an older connector, so every call site checks.
117
+ */
118
+ event?(eventId: string): ElementEventContext;
119
+ /**
120
+ * The checkout modal, the same way. Absent on an older connector.
121
+ */
122
+ checkout?: ElementCheckoutContext;
123
+ /**
124
+ * Subscribe to a client event (`cart:updated` and the rest), returning the
125
+ * unsubscribe. This is how a core that lives on a subscription binds it in
126
+ * its own `mount` and hands the unsubscribe to `ctx.onTeardown`, rather than
127
+ * every harness re-implementing subscribe-on-connect per runtime (TKT-197).
128
+ * Optional, so a connector without a bus is tolerated rather than assumed.
129
+ */
130
+ on?(event: string, callback: (data: unknown) => void): (() => void) | void;
25
131
  }
@@ -0,0 +1,55 @@
1
+ /** The four states a seller puts this button in. */
2
+ export type BuyTicketsButtonSaleState = 'available' | 'soldOut' | 'waitlist' | 'comingSoon';
3
+ export type BuyTicketsButtonVariant = 'primary' | 'secondary' | 'outline';
4
+ export type BuyTicketsButtonSize = 'sm' | 'md' | 'lg';
5
+ export interface BuyTicketsButtonProps {
6
+ eventId: string;
7
+ occurrenceId: string | undefined;
8
+ state: BuyTicketsButtonSaleState;
9
+ label: string | undefined;
10
+ variant: BuyTicketsButtonVariant;
11
+ size: BuyTicketsButtonSize;
12
+ fullWidth: boolean;
13
+ price: number | undefined;
14
+ currency: string;
15
+ pricePrefix: string;
16
+ disabled: boolean;
17
+ }
18
+ /**
19
+ * Nothing. The modal belongs to the client, the sale state is the host's to
20
+ * set, and hover is the harness's: a pointer is not a fact every runtime has,
21
+ * the same way Enter and Space are not.
22
+ */
23
+ export type BuyTicketsButtonState = Record<string, never>;
24
+ export interface BuyTicketsButtonDerived {
25
+ /** Whether a press should do anything. The harness draws the rest from it. */
26
+ interactive: boolean;
27
+ /** The label on the button: the host's override, else the state's own. */
28
+ buttonLabel: string;
29
+ /**
30
+ * The class the sale state is rendered under. The core names the state and
31
+ * the stylesheet says which token family that class paints with, because a
32
+ * core cannot map a status onto one (`tl-event-card`'s status badge is the
33
+ * same bargain).
34
+ */
35
+ stateClass: string;
36
+ /** The formatted price, or null when the host gave none to show. */
37
+ priceLabel: string | null;
38
+ }
39
+ export type BuyTicketsButtonStatus = 'ready' | 'disabled';
40
+ declare const STRINGS: {
41
+ available: string;
42
+ soldOut: string;
43
+ waitlist: string;
44
+ comingSoon: string;
45
+ };
46
+ export type BuyTicketsButtonStrings = typeof STRINGS;
47
+ export declare const buyTicketsButtonCore: import("../define.js").ElementCore<BuyTicketsButtonProps, BuyTicketsButtonState, BuyTicketsButtonDerived, {
48
+ activate(): void;
49
+ }, BuyTicketsButtonStatus, {
50
+ available: string;
51
+ soldOut: string;
52
+ waitlist: string;
53
+ comingSoon: string;
54
+ }>;
55
+ export {};