@seatlayer/js 0.37.0 → 0.38.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.
- package/README.md +33 -0
- package/dist/chunk-HMLY7DHA.js +16 -0
- package/dist/chunk-HMLY7DHA.js.map +1 -0
- package/dist/hostedCheckout-PLHKPT6W.js +345 -0
- package/dist/hostedCheckout-PLHKPT6W.js.map +1 -0
- package/dist/index.cjs +745 -156
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +182 -1
- package/dist/index.d.ts +182 -1
- package/dist/index.js +261 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,14 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
8
8
|
var __typeError = (msg) => {
|
|
9
9
|
throw TypeError(msg);
|
|
10
10
|
};
|
|
11
|
+
var __esm = (fn, res, err) => function __init() {
|
|
12
|
+
if (err) throw err[0];
|
|
13
|
+
try {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
throw err = [e], e;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
11
19
|
var __export = (target, all) => {
|
|
12
20
|
for (var name in all)
|
|
13
21
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -35,6 +43,354 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
|
|
|
35
43
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
36
44
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
37
45
|
|
|
46
|
+
// src/hostedCheckout.ts
|
|
47
|
+
var hostedCheckout_exports = {};
|
|
48
|
+
__export(hostedCheckout_exports, {
|
|
49
|
+
errorCopy: () => errorCopy,
|
|
50
|
+
formatMoney: () => formatMoney,
|
|
51
|
+
mountCheckout: () => mountCheckout,
|
|
52
|
+
unavailableCopy: () => unavailableCopy
|
|
53
|
+
});
|
|
54
|
+
function ensureStyle() {
|
|
55
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
|
|
56
|
+
const el2 = document.createElement("style");
|
|
57
|
+
el2.id = STYLE_ID;
|
|
58
|
+
el2.textContent = CSS;
|
|
59
|
+
document.head.appendChild(el2);
|
|
60
|
+
}
|
|
61
|
+
function formatMoney(amount, currency) {
|
|
62
|
+
try {
|
|
63
|
+
return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
|
|
64
|
+
} catch {
|
|
65
|
+
return `${amount.toFixed(2)} ${currency}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function unavailableCopy(reason, seatCount) {
|
|
69
|
+
const held = `${seatCount} ${seatCount === 1 ? "seat is" : "seats are"} held for a limited time.`;
|
|
70
|
+
if (reason === "payments_off_for_event") {
|
|
71
|
+
return {
|
|
72
|
+
title: "This event isn\u2019t sold online",
|
|
73
|
+
body: `${held} The organiser isn\u2019t taking payment for this event here.`,
|
|
74
|
+
detail: "Nothing has been charged. Check where you found this event for how to get tickets."
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (reason === "unavailable_for_event") {
|
|
78
|
+
return {
|
|
79
|
+
title: "This event isn\u2019t taking payment yet",
|
|
80
|
+
body: `${held} Online payment is not switched on for this event.`,
|
|
81
|
+
detail: "Nothing has been charged. If you were sent here to pay, let the organiser know \u2014 only they can turn payment on for this event."
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
title: "Finish in the ticketing checkout",
|
|
86
|
+
body: `${held} Payment for this event is taken elsewhere.`,
|
|
87
|
+
detail: "Nothing has been charged. Continue in the checkout on this page to pay for your seats."
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function errorCopy(code) {
|
|
91
|
+
switch (code) {
|
|
92
|
+
case "gateway_not_connected":
|
|
93
|
+
case "payments_not_enabled_for_event":
|
|
94
|
+
return "This event is not taking online payments. Contact the organiser to buy these seats.";
|
|
95
|
+
case "provider_mismatch":
|
|
96
|
+
return "This event\u2019s payment setup changed while you were choosing. Nothing was charged \u2014 please try again.";
|
|
97
|
+
case "hold_not_active":
|
|
98
|
+
case "hold_not_found":
|
|
99
|
+
return "Your seats were released before checkout started. Nothing was charged \u2014 please pick again.";
|
|
100
|
+
case "checkout_already_started":
|
|
101
|
+
return "A payment for these seats is already in progress. Finish that one, or wait for it to time out before starting again.";
|
|
102
|
+
case "event_closed":
|
|
103
|
+
return "Sales for this event have closed.";
|
|
104
|
+
case "gateway_currency_mismatch":
|
|
105
|
+
case "unsupported_currency":
|
|
106
|
+
case "mixed_currency_hold":
|
|
107
|
+
case "price_unusable":
|
|
108
|
+
return "These seats cannot be checked out right now because of a pricing configuration problem. Nothing was charged \u2014 please contact the organiser.";
|
|
109
|
+
case "rate_limited":
|
|
110
|
+
return "Too many attempts. Wait a moment and try again.";
|
|
111
|
+
default:
|
|
112
|
+
return "We could not start the payment. Nothing was charged \u2014 please try again.";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function loadRazorpay() {
|
|
116
|
+
const existing = window.Razorpay;
|
|
117
|
+
if (existing) return Promise.resolve(existing);
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const previous = document.querySelector(`script[src="${RAZORPAY_SCRIPT}"]`);
|
|
120
|
+
const tag = previous ?? document.createElement("script");
|
|
121
|
+
const done = () => {
|
|
122
|
+
const ctor = window.Razorpay;
|
|
123
|
+
if (ctor) resolve(ctor);
|
|
124
|
+
else reject(new Error("razorpay_unavailable"));
|
|
125
|
+
};
|
|
126
|
+
tag.addEventListener("load", done, { once: true });
|
|
127
|
+
tag.addEventListener("error", () => reject(new Error("razorpay_script_failed")), { once: true });
|
|
128
|
+
if (previous) return;
|
|
129
|
+
tag.src = RAZORPAY_SCRIPT;
|
|
130
|
+
tag.async = true;
|
|
131
|
+
document.head.appendChild(tag);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function el(tag, className, text) {
|
|
135
|
+
const node = document.createElement(tag);
|
|
136
|
+
if (className) node.className = className;
|
|
137
|
+
if (text !== void 0) node.textContent = text;
|
|
138
|
+
return node;
|
|
139
|
+
}
|
|
140
|
+
function mountCheckout(mount) {
|
|
141
|
+
ensureStyle();
|
|
142
|
+
let live = true;
|
|
143
|
+
let confirmed = false;
|
|
144
|
+
const scrim = el("div", "sl-hco");
|
|
145
|
+
scrim.setAttribute("role", "dialog");
|
|
146
|
+
scrim.setAttribute("aria-modal", "true");
|
|
147
|
+
const card = el("div", "sl-hco-card");
|
|
148
|
+
scrim.appendChild(card);
|
|
149
|
+
const destroy = () => {
|
|
150
|
+
live = false;
|
|
151
|
+
scrim.remove();
|
|
152
|
+
document.removeEventListener("keydown", onKey, true);
|
|
153
|
+
};
|
|
154
|
+
const cancel = () => {
|
|
155
|
+
destroy();
|
|
156
|
+
mount.onCancel();
|
|
157
|
+
};
|
|
158
|
+
function onKey(event) {
|
|
159
|
+
if (event.key !== "Escape" || !scrim.isConnected) return;
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
cancel();
|
|
163
|
+
}
|
|
164
|
+
document.addEventListener("keydown", onKey, true);
|
|
165
|
+
const title = el("h2", "sl-hco-title", "Checkout");
|
|
166
|
+
const titleId = `sl-hco-t-${Math.random().toString(36).slice(2, 8)}`;
|
|
167
|
+
title.id = titleId;
|
|
168
|
+
scrim.setAttribute("aria-labelledby", titleId);
|
|
169
|
+
const show = (...nodes) => {
|
|
170
|
+
card.replaceChildren(title, ...nodes);
|
|
171
|
+
};
|
|
172
|
+
const fail = (message) => {
|
|
173
|
+
if (!live) return;
|
|
174
|
+
const status = el("p", "sl-hco-status sl-hco-error", message);
|
|
175
|
+
status.setAttribute("role", "alert");
|
|
176
|
+
const back2 = el("button", "sl-hco-back", "Back to seats");
|
|
177
|
+
back2.type = "button";
|
|
178
|
+
back2.addEventListener("click", cancel);
|
|
179
|
+
show(status, back2);
|
|
180
|
+
};
|
|
181
|
+
const waiting = (message) => {
|
|
182
|
+
if (!live) return;
|
|
183
|
+
const status = el("p", "sl-hco-status", message);
|
|
184
|
+
status.setAttribute("role", "status");
|
|
185
|
+
show(status);
|
|
186
|
+
};
|
|
187
|
+
const awaitConfirmation = async (orderId) => {
|
|
188
|
+
waiting("Payment received \u2014 confirming your seats\u2026");
|
|
189
|
+
const deadline = Date.now() + CONFIRM_TIMEOUT_MS;
|
|
190
|
+
while (live && Date.now() < deadline) {
|
|
191
|
+
try {
|
|
192
|
+
const body = await mount.orderStatus(orderId);
|
|
193
|
+
if (!live) return;
|
|
194
|
+
if (body.status === "confirmed") {
|
|
195
|
+
confirmed = true;
|
|
196
|
+
title.textContent = "Your tickets are confirmed";
|
|
197
|
+
const status = el(
|
|
198
|
+
"p",
|
|
199
|
+
"sl-hco-status",
|
|
200
|
+
`${body.seatCount} ${body.seatCount === 1 ? "seat" : "seats"} confirmed. We have emailed your tickets.`
|
|
201
|
+
);
|
|
202
|
+
const receipt = el("dl", "sl-hco-receipt");
|
|
203
|
+
receipt.append(
|
|
204
|
+
el("dt", void 0, "Paid"),
|
|
205
|
+
el("dd", void 0, `${body.amountFormatted} ${body.currency}`),
|
|
206
|
+
el("dt", void 0, "Order"),
|
|
207
|
+
el("dd", "sl-hco-ref", body.orderId)
|
|
208
|
+
);
|
|
209
|
+
const close = el("button", "sl-hco-back", "Close");
|
|
210
|
+
close.type = "button";
|
|
211
|
+
close.addEventListener("click", cancel);
|
|
212
|
+
show(status, receipt, close);
|
|
213
|
+
mount.onConfirmed(body);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (body.status === "failed" || body.status === "expired") {
|
|
217
|
+
fail(body.status === "expired" ? "Your seats were released before payment completed. Nothing was charged \u2014 please pick again." : "The payment did not complete. If you were charged, it has been refunded automatically.");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
await new Promise((resolve) => setTimeout(resolve, CONFIRM_POLL_MS));
|
|
223
|
+
}
|
|
224
|
+
if (!live || confirmed) return;
|
|
225
|
+
fail("Still confirming with the payment provider. If your payment went through, your tickets will arrive by email shortly \u2014 you do not need to pay again.");
|
|
226
|
+
};
|
|
227
|
+
if (mount.state.kind === "unavailable") {
|
|
228
|
+
const copy = unavailableCopy(mount.state.reason, mount.state.seatCount);
|
|
229
|
+
title.textContent = copy.title;
|
|
230
|
+
const kicker = el("p", "sl-hco-label", "Seats held");
|
|
231
|
+
const back2 = el("button", "sl-hco-back", "Back to seat map");
|
|
232
|
+
back2.type = "button";
|
|
233
|
+
back2.addEventListener("click", cancel);
|
|
234
|
+
card.replaceChildren(
|
|
235
|
+
kicker,
|
|
236
|
+
title,
|
|
237
|
+
el("p", "sl-hco-status", copy.body),
|
|
238
|
+
el("p", "sl-hco-note", copy.detail),
|
|
239
|
+
back2
|
|
240
|
+
);
|
|
241
|
+
mount.root.appendChild(scrim);
|
|
242
|
+
back2.focus();
|
|
243
|
+
return { destroy };
|
|
244
|
+
}
|
|
245
|
+
if (mount.state.kind === "resume") {
|
|
246
|
+
mount.root.appendChild(scrim);
|
|
247
|
+
void awaitConfirmation(mount.state.orderId);
|
|
248
|
+
return { destroy };
|
|
249
|
+
}
|
|
250
|
+
const { order, provider } = mount.state;
|
|
251
|
+
const summary = el("div", "sl-hco-summary");
|
|
252
|
+
const seats = el("div", "sl-hco-seats");
|
|
253
|
+
for (const label of order.labels) seats.appendChild(el("span", "sl-hco-seat", label));
|
|
254
|
+
const totalText = formatMoney(order.total, order.currency);
|
|
255
|
+
const totalRow = el("div", "sl-hco-total");
|
|
256
|
+
totalRow.append(el("span", void 0, "Total"), el("strong", void 0, totalText));
|
|
257
|
+
summary.append(seats, totalRow);
|
|
258
|
+
const form = el("form", "sl-hco-form");
|
|
259
|
+
const emailLabel = el("label", "sl-hco-label", "Email \u2014 your tickets go here");
|
|
260
|
+
const email = el("input", "sl-hco-input");
|
|
261
|
+
email.type = "email";
|
|
262
|
+
email.required = true;
|
|
263
|
+
email.autocomplete = "email";
|
|
264
|
+
email.placeholder = "you@example.com";
|
|
265
|
+
email.id = `${titleId}-email`;
|
|
266
|
+
emailLabel.htmlFor = email.id;
|
|
267
|
+
const nameLabel = el("label", "sl-hco-label", "Name (optional)");
|
|
268
|
+
const name = el("input", "sl-hco-input");
|
|
269
|
+
name.type = "text";
|
|
270
|
+
name.autocomplete = "name";
|
|
271
|
+
name.placeholder = "Your name";
|
|
272
|
+
name.id = `${titleId}-name`;
|
|
273
|
+
nameLabel.htmlFor = name.id;
|
|
274
|
+
const pay = el("button", "sl-hco-pay", `Pay ${totalText}`);
|
|
275
|
+
pay.type = "submit";
|
|
276
|
+
pay.disabled = true;
|
|
277
|
+
const back = el("button", "sl-hco-back", "Back to seats");
|
|
278
|
+
back.type = "button";
|
|
279
|
+
back.addEventListener("click", cancel);
|
|
280
|
+
const until = new Date(order.expiresAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
281
|
+
const note = el(
|
|
282
|
+
"p",
|
|
283
|
+
"sl-hco-note",
|
|
284
|
+
`Your seats are held until ${until}. Payment is handled by ${provider === "razorpay" ? "Razorpay" : "Stripe"} \u2014 we never see your card details.`
|
|
285
|
+
);
|
|
286
|
+
const emailValid = () => /.+@.+\..+/.test(email.value.trim());
|
|
287
|
+
email.addEventListener("input", () => {
|
|
288
|
+
pay.disabled = !emailValid();
|
|
289
|
+
});
|
|
290
|
+
const start = async () => {
|
|
291
|
+
waiting("Opening secure payment\u2026");
|
|
292
|
+
try {
|
|
293
|
+
const body = await mount.startSession({
|
|
294
|
+
holdId: order.holdId,
|
|
295
|
+
buyerEmail: email.value.trim(),
|
|
296
|
+
// Deliberately no `provider`: since W2 the EVENT row decides which
|
|
297
|
+
// gateway charges, and naming one here can only ever 409 on a mismatch
|
|
298
|
+
// the buyer cannot do anything about.
|
|
299
|
+
...name.value.trim() ? { buyerName: name.value.trim() } : {}
|
|
300
|
+
});
|
|
301
|
+
if (!live) return;
|
|
302
|
+
if (body.redirectUrl) {
|
|
303
|
+
waiting("Taking you to secure payment\u2026");
|
|
304
|
+
window.location.assign(body.redirectUrl);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (body.clientPayload) {
|
|
308
|
+
const payload = body.clientPayload;
|
|
309
|
+
const Razorpay = await loadRazorpay();
|
|
310
|
+
if (!live) return;
|
|
311
|
+
waiting("Waiting for payment\u2026");
|
|
312
|
+
new Razorpay({
|
|
313
|
+
key: payload.key,
|
|
314
|
+
order_id: payload.orderId,
|
|
315
|
+
amount: payload.amount,
|
|
316
|
+
currency: payload.currency,
|
|
317
|
+
name: payload.name,
|
|
318
|
+
prefill: payload.prefill,
|
|
319
|
+
// The handler fires on the browser's word alone, so it only starts the
|
|
320
|
+
// wait — the webhook is what actually confirms the order.
|
|
321
|
+
handler: () => {
|
|
322
|
+
void awaitConfirmation(body.orderId);
|
|
323
|
+
},
|
|
324
|
+
modal: { ondismiss: () => {
|
|
325
|
+
if (live) details();
|
|
326
|
+
} }
|
|
327
|
+
}).open();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
fail(errorCopy("gateway_unavailable"));
|
|
331
|
+
} catch (err) {
|
|
332
|
+
mount.onError?.(err);
|
|
333
|
+
fail(errorCopy(err?.code));
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
form.addEventListener("submit", (event) => {
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
if (emailValid()) void start();
|
|
339
|
+
});
|
|
340
|
+
form.append(emailLabel, email, nameLabel, name, pay, back, note);
|
|
341
|
+
const details = () => {
|
|
342
|
+
title.textContent = "Checkout";
|
|
343
|
+
show(summary, form);
|
|
344
|
+
pay.disabled = !emailValid();
|
|
345
|
+
};
|
|
346
|
+
details();
|
|
347
|
+
mount.root.appendChild(scrim);
|
|
348
|
+
email.focus();
|
|
349
|
+
return { destroy };
|
|
350
|
+
}
|
|
351
|
+
var CONFIRM_TIMEOUT_MS, CONFIRM_POLL_MS, RAZORPAY_SCRIPT, STYLE_ID, CSS;
|
|
352
|
+
var init_hostedCheckout = __esm({
|
|
353
|
+
"src/hostedCheckout.ts"() {
|
|
354
|
+
"use strict";
|
|
355
|
+
CONFIRM_TIMEOUT_MS = 9e4;
|
|
356
|
+
CONFIRM_POLL_MS = 2e3;
|
|
357
|
+
RAZORPAY_SCRIPT = "https://checkout.razorpay.com/v1/checkout.js";
|
|
358
|
+
STYLE_ID = "seatlayer-checkout-style";
|
|
359
|
+
CSS = /* @sl-css */
|
|
360
|
+
`
|
|
361
|
+
.sl-hco{position:absolute;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
|
|
362
|
+
padding:16px;background:color-mix(in srgb, var(--sl-bg) 82%, transparent);backdrop-filter:blur(3px)}
|
|
363
|
+
.sl-hco-card{width:100%;max-width:380px;max-height:100%;overflow:auto;padding:22px;
|
|
364
|
+
background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);
|
|
365
|
+
border-radius:var(--sl-radius);box-shadow:0 18px 48px rgba(0,0,0,.28)}
|
|
366
|
+
.sl-hco-title{margin:0 0 14px;font-size:19px;font-weight:650;letter-spacing:-.01em}
|
|
367
|
+
.sl-hco-summary{margin-bottom:16px;padding-bottom:14px;border-bottom:1px solid var(--sl-line)}
|
|
368
|
+
.sl-hco-seats{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}
|
|
369
|
+
.sl-hco-seat{padding:3px 8px;font-size:12px;font-weight:600;border-radius:calc(var(--sl-radius) * .5);
|
|
370
|
+
background:var(--sl-bg);border:1px solid var(--sl-line)}
|
|
371
|
+
.sl-hco-total{display:flex;justify-content:space-between;align-items:baseline;font-size:14px}
|
|
372
|
+
.sl-hco-total strong{font-size:17px;font-weight:700}
|
|
373
|
+
.sl-hco-label{display:block;margin:12px 0 5px;font-size:12px;font-weight:600;color:var(--sl-muted)}
|
|
374
|
+
.sl-hco-input{width:100%;padding:10px 11px;font:inherit;font-size:15px;color:var(--sl-text);
|
|
375
|
+
background:var(--sl-bg);border:1px solid var(--sl-line);border-radius:calc(var(--sl-radius) * .55)}
|
|
376
|
+
.sl-hco-input:focus-visible{outline:2px solid var(--sl-accent);outline-offset:1px}
|
|
377
|
+
.sl-hco-pay{width:100%;margin-top:16px;padding:12px;font:inherit;font-size:15px;font-weight:650;
|
|
378
|
+
color:var(--sl-accent-ink);background:var(--sl-accent);border:0;
|
|
379
|
+
border-radius:calc(var(--sl-radius) * .55);cursor:pointer}
|
|
380
|
+
.sl-hco-pay[disabled]{opacity:.5;cursor:default}
|
|
381
|
+
.sl-hco-back{width:100%;margin-top:8px;padding:10px;font:inherit;font-size:14px;color:var(--sl-muted);
|
|
382
|
+
background:none;border:0;cursor:pointer;text-decoration:underline}
|
|
383
|
+
.sl-hco-note{margin:12px 0 0;font-size:12px;line-height:1.5;color:var(--sl-muted)}
|
|
384
|
+
.sl-hco-status{margin:8px 0 0;font-size:14px;line-height:1.55}
|
|
385
|
+
.sl-hco-error{color:var(--sl-danger, #c0392b)}
|
|
386
|
+
.sl-hco-receipt{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;margin:14px 0 0;font-size:13px}
|
|
387
|
+
.sl-hco-receipt dt{color:var(--sl-muted)}
|
|
388
|
+
.sl-hco-receipt dd{margin:0;text-align:right}
|
|
389
|
+
.sl-hco-ref{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;word-break:break-all}
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
38
394
|
// src/index.ts
|
|
39
395
|
var index_exports = {};
|
|
40
396
|
__export(index_exports, {
|
|
@@ -185,6 +541,41 @@ var PubApi = class {
|
|
|
185
541
|
body: { holdId, ...ttlMs ? { ttlMs } : {} }
|
|
186
542
|
});
|
|
187
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Which gateways this event can actually take money through — the question
|
|
546
|
+
* `checkout: 'hosted'` has to answer BEFORE it shows a buyer a Pay button, so
|
|
547
|
+
* the answer is never discovered by failing a payment.
|
|
548
|
+
*
|
|
549
|
+
* Anonymous, and it discloses no account, key, mode or currency for a gateway
|
|
550
|
+
* that did not match.
|
|
551
|
+
*/
|
|
552
|
+
paymentOptions(key) {
|
|
553
|
+
return this.request(`/pub/events/${encodeURIComponent(key)}/payment-options`);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Turn a live hold into an order and start a payment.
|
|
557
|
+
*
|
|
558
|
+
* The amount is NOT sent: the server recomputes it from the hold's own items,
|
|
559
|
+
* which is the only reason a browser cannot alter what it pays. Nor is the
|
|
560
|
+
* PROVIDER — the event row decides which gateway charges, and a `provider` in
|
|
561
|
+
* the body is checked rather than obeyed (409 `provider_mismatch`). Omitting
|
|
562
|
+
* it is the shape that cannot disagree.
|
|
563
|
+
*/
|
|
564
|
+
startCheckout(key, input) {
|
|
565
|
+
return this.request(`/pub/events/${encodeURIComponent(key)}/checkout`, {
|
|
566
|
+
method: "POST",
|
|
567
|
+
body: input
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Poll an order while its gateway webhook lands. The order id is an
|
|
572
|
+
* unguessable token the buyer already holds, so it acts as the capability —
|
|
573
|
+
* which is also why a buyer returning from a gateway page can be told what
|
|
574
|
+
* happened with nothing but the id in the return URL.
|
|
575
|
+
*/
|
|
576
|
+
orderStatus(orderId) {
|
|
577
|
+
return this.request(`/pub/orders/${encodeURIComponent(orderId)}/status`);
|
|
578
|
+
}
|
|
188
579
|
/**
|
|
189
580
|
* Mint a one-use subscribe ticket for the next socket attempt (protocol doc
|
|
190
581
|
* §3). The bearer travels here, over ordinary HTTPS where CORS and Origin
|
|
@@ -845,9 +1236,9 @@ var DEFAULT_API_BASE = "https://api.seatlayer.io";
|
|
|
845
1236
|
var DEFAULT_MAX_SELECTION = 10;
|
|
846
1237
|
function resolveContainer(container) {
|
|
847
1238
|
if (typeof container === "string") {
|
|
848
|
-
const
|
|
849
|
-
if (!
|
|
850
|
-
return
|
|
1239
|
+
const el2 = document.querySelector(container);
|
|
1240
|
+
if (!el2) throw new Error(`seatmap: container "${container}" not found`);
|
|
1241
|
+
return el2;
|
|
851
1242
|
}
|
|
852
1243
|
if (!(container instanceof HTMLElement)) {
|
|
853
1244
|
throw new Error("seatmap: container must be a CSS selector or an HTMLElement");
|
|
@@ -1738,7 +2129,8 @@ var EmbeddedDesigner = class {
|
|
|
1738
2129
|
}
|
|
1739
2130
|
buildSkeleton(overlay) {
|
|
1740
2131
|
const style = document.createElement("style");
|
|
1741
|
-
style.textContent =
|
|
2132
|
+
style.textContent = /* @sl-css */
|
|
2133
|
+
`
|
|
1742
2134
|
@media (prefers-reduced-motion: no-preference) {
|
|
1743
2135
|
@keyframes seatlayer-designer-shimmer {
|
|
1744
2136
|
0% { background-position: -320px 0; }
|
|
@@ -1877,9 +2269,9 @@ function pointInPolygon(x, y, poly) {
|
|
|
1877
2269
|
}
|
|
1878
2270
|
function resolveContainer3(container) {
|
|
1879
2271
|
if (typeof container === "string") {
|
|
1880
|
-
const
|
|
1881
|
-
if (!
|
|
1882
|
-
return
|
|
2272
|
+
const el2 = document.querySelector(container);
|
|
2273
|
+
if (!el2) throw new Error(`seatmap: container "${container}" not found`);
|
|
2274
|
+
return el2;
|
|
1883
2275
|
}
|
|
1884
2276
|
if (!(container instanceof HTMLElement)) {
|
|
1885
2277
|
throw new Error("seatmap: container must be a CSS selector or an HTMLElement");
|
|
@@ -1918,20 +2310,45 @@ function hasWebGL2() {
|
|
|
1918
2310
|
}
|
|
1919
2311
|
return _webgl2Cache;
|
|
1920
2312
|
}
|
|
2313
|
+
function cdnChunkUrl(fileName) {
|
|
2314
|
+
const base = SEATLAYER_MODULE_URL ?? (typeof location !== "undefined" ? location.href : void 0);
|
|
2315
|
+
if (!base) throw new Error(`seatlayer: cannot resolve the ${fileName} chunk URL`);
|
|
2316
|
+
return new URL(`./${fileName}`, base).href;
|
|
2317
|
+
}
|
|
1921
2318
|
async function loadVenue3d() {
|
|
1922
2319
|
if (typeof __SEATLAYER_CDN__ !== "undefined" && __SEATLAYER_CDN__) {
|
|
1923
|
-
const base = SEATLAYER_MODULE_URL ?? (typeof location !== "undefined" ? location.href : void 0);
|
|
1924
|
-
if (!base) throw new Error("seatlayer: cannot resolve the 3D view chunk URL");
|
|
1925
|
-
const url = new URL("./seatlayer-view3d.mjs", base).href;
|
|
1926
2320
|
return import(
|
|
1927
2321
|
/* @vite-ignore */
|
|
1928
|
-
|
|
2322
|
+
cdnChunkUrl("seatlayer-view3d.mjs")
|
|
1929
2323
|
);
|
|
1930
2324
|
}
|
|
1931
2325
|
return import("@seatlayer/core/view3d");
|
|
1932
2326
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
2327
|
+
async function loadPanorama() {
|
|
2328
|
+
if (typeof __SEATLAYER_CDN__ !== "undefined" && __SEATLAYER_CDN__) {
|
|
2329
|
+
return import(
|
|
2330
|
+
/* @vite-ignore */
|
|
2331
|
+
cdnChunkUrl("seatlayer-panorama.mjs")
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
return import("@seatlayer/core");
|
|
2335
|
+
}
|
|
2336
|
+
async function loadHostedCheckout() {
|
|
2337
|
+
if (typeof __SEATLAYER_CDN__ !== "undefined" && __SEATLAYER_CDN__) {
|
|
2338
|
+
return import(
|
|
2339
|
+
/* @vite-ignore */
|
|
2340
|
+
cdnChunkUrl("seatlayer-checkout.mjs")
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
return Promise.resolve().then(() => (init_hostedCheckout(), hostedCheckout_exports));
|
|
2344
|
+
}
|
|
2345
|
+
function paymentsOffReason(reason) {
|
|
2346
|
+
return reason === "unavailable_for_event" || reason === "payments_off_for_event" ? reason : "not_configured";
|
|
2347
|
+
}
|
|
2348
|
+
var STYLE_ID2 = "seatlayer-picker-style";
|
|
2349
|
+
var CSS2 = (
|
|
2350
|
+
/* @sl-css */
|
|
2351
|
+
`
|
|
1935
2352
|
.sl-picker{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:420px;overflow:hidden;
|
|
1936
2353
|
background:var(--sl-bg);color:var(--sl-text);font-family:var(--sl-font);border-radius:var(--sl-radius);
|
|
1937
2354
|
--sl-r-sm:calc(var(--sl-radius) * .55);
|
|
@@ -2668,13 +3085,14 @@ var CSS = `
|
|
|
2668
3085
|
.sl-modal-scrim{position:fixed;inset:0;z-index:2147483000;background:rgba(5,7,12,.66);display:flex;align-items:center;justify-content:center;padding:18px}
|
|
2669
3086
|
.sl-modal-frame{width:min(1200px,100%);height:min(820px,100%);border-radius:16px;overflow:hidden;box-shadow:0 40px 120px -30px rgba(0,0,0,.8)}
|
|
2670
3087
|
@media(max-width:640px){.sl-modal-scrim{padding:0}.sl-modal-frame{width:100%;height:100%;border-radius:0}}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
3088
|
+
`
|
|
3089
|
+
);
|
|
3090
|
+
function ensureStyle2() {
|
|
3091
|
+
if (document.getElementById(STYLE_ID2)) return;
|
|
3092
|
+
const el2 = document.createElement("style");
|
|
3093
|
+
el2.id = STYLE_ID2;
|
|
3094
|
+
el2.textContent = CSS2;
|
|
3095
|
+
document.head.appendChild(el2);
|
|
2678
3096
|
}
|
|
2679
3097
|
function resolveTokens(chart, host) {
|
|
2680
3098
|
const accent = host?.accent ?? chart?.accent ?? "#f4b740";
|
|
@@ -2733,6 +3151,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
2733
3151
|
this.handedOff = false;
|
|
2734
3152
|
/** Guards single onBooked + single success overlay per hold. */
|
|
2735
3153
|
this.bookedShown = false;
|
|
3154
|
+
/**
|
|
3155
|
+
* In-flight or settled `payment-options` for this event, started at render in
|
|
3156
|
+
* hosted mode. One request, kicked off while the buyer is still choosing, so
|
|
3157
|
+
* pressing Pay does not wait on a lookup whose answer never changes mid-session.
|
|
3158
|
+
*/
|
|
3159
|
+
this.paymentOptions = null;
|
|
3160
|
+
/** The mounted payment card, while one is up. */
|
|
3161
|
+
this.checkoutPanel = null;
|
|
2736
3162
|
this.extendEl = null;
|
|
2737
3163
|
this.bookedEl = null;
|
|
2738
3164
|
this.gaQty = /* @__PURE__ */ new Map();
|
|
@@ -2847,6 +3273,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
2847
3273
|
onObjectUnavailable: (event) => this.opts.onSelectedObjectUnavailable?.(event)
|
|
2848
3274
|
});
|
|
2849
3275
|
this.api = options.transport ?? this.pubApi;
|
|
3276
|
+
if (options.checkout === "hosted" && !this.pubApi) {
|
|
3277
|
+
console.warn(
|
|
3278
|
+
'seatlayer: checkout: "hosted" needs the widget\'s own transport \u2014 a custom `transport` owns its backend, so the picker is staying on onCheckout for this mount.'
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
this.checkoutMode = options.checkout === "hosted" && this.pubApi ? "hosted" : "handoff";
|
|
2850
3282
|
this.maxTickets = Math.max(1, Math.floor(options.maxSelection ?? DEFAULT_MAX_SELECTION2));
|
|
2851
3283
|
this.cbSafe = readStoredColorblind() ?? !!options.colorblindSafe;
|
|
2852
3284
|
this.controller = new import_core2.PickerController({
|
|
@@ -3152,7 +3584,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3152
3584
|
}
|
|
3153
3585
|
/** Mount the full picker as a document-level modal. Resolves after render. */
|
|
3154
3586
|
static async open(options) {
|
|
3155
|
-
|
|
3587
|
+
ensureStyle2();
|
|
3156
3588
|
const scrim = document.createElement("div");
|
|
3157
3589
|
scrim.className = "sl-modal-scrim";
|
|
3158
3590
|
const frame = document.createElement("div");
|
|
@@ -3207,9 +3639,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3207
3639
|
async render() {
|
|
3208
3640
|
if (this.rendered) return this;
|
|
3209
3641
|
this.rendered = true;
|
|
3210
|
-
|
|
3642
|
+
ensureStyle2();
|
|
3211
3643
|
await (0, import_core2.loadLocale)(this.opts.locale);
|
|
3212
3644
|
if (this.opts.messages) (0, import_core2.setStringOverrides)(this.opts.messages);
|
|
3645
|
+
if (this.checkoutMode === "hosted") this.paymentOptions = this.pubApi.paymentOptions(this.opts.event);
|
|
3213
3646
|
const mount = resolveContainer3(this.opts.container);
|
|
3214
3647
|
const root = document.createElement("div");
|
|
3215
3648
|
root.className = "sl-picker";
|
|
@@ -3294,8 +3727,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
3294
3727
|
</div>
|
|
3295
3728
|
</div>
|
|
3296
3729
|
</div>`;
|
|
3297
|
-
root.querySelectorAll("[data-ref]").forEach((
|
|
3298
|
-
this.els[
|
|
3730
|
+
root.querySelectorAll("[data-ref]").forEach((el2) => {
|
|
3731
|
+
this.els[el2.dataset.ref] = el2;
|
|
3299
3732
|
});
|
|
3300
3733
|
this.mapHost = this.els.map;
|
|
3301
3734
|
const applyLayout = () => {
|
|
@@ -3507,8 +3940,40 @@ var SeatPicker = class _SeatPicker {
|
|
|
3507
3940
|
if (this.salesClosed) this.applySalesClosed();
|
|
3508
3941
|
this.syncPrices();
|
|
3509
3942
|
this.syncTray();
|
|
3943
|
+
this.resumeHostedOrder();
|
|
3510
3944
|
return this;
|
|
3511
3945
|
}
|
|
3946
|
+
/**
|
|
3947
|
+
* A hosted gateway returned this buyer to a page that runs the widget, with
|
|
3948
|
+
* `?order=…&status=…` in the URL. Pick the order up and finish the story.
|
|
3949
|
+
*
|
|
3950
|
+
* Only `success` resumes. `cancelled` means the buyer backed out at the
|
|
3951
|
+
* gateway and their seats are still held — the map they are looking at IS the
|
|
3952
|
+
* right screen, and opening a card to say "you cancelled" would be noise.
|
|
3953
|
+
*
|
|
3954
|
+
* The two parameters are then stripped with `replaceState`, because they are a
|
|
3955
|
+
* one-shot instruction: leaving them in place would re-open the confirmation
|
|
3956
|
+
* on every later navigation, and would carry an order id into browser history
|
|
3957
|
+
* and any Referer this page later sends. `status` is only ever removed
|
|
3958
|
+
* alongside an `order` we actually consumed, so a host page that uses a
|
|
3959
|
+
* `status` parameter of its own keeps it.
|
|
3960
|
+
*/
|
|
3961
|
+
resumeHostedOrder() {
|
|
3962
|
+
if (this.checkoutMode !== "hosted" || typeof location === "undefined") return;
|
|
3963
|
+
const params = new URLSearchParams(location.search);
|
|
3964
|
+
const orderId = params.get("order");
|
|
3965
|
+
if (!orderId) return;
|
|
3966
|
+
const status = params.get("status");
|
|
3967
|
+
params.delete("order");
|
|
3968
|
+
params.delete("status");
|
|
3969
|
+
try {
|
|
3970
|
+
const query = params.toString();
|
|
3971
|
+
history.replaceState(history.state, "", `${location.pathname}${query ? `?${query}` : ""}${location.hash}`);
|
|
3972
|
+
} catch {
|
|
3973
|
+
}
|
|
3974
|
+
if (status !== "success") return;
|
|
3975
|
+
void this.openCheckoutPanel({ kind: "resume", orderId });
|
|
3976
|
+
}
|
|
3512
3977
|
/**
|
|
3513
3978
|
* Move layout-dependent chrome between its wide dock (map regions / zoom
|
|
3514
3979
|
* column) and its narrow dock (the sheet's consolidated Filters row), and
|
|
@@ -3534,27 +3999,27 @@ var SeatPicker = class _SeatPicker {
|
|
|
3534
3999
|
}
|
|
3535
4000
|
/** The "Need more time?" prompt shown in the hold's final EXTEND_PROMPT_MS. */
|
|
3536
4001
|
buildExtendPrompt() {
|
|
3537
|
-
const
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
(this.regions["bottom-center"] ?? this.els.map).appendChild(
|
|
3542
|
-
this.extendEl =
|
|
3543
|
-
this.els.extendTxt =
|
|
3544
|
-
this.els.extendBtn =
|
|
4002
|
+
const el2 = document.createElement("div");
|
|
4003
|
+
el2.className = "sl-extend";
|
|
4004
|
+
el2.setAttribute("role", "status");
|
|
4005
|
+
el2.innerHTML = `<span class="sl-extend-txt" data-ref="extendTxt"></span><button type="button" class="sl-extend-btn" data-ref="extendBtn"></button>`;
|
|
4006
|
+
(this.regions["bottom-center"] ?? this.els.map).appendChild(el2);
|
|
4007
|
+
this.extendEl = el2;
|
|
4008
|
+
this.els.extendTxt = el2.querySelector('[data-ref="extendTxt"]');
|
|
4009
|
+
this.els.extendBtn = el2.querySelector('[data-ref="extendBtn"]');
|
|
3545
4010
|
this.els.extendBtn.textContent = "Add time";
|
|
3546
4011
|
this.els.extendBtn.addEventListener("click", () => void this.handleExtend());
|
|
3547
4012
|
}
|
|
3548
4013
|
/** Success overlay + onBooked fire when the held seats settle to booked. */
|
|
3549
4014
|
buildBookedOverlay() {
|
|
3550
|
-
const
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
this.root.appendChild(
|
|
3556
|
-
this.bookedEl =
|
|
3557
|
-
this.els.bookedSub =
|
|
4015
|
+
const el2 = document.createElement("div");
|
|
4016
|
+
el2.className = "sl-booked";
|
|
4017
|
+
el2.setAttribute("role", "status");
|
|
4018
|
+
el2.setAttribute("aria-live", "polite");
|
|
4019
|
+
el2.innerHTML = `<div class="sl-booked-badge"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></div><div class="sl-booked-title">You're all set</div><div class="sl-booked-sub" data-ref="bookedSub"></div>`;
|
|
4020
|
+
this.root.appendChild(el2);
|
|
4021
|
+
this.bookedEl = el2;
|
|
4022
|
+
this.els.bookedSub = el2.querySelector('[data-ref="bookedSub"]');
|
|
3558
4023
|
}
|
|
3559
4024
|
/**
|
|
3560
4025
|
* Localized string with a literal fallback. `t()` returns the key itself for
|
|
@@ -3568,13 +4033,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3568
4033
|
/** Sold-out overlay — centered over the map, disabled waitlist stub (Gap 2). */
|
|
3569
4034
|
buildSoldoutOverlay() {
|
|
3570
4035
|
if (!this.els.map) return;
|
|
3571
|
-
const
|
|
3572
|
-
|
|
3573
|
-
|
|
4036
|
+
const el2 = document.createElement("div");
|
|
4037
|
+
el2.className = "sl-soldout";
|
|
4038
|
+
el2.setAttribute("role", "status");
|
|
3574
4039
|
const name = (this.controller.doc?.theme?.brandName ?? this.opts.theme?.brandName ?? this.els.name?.textContent ?? this.tf("picker.soldOutEyebrow", "This event")).toUpperCase();
|
|
3575
|
-
|
|
3576
|
-
this.els.map.appendChild(
|
|
3577
|
-
this.soldoutEl =
|
|
4040
|
+
el2.innerHTML = `<div class="sl-soldout-eyebrow">${name}</div><div class="sl-soldout-title">${this.tf("picker.soldOutTitle", "Sold out")}</div><p class="sl-soldout-copy">${this.tf("picker.soldOutCopy", "Every seat is gone. Join the waitlist and we\u2019ll email you if seats are released.")}</p><button type="button" class="sl-soldout-btn" disabled>${this.tf("picker.waitlist", "Join waitlist")}</button>`;
|
|
4041
|
+
this.els.map.appendChild(el2);
|
|
4042
|
+
this.soldoutEl = el2;
|
|
3578
4043
|
}
|
|
3579
4044
|
/**
|
|
3580
4045
|
* Recompute the sold-out state on every price/availability sync. Sold-out ⇔
|
|
@@ -3631,10 +4096,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
3631
4096
|
if (this.badgeHidden(chartTheme)) return;
|
|
3632
4097
|
const foot = this.els.foot;
|
|
3633
4098
|
if (!foot) return;
|
|
3634
|
-
const
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
foot.appendChild(
|
|
4099
|
+
const el2 = document.createElement("div");
|
|
4100
|
+
el2.className = "sl-powered";
|
|
4101
|
+
el2.innerHTML = `<span class="sl-powered-mark" aria-hidden="true">` + SEATLAYER_ATTRIBUTION_MARK_SVG + `</span><span>${this.tf("picker.poweredBy", "Powered by SeatLayer")}</span>`;
|
|
4102
|
+
foot.appendChild(el2);
|
|
3638
4103
|
}
|
|
3639
4104
|
// ---- Feature 6: chrome anchor regions -------------------------------------
|
|
3640
4105
|
/**
|
|
@@ -3648,11 +4113,11 @@ var SeatPicker = class _SeatPicker {
|
|
|
3648
4113
|
if (!this.els.map) return;
|
|
3649
4114
|
const REGIONS = ["top-left", "top-center", "top-right", "left-rail", "bottom-left", "bottom-center", "bottom-right"];
|
|
3650
4115
|
for (const region of REGIONS) {
|
|
3651
|
-
const
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
this.els.map.appendChild(
|
|
3655
|
-
this.regions[region] =
|
|
4116
|
+
const el2 = document.createElement("div");
|
|
4117
|
+
el2.className = "sl-anchor";
|
|
4118
|
+
el2.dataset.region = region;
|
|
4119
|
+
this.els.map.appendChild(el2);
|
|
4120
|
+
this.regions[region] = el2;
|
|
3656
4121
|
}
|
|
3657
4122
|
}
|
|
3658
4123
|
// ---- F3 minimap -----------------------------------------------------------
|
|
@@ -3672,12 +4137,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
3672
4137
|
this.motionTimers.add(timer);
|
|
3673
4138
|
}
|
|
3674
4139
|
/** Restart one finite CSS animation without leaving a permanent state class. */
|
|
3675
|
-
animateOnce(
|
|
3676
|
-
if (!
|
|
3677
|
-
|
|
3678
|
-
void
|
|
3679
|
-
|
|
3680
|
-
this.scheduleMotion(() =>
|
|
4140
|
+
animateOnce(el2, className, duration = 600) {
|
|
4141
|
+
if (!el2 || this.reducedMotion()) return;
|
|
4142
|
+
el2.classList.remove(className);
|
|
4143
|
+
void el2.offsetWidth;
|
|
4144
|
+
el2.classList.add(className);
|
|
4145
|
+
this.scheduleMotion(() => el2.classList.remove(className), duration);
|
|
3681
4146
|
}
|
|
3682
4147
|
/** Selection feedback belongs on the selected seat, not across the whole map. */
|
|
3683
4148
|
flashPickedSeat(id) {
|
|
@@ -4331,13 +4796,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
4331
4796
|
const cat = this.controller.doc?.categories.find((candidate) => candidate.key === table.categoryKey);
|
|
4332
4797
|
const variable = table.bookingMode === "variable";
|
|
4333
4798
|
const typeWord = this.rowTypeWord(table);
|
|
4334
|
-
const
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
this.root.appendChild(
|
|
4338
|
-
this.tableDialogEl =
|
|
4799
|
+
const el2 = document.createElement("div");
|
|
4800
|
+
el2.className = "sl-table-scrim";
|
|
4801
|
+
el2.innerHTML = `<section class="sl-table-dialog" role="dialog" aria-modal="true" aria-labelledby="sl-table-title" aria-describedby="sl-table-copy"><div class="sl-table-head"><div class="sl-table-eyebrow">${esc3(variable ? `Flexible party \xB7 ${typeWord}` : `Whole ${typeWord}`)}</div><h2 class="sl-table-title" id="sl-table-title">${esc3(table.displayLabel ?? table.label)}</h2><p class="sl-table-copy" id="sl-table-copy">${variable ? `Choose how many guests will sit together. This table is held exclusively for your party.` : `All ${table.capacity} places are booked together as one exclusive table.`}</p></div><div class="sl-table-body"><div class="sl-table-summary"><span>${esc3(cat?.label ?? table.categoryKey)}</span><b data-table-unit>${this.money(this.paidPrice(table.categoryKey, table.tierId ?? null, table.price))} per guest</b><span class="muted">Table capacity</span><span>${table.capacity} guests</span><span class="muted">Total</span><b data-table-total></b></div>` + (variable ? `<label class="sl-table-qtylabel" id="sl-table-qty-label">Number of guests</label><div class="sl-table-stepper" role="group" aria-labelledby="sl-table-qty-label"><button type="button" data-table-step="-1" aria-label="Fewer guests">\u2212</button><output aria-live="polite" data-table-qty>${table.quantity}</output><button type="button" data-table-step="1" aria-label="More guests">+</button></div><div class="sl-table-range">Choose ${table.minOccupancy}\u2013${table.maxOccupancy} guests</div>` : `<input type="hidden" data-table-qty value="${table.capacity}">`) + `<div class="sl-table-actions"><button type="button" class="sl-table-cancel">Cancel</button><button type="button" class="sl-table-confirm">${held ? "Update table" : variable ? "Select table" : "Select whole table"}</button></div></div></section>`;
|
|
4802
|
+
this.root.appendChild(el2);
|
|
4803
|
+
this.tableDialogEl = el2;
|
|
4339
4804
|
this.renderTableDialogState();
|
|
4340
|
-
|
|
4805
|
+
el2.querySelectorAll("[data-table-step]").forEach((button) => {
|
|
4341
4806
|
button.addEventListener("click", () => {
|
|
4342
4807
|
if (!this.tableDialog) return;
|
|
4343
4808
|
const next = Math.max(
|
|
@@ -4348,14 +4813,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
4348
4813
|
this.renderTableDialogState();
|
|
4349
4814
|
});
|
|
4350
4815
|
});
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
if (event.target ===
|
|
4816
|
+
el2.querySelector(".sl-table-cancel").addEventListener("click", () => this.cancelTableDialog());
|
|
4817
|
+
el2.querySelector(".sl-table-confirm").addEventListener("click", () => void this.confirmTableDialog());
|
|
4818
|
+
el2.addEventListener("mousedown", (event) => {
|
|
4819
|
+
if (event.target === el2) this.cancelTableDialog();
|
|
4355
4820
|
});
|
|
4356
|
-
|
|
4821
|
+
el2.addEventListener("keydown", (event) => {
|
|
4357
4822
|
if (event.key !== "Tab") return;
|
|
4358
|
-
const focusable = [...
|
|
4823
|
+
const focusable = [...el2.querySelectorAll('button:not(:disabled),[tabindex]:not([tabindex="-1"])')];
|
|
4359
4824
|
if (!focusable.length) return;
|
|
4360
4825
|
const first = focusable[0];
|
|
4361
4826
|
const last = focusable[focusable.length - 1];
|
|
@@ -4367,22 +4832,22 @@ var SeatPicker = class _SeatPicker {
|
|
|
4367
4832
|
first.focus();
|
|
4368
4833
|
}
|
|
4369
4834
|
});
|
|
4370
|
-
requestAnimationFrame(() =>
|
|
4835
|
+
requestAnimationFrame(() => el2.querySelector(variable ? '[data-table-step="-1"]' : ".sl-table-confirm")?.focus());
|
|
4371
4836
|
}
|
|
4372
4837
|
renderTableDialogState() {
|
|
4373
4838
|
const table = this.tableDialog;
|
|
4374
|
-
const
|
|
4375
|
-
if (!table || !
|
|
4376
|
-
const output =
|
|
4839
|
+
const el2 = this.tableDialogEl;
|
|
4840
|
+
if (!table || !el2) return;
|
|
4841
|
+
const output = el2.querySelector("output[data-table-qty]");
|
|
4377
4842
|
if (output) output.value = String(table.quantity);
|
|
4378
|
-
const hidden =
|
|
4843
|
+
const hidden = el2.querySelector("input[data-table-qty]");
|
|
4379
4844
|
if (hidden) hidden.value = String(table.quantity);
|
|
4380
|
-
|
|
4845
|
+
el2.querySelectorAll("[data-table-step]").forEach((button) => {
|
|
4381
4846
|
const delta = Number(button.dataset.tableStep);
|
|
4382
4847
|
button.disabled = delta < 0 ? table.quantity <= table.minOccupancy : table.quantity >= table.maxOccupancy;
|
|
4383
4848
|
});
|
|
4384
4849
|
const unit = this.paidPrice(table.categoryKey, table.tierId ?? null, table.price);
|
|
4385
|
-
const total =
|
|
4850
|
+
const total = el2.querySelector("[data-table-total]");
|
|
4386
4851
|
if (total) total.textContent = this.money(unit * table.quantity);
|
|
4387
4852
|
}
|
|
4388
4853
|
async confirmTableDialog() {
|
|
@@ -4478,18 +4943,18 @@ var SeatPicker = class _SeatPicker {
|
|
|
4478
4943
|
details?.rowLabel || details?.objectType === "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${safe(this.rowTypeWord(details))}</span><span class="sl-confirm-value">${safe(this.rowShort(details) ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : "",
|
|
4479
4944
|
details?.objectType !== "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : ""
|
|
4480
4945
|
].filter(Boolean).join("");
|
|
4481
|
-
const
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
this.els.map.appendChild(
|
|
4489
|
-
this.confirmEl =
|
|
4946
|
+
const el2 = document.createElement("div");
|
|
4947
|
+
el2.className = "sl-confirm";
|
|
4948
|
+
el2.setAttribute("role", "dialog");
|
|
4949
|
+
el2.setAttribute("aria-modal", "true");
|
|
4950
|
+
el2.setAttribute("aria-label", `Confirm seat ${seat.label}`);
|
|
4951
|
+
el2.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
|
|
4952
|
+
el2.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + this.see3dConfirmHtml() + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
|
|
4953
|
+
this.els.map.appendChild(el2);
|
|
4954
|
+
this.confirmEl = el2;
|
|
4490
4955
|
this.reanchorConfirm();
|
|
4491
|
-
|
|
4492
|
-
|
|
4956
|
+
el2.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
|
|
4957
|
+
el2.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
|
|
4493
4958
|
if (this.buyerView === "venue3d") {
|
|
4494
4959
|
void this.view3dHandle?.flyToSeat(seat.id);
|
|
4495
4960
|
} else {
|
|
@@ -4498,9 +4963,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
4498
4963
|
void this.enter3d(seat.id);
|
|
4499
4964
|
}
|
|
4500
4965
|
});
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
requestAnimationFrame(() =>
|
|
4966
|
+
el2.querySelector(".sl-confirm-add").addEventListener("click", () => this.commitConfirm());
|
|
4967
|
+
el2.querySelector(".sl-confirm-cancel").addEventListener("click", () => this.cancelConfirm());
|
|
4968
|
+
requestAnimationFrame(() => el2.querySelector(".sl-confirm-add")?.focus());
|
|
4504
4969
|
}
|
|
4505
4970
|
reanchorConfirm() {
|
|
4506
4971
|
if (!this.confirmEl || !this.confirmSeat) return;
|
|
@@ -4559,10 +5024,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
4559
5024
|
* uploaded photo (seat.viewUrl) when present, else a panorama generated from
|
|
4560
5025
|
* the chart geometry — the stage placed at this seat's true bearing + size.
|
|
4561
5026
|
* Zero extra dependencies: an equirectangular image panned with `repeat-x`.
|
|
5027
|
+
*
|
|
5028
|
+
* Async only because the generator is a lazy chunk (see `loadPanorama`); an
|
|
5029
|
+
* organizer photo needs no generator and never waits on it. The two callers
|
|
5030
|
+
* are click handlers, so nothing observes the promise.
|
|
4562
5031
|
*/
|
|
4563
|
-
openSeatView(seat) {
|
|
5032
|
+
async openSeatView(seat) {
|
|
4564
5033
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
4565
|
-
this.closeSeatView();
|
|
4566
5034
|
const doc = this.controller.doc;
|
|
4567
5035
|
const activeId = this.controller.getActiveFloorId();
|
|
4568
5036
|
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
@@ -4574,18 +5042,27 @@ var SeatPicker = class _SeatPicker {
|
|
|
4574
5042
|
caption = (0, import_core2.t)("picker.panorama360");
|
|
4575
5043
|
real = true;
|
|
4576
5044
|
} else {
|
|
4577
|
-
|
|
5045
|
+
let pano2;
|
|
5046
|
+
try {
|
|
5047
|
+
const { generateSeatPanorama } = await loadPanorama();
|
|
5048
|
+
pano2 = generateSeatPanorama(seat, focal, this.allSeats());
|
|
5049
|
+
} catch (err) {
|
|
5050
|
+
this.opts.onError?.(err);
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
5053
|
+
if (!this.root || !this.seatViewEnabled()) return;
|
|
4578
5054
|
panoUrl = pano2.url;
|
|
4579
5055
|
caption = (0, import_core2.t)("picker.illustrationCaption", { m: pano2.distanceM });
|
|
4580
5056
|
}
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
this.
|
|
4588
|
-
|
|
5057
|
+
this.closeSeatView();
|
|
5058
|
+
const el2 = document.createElement("div");
|
|
5059
|
+
el2.className = "sl-view";
|
|
5060
|
+
el2.setAttribute("role", "dialog");
|
|
5061
|
+
el2.setAttribute("aria-label", (0, import_core2.t)("picker.viewFromSeat", { label: seat.label }));
|
|
5062
|
+
el2.innerHTML = `<div class="sl-view-head"><span class="sl-view-title">${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}</span><span class="sl-view-cap">${caption}</span><button type="button" class="sl-view-x" aria-label="Close"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div><div class="sl-view-pano"><span class="sl-view-badge">${real ? (0, import_core2.t)("picker.real360") : (0, import_core2.t)("picker.preview")}</span><span class="sl-view-hint">Drag to look around \xB7 scroll to zoom</span></div>`;
|
|
5063
|
+
this.root.appendChild(el2);
|
|
5064
|
+
this.viewEl = el2;
|
|
5065
|
+
const pano = el2.querySelector(".sl-view-pano");
|
|
4589
5066
|
pano.style.backgroundImage = `url("${panoUrl}")`;
|
|
4590
5067
|
const VFOV_DEG = 70;
|
|
4591
5068
|
const MAX_PITCH_DEG = 35;
|
|
@@ -4637,7 +5114,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4637
5114
|
pano.addEventListener("pointerup", onUp);
|
|
4638
5115
|
pano.addEventListener("pointercancel", onUp);
|
|
4639
5116
|
pano.addEventListener("wheel", onWheel, { passive: false });
|
|
4640
|
-
const closeBtn =
|
|
5117
|
+
const closeBtn = el2.querySelector(".sl-view-x");
|
|
4641
5118
|
closeBtn.addEventListener("click", () => this.closeSeatView());
|
|
4642
5119
|
const onKey = (e) => {
|
|
4643
5120
|
if (e.key === "Escape") {
|
|
@@ -4645,7 +5122,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4645
5122
|
this.closeSeatView();
|
|
4646
5123
|
}
|
|
4647
5124
|
};
|
|
4648
|
-
|
|
5125
|
+
el2.addEventListener("keydown", onKey);
|
|
4649
5126
|
closeBtn.focus();
|
|
4650
5127
|
this.viewCleanup = () => {
|
|
4651
5128
|
pano.removeEventListener("pointerdown", onDown);
|
|
@@ -4653,7 +5130,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4653
5130
|
pano.removeEventListener("pointerup", onUp);
|
|
4654
5131
|
pano.removeEventListener("pointercancel", onUp);
|
|
4655
5132
|
pano.removeEventListener("wheel", onWheel);
|
|
4656
|
-
|
|
5133
|
+
el2.removeEventListener("keydown", onKey);
|
|
4657
5134
|
};
|
|
4658
5135
|
}
|
|
4659
5136
|
closeSeatView() {
|
|
@@ -4943,7 +5420,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4943
5420
|
this.els.tray.querySelectorAll(".sl-chip .view[data-view-label]").forEach((btn) => {
|
|
4944
5421
|
btn.addEventListener("click", () => {
|
|
4945
5422
|
const seat = this.controller.seatByLabel(btn.dataset.viewLabel);
|
|
4946
|
-
if (seat) this.openSeatView(seat);
|
|
5423
|
+
if (seat) void this.openSeatView(seat);
|
|
4947
5424
|
});
|
|
4948
5425
|
});
|
|
4949
5426
|
this.els.tray.querySelectorAll(".sl-chip[data-locate]").forEach((chip) => {
|
|
@@ -4964,8 +5441,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
4964
5441
|
});
|
|
4965
5442
|
});
|
|
4966
5443
|
if (this.salesClosed) {
|
|
4967
|
-
this.els.tray.querySelectorAll(".sl-ba-go,[data-ba],[data-ba-cat],[data-ba-zone],[data-ba-replace],.sl-ga button").forEach((
|
|
4968
|
-
|
|
5444
|
+
this.els.tray.querySelectorAll(".sl-ba-go,[data-ba],[data-ba-cat],[data-ba-zone],[data-ba-replace],.sl-ga button").forEach((el2) => {
|
|
5445
|
+
el2.disabled = true;
|
|
4969
5446
|
});
|
|
4970
5447
|
}
|
|
4971
5448
|
const gaTotal = this.pendingGATotal(gaAreas);
|
|
@@ -5086,7 +5563,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5086
5563
|
const seats = this.hold.seats ?? committed;
|
|
5087
5564
|
this.handedOff = true;
|
|
5088
5565
|
this.setCtaPhase("checkout");
|
|
5089
|
-
this.
|
|
5566
|
+
this.checkoutHandoff(this.hold, seats);
|
|
5090
5567
|
return;
|
|
5091
5568
|
}
|
|
5092
5569
|
this.holdingLabels = new Set(committed.map((seat) => seat.label));
|
|
@@ -5115,7 +5592,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5115
5592
|
this.flashHeldSeats(hold);
|
|
5116
5593
|
this.setCtaPhase("checkout");
|
|
5117
5594
|
this.emitHoldChange();
|
|
5118
|
-
this.
|
|
5595
|
+
this.checkoutHandoff(hold, hold.seats ?? chosenSeats);
|
|
5119
5596
|
} catch (err) {
|
|
5120
5597
|
this.opts.onError?.(err);
|
|
5121
5598
|
const problem = err;
|
|
@@ -5220,6 +5697,109 @@ var SeatPicker = class _SeatPicker {
|
|
|
5220
5697
|
this.bookedEl?.classList.add("on");
|
|
5221
5698
|
this.opts.onBooked?.(handoff);
|
|
5222
5699
|
}
|
|
5700
|
+
/**
|
|
5701
|
+
* The seats are held. Send the buyer wherever this picker's `checkout` option
|
|
5702
|
+
* says they go.
|
|
5703
|
+
*
|
|
5704
|
+
* The default branch is the literal call that stood here before hosted
|
|
5705
|
+
* checkout existed, unchanged, so nothing about an existing integration moves.
|
|
5706
|
+
*/
|
|
5707
|
+
checkoutHandoff(hold, seats) {
|
|
5708
|
+
if (this.checkoutMode === "hosted") {
|
|
5709
|
+
void this.startHostedCheckout(hold, seats);
|
|
5710
|
+
return;
|
|
5711
|
+
}
|
|
5712
|
+
this.opts.onCheckout?.(hold, seats, this.buildHandoff(hold));
|
|
5713
|
+
}
|
|
5714
|
+
/**
|
|
5715
|
+
* Take the money ourselves, through the organizer's own gateway.
|
|
5716
|
+
*
|
|
5717
|
+
* Order of operations matters: ASK FIRST, load second. `payment-options` is
|
|
5718
|
+
* already in flight from render, and its answer decides whether any payment
|
|
5719
|
+
* code is fetched at all — an event that cannot charge never downloads the
|
|
5720
|
+
* card that would have charged it.
|
|
5721
|
+
*
|
|
5722
|
+
* An empty list is not a failure and never dead-ends the buyer. It routes them
|
|
5723
|
+
* to whatever the host has: `onCheckoutUnavailable` (with the server's reason,
|
|
5724
|
+
* so the host can say the right one of three very different sentences), then
|
|
5725
|
+
* `onCheckout` with the ordinary handoff. A host that supplied neither gets
|
|
5726
|
+
* the widget's own honest card instead of a press that did nothing.
|
|
5727
|
+
*/
|
|
5728
|
+
async startHostedCheckout(hold, seats) {
|
|
5729
|
+
const handoff = this.buildHandoff(hold);
|
|
5730
|
+
let options = null;
|
|
5731
|
+
try {
|
|
5732
|
+
options = await (this.paymentOptions ??= this.pubApi.paymentOptions(this.opts.event));
|
|
5733
|
+
} catch (err) {
|
|
5734
|
+
this.opts.onError?.(err);
|
|
5735
|
+
}
|
|
5736
|
+
if (this.destroyed) return;
|
|
5737
|
+
const provider = options?.providers?.[0];
|
|
5738
|
+
if (!provider) {
|
|
5739
|
+
const reason = paymentsOffReason(options?.reason);
|
|
5740
|
+
const handled = !!this.opts.onCheckoutUnavailable || !!this.opts.onCheckout;
|
|
5741
|
+
this.opts.onCheckoutUnavailable?.({ reason, handoff });
|
|
5742
|
+
this.opts.onCheckout?.(hold, seats, handoff);
|
|
5743
|
+
if (!handled) {
|
|
5744
|
+
void this.openCheckoutPanel({
|
|
5745
|
+
kind: "unavailable",
|
|
5746
|
+
reason,
|
|
5747
|
+
seatCount: handoff.lineItems.reduce((sum, item) => sum + item.quantity, 0)
|
|
5748
|
+
});
|
|
5749
|
+
}
|
|
5750
|
+
return;
|
|
5751
|
+
}
|
|
5752
|
+
await this.openCheckoutPanel({
|
|
5753
|
+
kind: "pay",
|
|
5754
|
+
provider,
|
|
5755
|
+
order: {
|
|
5756
|
+
holdId: handoff.holdId,
|
|
5757
|
+
expiresAt: handoff.expiresAt,
|
|
5758
|
+
currency: handoff.currency,
|
|
5759
|
+
total: handoff.total,
|
|
5760
|
+
labels: handoff.lineItems.map((item) => item.displayLabel ?? item.label)
|
|
5761
|
+
}
|
|
5762
|
+
});
|
|
5763
|
+
}
|
|
5764
|
+
/**
|
|
5765
|
+
* Fetch the checkout chunk and put its card over the map.
|
|
5766
|
+
*
|
|
5767
|
+
* Every failure here lands the buyer back on a map with their seats still
|
|
5768
|
+
* held, which is a place they can act from — a blocked chunk request must not
|
|
5769
|
+
* leave them staring at a CTA that no longer does anything.
|
|
5770
|
+
*/
|
|
5771
|
+
async openCheckoutPanel(state) {
|
|
5772
|
+
let mountCheckout2;
|
|
5773
|
+
try {
|
|
5774
|
+
({ mountCheckout: mountCheckout2 } = await loadHostedCheckout());
|
|
5775
|
+
} catch (err) {
|
|
5776
|
+
this.opts.onError?.(err);
|
|
5777
|
+
this.toast("Checkout could not be opened. Your seats are still held \u2014 please try again.", "error");
|
|
5778
|
+
this.setCtaPhase("idle");
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
if (this.destroyed || !this.root) return;
|
|
5782
|
+
this.closeCheckoutPanel();
|
|
5783
|
+
this.checkoutPanel = mountCheckout2({
|
|
5784
|
+
root: this.root,
|
|
5785
|
+
state,
|
|
5786
|
+
startSession: (input) => this.pubApi.startCheckout(this.opts.event, input),
|
|
5787
|
+
orderStatus: (orderId) => this.pubApi.orderStatus(orderId),
|
|
5788
|
+
onCancel: () => {
|
|
5789
|
+
this.checkoutPanel = null;
|
|
5790
|
+
if (!this.hold) this.setCtaPhase("idle");
|
|
5791
|
+
},
|
|
5792
|
+
onConfirmed: (order) => {
|
|
5793
|
+
this.opts.onOrderConfirmed?.(order);
|
|
5794
|
+
void this.controller.refresh();
|
|
5795
|
+
},
|
|
5796
|
+
onError: (err) => this.opts.onError?.(err)
|
|
5797
|
+
});
|
|
5798
|
+
}
|
|
5799
|
+
closeCheckoutPanel() {
|
|
5800
|
+
this.checkoutPanel?.destroy();
|
|
5801
|
+
this.checkoutPanel = null;
|
|
5802
|
+
}
|
|
5223
5803
|
/** Assemble the stable {@link CheckoutHandoff} from a hold's server line items. */
|
|
5224
5804
|
buildHandoff(hold) {
|
|
5225
5805
|
const items = hold.items ?? [];
|
|
@@ -5251,28 +5831,28 @@ var SeatPicker = class _SeatPicker {
|
|
|
5251
5831
|
);
|
|
5252
5832
|
}
|
|
5253
5833
|
toast(msg, tone = "neutral", action) {
|
|
5254
|
-
const
|
|
5255
|
-
if (!
|
|
5256
|
-
|
|
5834
|
+
const el2 = this.els.toast;
|
|
5835
|
+
if (!el2) return;
|
|
5836
|
+
el2.replaceChildren();
|
|
5257
5837
|
const copy = document.createElement("span");
|
|
5258
5838
|
copy.textContent = msg;
|
|
5259
|
-
|
|
5260
|
-
|
|
5839
|
+
el2.appendChild(copy);
|
|
5840
|
+
el2.classList.toggle("has-action", !!action);
|
|
5261
5841
|
if (action) {
|
|
5262
5842
|
const button = document.createElement("button");
|
|
5263
5843
|
button.type = "button";
|
|
5264
5844
|
button.className = "sl-toast-action";
|
|
5265
5845
|
button.textContent = action.label;
|
|
5266
5846
|
button.addEventListener("click", action.onClick, { once: true });
|
|
5267
|
-
|
|
5847
|
+
el2.appendChild(button);
|
|
5268
5848
|
}
|
|
5269
|
-
|
|
5270
|
-
|
|
5849
|
+
el2.dataset.tone = tone;
|
|
5850
|
+
el2.classList.add("on");
|
|
5271
5851
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
5272
5852
|
this.toastTimer = setTimeout(() => {
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5853
|
+
el2.classList.remove("on");
|
|
5854
|
+
el2.classList.remove("has-action");
|
|
5855
|
+
el2.dataset.tone = "neutral";
|
|
5276
5856
|
}, 4200);
|
|
5277
5857
|
}
|
|
5278
5858
|
placeTooltip() {
|
|
@@ -5454,7 +6034,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5454
6034
|
}
|
|
5455
6035
|
/** Build the view-from-seat panorama the cinematic dissolves into — reuses the
|
|
5456
6036
|
* exact input path as the 2D `openSeatView` (organizer photo, else generated). */
|
|
5457
|
-
seatViewFor3d(seatId) {
|
|
6037
|
+
async seatViewFor3d(seatId) {
|
|
5458
6038
|
const seat = this.allSeats().find((s) => s.id === seatId);
|
|
5459
6039
|
if (!seat) return null;
|
|
5460
6040
|
if (seat.viewUrl) return { url: seat.viewUrl };
|
|
@@ -5463,7 +6043,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
5463
6043
|
const activeId = this.controller.getActiveFloorId();
|
|
5464
6044
|
const focal = seat.focalPoint ?? doc.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc.focalPoint ?? { x: 0, y: 0 };
|
|
5465
6045
|
try {
|
|
5466
|
-
|
|
6046
|
+
const { generateSeatPanorama } = await loadPanorama();
|
|
6047
|
+
return { url: generateSeatPanorama(seat, focal, this.allSeats()).url };
|
|
5467
6048
|
} catch {
|
|
5468
6049
|
return null;
|
|
5469
6050
|
}
|
|
@@ -5602,9 +6183,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
5602
6183
|
// in orbit instead of dissolving into an empty overlay.
|
|
5603
6184
|
getSeatView: (id) => new Promise((resolve, reject) => {
|
|
5604
6185
|
const run = () => {
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
6186
|
+
void this.seatViewFor3d(id).then((view) => {
|
|
6187
|
+
if (view) resolve(view);
|
|
6188
|
+
else reject(new Error("seat_view_unavailable"));
|
|
6189
|
+
});
|
|
5608
6190
|
};
|
|
5609
6191
|
const ric = globalThis.requestIdleCallback;
|
|
5610
6192
|
if (typeof ric === "function") ric(run, { timeout: 1500 });
|
|
@@ -5882,6 +6464,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5882
6464
|
this.closeConfirm();
|
|
5883
6465
|
this.dismissTableDialog(false);
|
|
5884
6466
|
this.closeSeatView();
|
|
6467
|
+
this.closeCheckoutPanel();
|
|
5885
6468
|
this.exit3d();
|
|
5886
6469
|
this.stopHoldTimer();
|
|
5887
6470
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
@@ -6603,7 +7186,9 @@ var PREVIEW_ELIGIBLE_STROKE = "#b9c0ff";
|
|
|
6603
7186
|
var PREVIEW_UNAVAILABLE_FILL = "#303846";
|
|
6604
7187
|
var PREVIEW_UNAVAILABLE_STROKE = "#4b5669";
|
|
6605
7188
|
var ALLOCATION_STROKE = "#101723";
|
|
6606
|
-
var CHANNELS_CSS =
|
|
7189
|
+
var CHANNELS_CSS = (
|
|
7190
|
+
/* @sl-css */
|
|
7191
|
+
`
|
|
6607
7192
|
.slm{--slm-mo-instant:80ms;--slm-mo-quick:140ms;--slm-mo-base:200ms;--slm-mo-slow:320ms;--slm-mo-ambient:2000ms;
|
|
6608
7193
|
--slm-mo-out:cubic-bezier(.2,.8,.2,1);--slm-mo-in-out:cubic-bezier(.4,0,.2,1);--slm-mo-exit:cubic-bezier(.4,0,1,1);
|
|
6609
7194
|
--slm-mo-spring:cubic-bezier(.34,1.3,.64,1)}
|
|
@@ -6792,7 +7377,8 @@ var CHANNELS_CSS = `
|
|
|
6792
7377
|
.slm-ch-staged.shake{outline:2px solid #e5484d;outline-offset:2px}
|
|
6793
7378
|
.slm-ch-staged.done{outline:2px solid #5bd39b;outline-offset:2px}
|
|
6794
7379
|
}
|
|
6795
|
-
|
|
7380
|
+
`
|
|
7381
|
+
);
|
|
6796
7382
|
function bucketRowsHtml(rows) {
|
|
6797
7383
|
return rows.map((row, index) => `
|
|
6798
7384
|
<div class="slm-ch-bucket" style="animation-delay:${Math.min(index, 4) * 30}ms">
|
|
@@ -7258,7 +7844,7 @@ var ChannelsMode = class {
|
|
|
7258
7844
|
paintSectionTargets(sections) {
|
|
7259
7845
|
const layer = this.layer;
|
|
7260
7846
|
if (!layer) return;
|
|
7261
|
-
layer.querySelectorAll(".slm-ch-section-target").forEach((
|
|
7847
|
+
layer.querySelectorAll(".slm-ch-section-target").forEach((el2) => el2.remove());
|
|
7262
7848
|
if (!sections) return;
|
|
7263
7849
|
for (const [id, section] of sections) {
|
|
7264
7850
|
const width = section.maxX - section.minX;
|
|
@@ -7304,7 +7890,7 @@ var ChannelsMode = class {
|
|
|
7304
7890
|
paintFlags(clusters) {
|
|
7305
7891
|
const layer = this.layer;
|
|
7306
7892
|
if (!layer) return;
|
|
7307
|
-
layer.querySelectorAll(".slm-ch-flag").forEach((
|
|
7893
|
+
layer.querySelectorAll(".slm-ch-flag").forEach((el2) => el2.remove());
|
|
7308
7894
|
if (this.view === "preview") return;
|
|
7309
7895
|
const ranked = [...clusters.entries()].sort((a, b) => b[1].n - a[1].n).slice(0, MAX_FLAGS);
|
|
7310
7896
|
for (const [channelId, cluster] of ranked) {
|
|
@@ -8667,9 +9253,9 @@ function toLocalInput(ms) {
|
|
|
8667
9253
|
}
|
|
8668
9254
|
function resolveContainer4(container) {
|
|
8669
9255
|
if (typeof container === "string") {
|
|
8670
|
-
const
|
|
8671
|
-
if (!
|
|
8672
|
-
return
|
|
9256
|
+
const el2 = document.querySelector(container);
|
|
9257
|
+
if (!el2) throw new Error(`seatmanager: container "${container}" not found`);
|
|
9258
|
+
return el2;
|
|
8673
9259
|
}
|
|
8674
9260
|
if (!(container instanceof HTMLElement)) {
|
|
8675
9261
|
throw new Error("seatmanager: container must be a CSS selector or an HTMLElement");
|
|
@@ -8680,7 +9266,7 @@ function toRenderStatus(s) {
|
|
|
8680
9266
|
return s === "blocked" ? "not_for_sale" : s;
|
|
8681
9267
|
}
|
|
8682
9268
|
var DEFAULT_API_BASE3 = "https://api.seatlayer.io";
|
|
8683
|
-
var
|
|
9269
|
+
var STYLE_ID3 = "seatlayer-manager-style";
|
|
8684
9270
|
var FEED_CAP = 80;
|
|
8685
9271
|
var MAX_LIVE_SEAT_PULSES = 16;
|
|
8686
9272
|
var MAX_LIVE_SECTION_PULSES = 4;
|
|
@@ -8690,7 +9276,9 @@ var LEGEND = [
|
|
|
8690
9276
|
{ key: "booked", label: "Booked", color: "#22a06b" },
|
|
8691
9277
|
{ key: "blocked", label: "Blocked", color: "#8b94ac" }
|
|
8692
9278
|
];
|
|
8693
|
-
var MANAGER_CSS =
|
|
9279
|
+
var MANAGER_CSS = (
|
|
9280
|
+
/* @sl-css */
|
|
9281
|
+
`
|
|
8694
9282
|
.slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:480px;overflow:hidden;
|
|
8695
9283
|
background:var(--slm-bg);color:var(--slm-text);font-family:var(--slm-font);border-radius:var(--slm-radius);
|
|
8696
9284
|
/* Motion tokens (motion-system \xA72), declared by the cockpit ROOT rather than
|
|
@@ -8932,13 +9520,14 @@ var MANAGER_CSS = `
|
|
|
8932
9520
|
transition:none!important;
|
|
8933
9521
|
scroll-behavior:auto!important}
|
|
8934
9522
|
}
|
|
8935
|
-
${CHANNELS_CSS}
|
|
9523
|
+
${CHANNELS_CSS}`
|
|
9524
|
+
);
|
|
8936
9525
|
function injectStyle() {
|
|
8937
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
8938
|
-
const
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
document.head.appendChild(
|
|
9526
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
9527
|
+
const el2 = document.createElement("style");
|
|
9528
|
+
el2.id = STYLE_ID3;
|
|
9529
|
+
el2.textContent = MANAGER_CSS;
|
|
9530
|
+
document.head.appendChild(el2);
|
|
8942
9531
|
}
|
|
8943
9532
|
function themeVars(theme) {
|
|
8944
9533
|
const t3 = theme ?? {};
|
|
@@ -10089,16 +10678,16 @@ var SeatManager = class {
|
|
|
10089
10678
|
paintModeTabs() {
|
|
10090
10679
|
const available = [];
|
|
10091
10680
|
this.els.modes?.querySelectorAll("[data-mode]").forEach((b) => {
|
|
10092
|
-
const
|
|
10093
|
-
const mode =
|
|
10681
|
+
const el2 = b;
|
|
10682
|
+
const mode = el2.dataset.mode;
|
|
10094
10683
|
const permitted = mode !== "channels" || !!this.channels;
|
|
10095
|
-
|
|
10684
|
+
el2.hidden = !permitted;
|
|
10096
10685
|
if (!permitted) return;
|
|
10097
|
-
available.push({ mode, label:
|
|
10686
|
+
available.push({ mode, label: el2.textContent ?? mode });
|
|
10098
10687
|
const active = mode === this.mode;
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10688
|
+
el2.classList.toggle("on", active);
|
|
10689
|
+
el2.setAttribute("aria-selected", String(active));
|
|
10690
|
+
el2.tabIndex = active ? 0 : -1;
|
|
10102
10691
|
});
|
|
10103
10692
|
const tools = this.els.tools;
|
|
10104
10693
|
if (tools) {
|
|
@@ -10884,13 +11473,13 @@ var SeatManager = class {
|
|
|
10884
11473
|
this.toast(msg, "err");
|
|
10885
11474
|
}
|
|
10886
11475
|
toast(msg, kind) {
|
|
10887
|
-
const
|
|
10888
|
-
if (!
|
|
10889
|
-
|
|
10890
|
-
|
|
11476
|
+
const el2 = this.els.toast;
|
|
11477
|
+
if (!el2) return;
|
|
11478
|
+
el2.textContent = msg;
|
|
11479
|
+
el2.className = `slm-toast on ${kind}`;
|
|
10891
11480
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
10892
11481
|
this.toastTimer = setTimeout(() => {
|
|
10893
|
-
|
|
11482
|
+
el2.className = "slm-toast";
|
|
10894
11483
|
}, 3200);
|
|
10895
11484
|
}
|
|
10896
11485
|
fail(err) {
|