@coras-io/embed 0.1.1 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @coras-io/embed
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c7714d5: Hover and selected states on dropdown rows, calendar days, chips and the ticket-type selector now take a faint tint from the brand primary colour instead of a fixed neutral grey, so they follow a rebrand and match the search button. The ticket-type selector's radius now follows the brand button radius (and the general brand radius by default) rather than staying fixed.
8
+ - e157901: Refine the seated theater booking flow: shoppers can change their seat count directly on the map, hover and legend prices show formatted currency, the map offers a full-screen view, "Add to basket" shows progress while it works, and the map closes and the ticket selector resets after a booking. The static seat map illustration is hidden when the interactive picker is available.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [c7714d5]
13
+ - @coras-io/brand-tokens@0.2.0
14
+
15
+ ## 0.1.2
16
+
17
+ ### Patch Changes
18
+
19
+ - 90975a0: Render prices with the decimal places each currency actually uses instead of
20
+ always showing two. Zero-decimal currencies (JPY, VND, ISK, HUF and others) now
21
+ display as whole amounts (`¥1,000` rather than `¥1,000.00`), and IDR is shown
22
+ without its defunct sen subunit. Two-decimal currencies are unaffected.
23
+
3
24
  ## 0.1.1
4
25
 
5
26
  ### Patch Changes
@@ -14,7 +14,8 @@ export declare class CorasSeatedSelector extends LitElement {
14
14
  workspaceKey: string;
15
15
  dateLabel: string;
16
16
  region: string;
17
- ticketCount: number;
17
+ maxSeats: number;
18
+ reserving: boolean;
18
19
  private selectedSeats;
19
20
  private selectionValid;
20
21
  private violations;
@@ -34,9 +34,14 @@ let CorasSeatedSelector = class CorasSeatedSelector extends LitElement {
34
34
  // Formatted performance date shown in the heading, e.g. "Sat 16 Mar 2023".
35
35
  this.dateLabel = "";
36
36
  this.region = "eu";
37
- // Total seats the user must pick (sum of chosen ticket-type counts). Caps the
38
- // chart selection and gates the confirm button.
39
- this.ticketCount = 0;
37
+ // Upper bound on how many seats can be picked (the venue's per-order max). 0
38
+ // means no cap. It only limits the map; it does not require an exact count,
39
+ // so the shopper is free to pick fewer or more than the upfront selection.
40
+ this.maxSeats = 0;
41
+ // Set by the host while the reservation request is in flight, so the confirm
42
+ // button shows a spinner instead of looking inert for the few seconds the
43
+ // basket call takes.
44
+ this.reserving = false;
40
45
  this.selectedSeats = [];
41
46
  this.selectionValid = true;
42
47
  this.violations = [];
@@ -143,7 +148,7 @@ let CorasSeatedSelector = class CorasSeatedSelector extends LitElement {
143
148
  // Drop seats chosen for the previous one before the next chart renders so they
144
149
  // never carry into the new chart or reservation.
145
150
  willUpdate(changed) {
146
- if (changed.has("instanceId") || changed.has("ticketCount")) {
151
+ if (changed.has("instanceId") || changed.has("maxSeats")) {
147
152
  this.selectedSeats = [];
148
153
  this.selectionValid = true;
149
154
  this.violations = [];
@@ -171,13 +176,11 @@ let CorasSeatedSelector = class CorasSeatedSelector extends LitElement {
171
176
  reservationCreated() {
172
177
  this.reserved = true;
173
178
  }
179
+ // Any valid, non-empty selection can be booked. The seat count is no longer
180
+ // tied to the upfront ticket count - the map's own cap (maxSeats) and orphan
181
+ // validator are the only constraints.
174
182
  get isComplete() {
175
- if (this.selectedSeats.length === 0 || !this.selectionValid) {
176
- return false;
177
- }
178
- return this.ticketCount
179
- ? this.selectedSeats.length === this.ticketCount
180
- : true;
183
+ return this.selectedSeats.length > 0 && this.selectionValid;
181
184
  }
182
185
  formatPrice(value) {
183
186
  return convertToLocaleCurrency({
@@ -207,27 +210,32 @@ let CorasSeatedSelector = class CorasSeatedSelector extends LitElement {
207
210
  const { groups, total } = buildSelectionSummary(this.selectedSeats, data.availableSeats, data.flatPriceBands, data.categories);
208
211
  return html `
209
212
  <div class="drawer" part="drawer">
210
- <div class="drawer-summary">
211
- <p class="drawer-total">${msg("Total")} ${this.formatPrice(total)}</p>
212
- ${map(groups, (group) => html `
213
- <p class="drawer-group">
214
- <span
215
- class="drawer-dot"
216
- style="background:${group.color}"
217
- ></span>
218
- ${msg(str `${group.count} ${group.label} Tickets : ${group.seatLabels.join(", ")} (${this.formatPrice(group.unitPrice)} each)`)}
219
- </p>
220
- `)}
221
- </div>
213
+ <div class="drawer-inner">
214
+ <div class="drawer-summary">
215
+ <p class="drawer-total">
216
+ ${msg("Total")} ${this.formatPrice(total)}
217
+ </p>
218
+ ${map(groups, (group) => html `
219
+ <p class="drawer-group">
220
+ <span
221
+ class="drawer-dot"
222
+ style="background:${group.color}"
223
+ ></span>
224
+ ${msg(str `${group.count} ${group.label} Tickets : ${group.seatLabels.join(", ")} (${this.formatPrice(group.unitPrice)} each)`)}
225
+ </p>
226
+ `)}
227
+ </div>
222
228
 
223
- <coras-button
224
- variant="primary"
225
- height="sm"
226
- ?disabled=${!this.isComplete}
227
- @click=${this.handleConfirm}
228
- >
229
- ${msg("Add to basket")}
230
- </coras-button>
229
+ <coras-button
230
+ variant="primary"
231
+ height="sm"
232
+ ?loading=${this.reserving}
233
+ ?disabled=${!this.isComplete || this.reserving}
234
+ @click=${this.handleConfirm}
235
+ >
236
+ ${msg("Add to basket")}
237
+ </coras-button>
238
+ </div>
231
239
  </div>
232
240
  `;
233
241
  }
@@ -254,7 +262,7 @@ let CorasSeatedSelector = class CorasSeatedSelector extends LitElement {
254
262
  language=${this.locale.split("-")[0] || "en"}
255
263
  currency=${this.currency}
256
264
  currency-locale=${this.locale}
257
- max-selected-objects=${this.ticketCount}
265
+ max-selected-objects=${this.maxSeats}
258
266
  .chartData=${chartData}
259
267
  .selectedObjects=${this.selectedLabels}
260
268
  @seats-io-object-selected=${this.handleObjectSelected}
@@ -301,8 +309,11 @@ __decorate([
301
309
  property({ type: String })
302
310
  ], CorasSeatedSelector.prototype, "region", void 0);
303
311
  __decorate([
304
- property({ type: Number, attribute: "ticket-count" })
305
- ], CorasSeatedSelector.prototype, "ticketCount", void 0);
312
+ property({ type: Number, attribute: "max-seats" })
313
+ ], CorasSeatedSelector.prototype, "maxSeats", void 0);
314
+ __decorate([
315
+ property({ type: Boolean, reflect: true })
316
+ ], CorasSeatedSelector.prototype, "reserving", void 0);
306
317
  __decorate([
307
318
  state()
308
319
  ], CorasSeatedSelector.prototype, "selectedSeats", void 0);
@@ -33,16 +33,25 @@ export default css `
33
33
  bottom: 0;
34
34
  z-index: 20;
35
35
  display: flex;
36
- align-items: center;
37
- justify-content: space-between;
38
- gap: 24px;
36
+ justify-content: center;
39
37
  box-sizing: border-box;
40
- padding: 24px clamp(24px, 6vw, 96px);
38
+ padding: 24px clamp(16px, 4vw, 48px);
41
39
  background: var(--coras-color-background, #fcfcff);
42
40
  border-top: 1px solid var(--coras-color-neutral-200, #e6e6e6);
43
41
  box-shadow: 1px -4px 8px 4px rgba(0, 0, 0, 0.03);
44
42
  }
45
43
 
44
+ /* Cap the content width and centre it so the summary and the button stay
45
+ close together on wide screens instead of hugging opposite edges. */
46
+ .drawer-inner {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ gap: 24px;
51
+ width: 100%;
52
+ max-width: 720px;
53
+ }
54
+
46
55
  .drawer-summary {
47
56
  display: flex;
48
57
  flex-direction: column;
@@ -75,10 +84,13 @@ export default css `
75
84
 
76
85
  @media (max-width: 600px) {
77
86
  .drawer {
87
+ padding: 16px;
88
+ }
89
+
90
+ .drawer-inner {
78
91
  flex-direction: column;
79
92
  align-items: stretch;
80
93
  gap: 12px;
81
- padding: 16px;
82
94
  }
83
95
  }
84
96
  `;
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { html, LitElement, } from "lit";
8
8
  import { property, query } from "lit/decorators.js";
9
9
  import { fireInternalEvent } from "../../core/events.js";
10
+ import { convertToLocaleCurrency } from "../../core/currency/index.js";
10
11
  import { SEATS_IO_DEFAULTS } from "./seats-io.constants.js";
11
12
  import styles from "./seats-io.styles.js";
12
13
  export class CorasSeatsIO extends LitElement {
@@ -49,6 +50,15 @@ export class CorasSeatsIO extends LitElement {
49
50
  workspaceKey: this.workspaceKey,
50
51
  event: this.event,
51
52
  language: this.language,
53
+ // Prices reach seats.io as raw minor units (e.g. 3500). Format them to the
54
+ // shopper's currency and locale so tooltips and the legend read "£35.00",
55
+ // not "3500". A presentation-layer callback, so unlike the renderer-engine
56
+ // callbacks it may close over the element's currency/locale.
57
+ priceFormatter: (price) => convertToLocaleCurrency({
58
+ value: price,
59
+ locale: this.currencyLocale,
60
+ currency: this.currency,
61
+ }),
52
62
  };
53
63
  if (!this.chartData) {
54
64
  return base;
@@ -1,9 +1,13 @@
1
1
  export declare const SEATS_IO_DEFAULTS: {
2
2
  showRowLabels: boolean;
3
3
  showRowLines: boolean;
4
+ showFullScreenButton: boolean;
4
5
  legend: {
5
6
  hideNonSelectableCategories: boolean;
6
7
  };
8
+ objectTooltip: {
9
+ showUnavailableNotice: boolean;
10
+ };
7
11
  loading: string;
8
12
  showActiveSectionTooltipOnMobile: boolean;
9
13
  };
@@ -1,9 +1,16 @@
1
1
  export const SEATS_IO_DEFAULTS = {
2
2
  showRowLabels: true,
3
3
  showRowLines: true,
4
+ // Let shoppers pop the map out to full screen; the enlarge affordance sits
5
+ // next to seats.io's own zoom controls.
6
+ showFullScreenButton: true,
4
7
  legend: {
5
8
  hideNonSelectableCategories: true,
6
9
  },
10
+ // Show an "unavailable" note when hovering a seat that can't be picked.
11
+ objectTooltip: {
12
+ showUnavailableNotice: true,
13
+ },
7
14
  loading: " ",
8
15
  showActiveSectionTooltipOnMobile: false,
9
16
  };
@@ -20,10 +20,11 @@ export default css `
20
20
  width: 100%;
21
21
  }
22
22
 
23
- /* The trigger reads as a prominent text field, so it keeps a fixed field
24
- radius (like the payment fields) rather than tracking the brand radius. */
23
+ /* The trigger is a button, so its radius follows the brand button radius
24
+ (which tracks the general brand radius until a separate button radius is
25
+ set) rather than a fixed field radius. */
25
26
  coras-button {
26
- --button-border-radius-small: var(--coras-radius-field);
27
+ --button-border-radius-small: var(--coras-radius-button);
27
28
  }
28
29
  }
29
30
 
@@ -5,8 +5,6 @@ type convertToLocaleCurrencyProps = {
5
5
  locale: (typeof allLocales)[number];
6
6
  currency: SupportedCurrencies;
7
7
  isSmallestCurrencyUnit?: boolean;
8
- maximumFractionDigits?: number;
9
- minimumFractionDigits?: number;
10
8
  };
11
- export declare const convertToLocaleCurrency: ({ value, locale, currency, maximumFractionDigits, minimumFractionDigits, isSmallestCurrencyUnit, }: convertToLocaleCurrencyProps) => string;
9
+ export declare const convertToLocaleCurrency: ({ value, locale, currency, isSmallestCurrencyUnit, }: convertToLocaleCurrencyProps) => string;
12
10
  export {};
@@ -1,6 +1,16 @@
1
- export const convertToLocaleCurrency = ({ value = 10000, locale = "en-US", currency = "USD", maximumFractionDigits = 2, minimumFractionDigits = 2, isSmallestCurrencyUnit = true, }) => new Intl.NumberFormat(locale, {
2
- style: "currency",
3
- currency: currency,
4
- maximumFractionDigits: maximumFractionDigits,
5
- minimumFractionDigits: minimumFractionDigits,
6
- }).format(isSmallestCurrencyUnit ? value / 100 : value);
1
+ // Currencies we render without decimals even though their CLDR default is 2.
2
+ // Intl already drops decimals for genuinely zero-decimal currencies (JPY, VND,
3
+ // ISK, HUF, ALL); IDR's default is 2 (the defunct sen subunit) but amounts are
4
+ // only ever whole rupiah, so we force it to 0 here.
5
+ const forcedZeroDecimalCurrencies = new Set(["IDR"]);
6
+ export const convertToLocaleCurrency = ({ value = 10000, locale = "en-US", currency = "USD", isSmallestCurrencyUnit = true, }) => {
7
+ const fractionDigits = forcedZeroDecimalCurrencies.has(currency)
8
+ ? 0
9
+ : undefined;
10
+ return new Intl.NumberFormat(locale, {
11
+ style: "currency",
12
+ currency: currency,
13
+ minimumFractionDigits: fractionDigits,
14
+ maximumFractionDigits: fractionDigits,
15
+ }).format(isSmallestCurrencyUnit ? value / 100 : value);
16
+ };