@seatlayer/js 0.17.0 → 0.18.1

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/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { PickerSeat, SeatHoverDetails, ChartDoc, ChartTheme, ExpandedSeat } from '@seatlayer/core';
1
+ import { PickerSeat, SeatHoverDetails, PickerTransport, ChartDoc, ChartTheme, ExpandedSeat } from '@seatlayer/core';
2
2
  export { ExpandedSeat, SeatHoverDetails } from '@seatlayer/core';
3
3
 
4
4
  /**
@@ -242,6 +242,25 @@ interface EmbeddedDesignerOptions {
242
242
  style?: Partial<CSSStyleDeclaration>;
243
243
  allow?: string;
244
244
  referrerPolicy?: ReferrerPolicy;
245
+ /**
246
+ * Show the built-in branded loading skeleton and error/expiry card inside the
247
+ * container while the Designer boots. Defaults to `true`. Set `false` when the
248
+ * host renders its own loading and error chrome.
249
+ */
250
+ showLoadingState?: boolean;
251
+ /**
252
+ * If the Designer never posts `ready` within this many milliseconds, the host
253
+ * transitions to the error card with a timeout message. Defaults to `20000`.
254
+ * Only used when `showLoadingState` is enabled.
255
+ */
256
+ loadingTimeoutMs?: number;
257
+ /**
258
+ * Called when the user presses "Try again" on the error card. Use it to mint a
259
+ * fresh Designer session and call `setDesignerUrl()` with the new URL, which
260
+ * recreates the iframe and returns to the loading state. When omitted, "Try
261
+ * again" reloads the current `designerUrl` in place.
262
+ */
263
+ onRequestRelaunch?: () => void;
245
264
  onReady?: (message: EmbeddedDesignerMessage) => void;
246
265
  onSaved?: (message: EmbeddedDesignerMessage) => void;
247
266
  onPublished?: (message: EmbeddedDesignerMessage) => void;
@@ -253,12 +272,30 @@ declare class EmbeddedDesigner {
253
272
  private options;
254
273
  private frame;
255
274
  private designerOrigin;
275
+ private overlay;
276
+ private timeoutTimer;
277
+ private phase;
278
+ private restoreContainerPosition;
256
279
  constructor(options: EmbeddedDesignerOptions);
257
280
  mount(): HTMLIFrameElement;
258
281
  /** Replace the iframe instead of assigning a new fragment to an existing one. */
259
282
  setDesignerUrl(designerUrl: string): HTMLIFrameElement;
260
283
  getIframe(): HTMLIFrameElement | null;
261
284
  destroy(): void;
285
+ private loadingStateEnabled;
286
+ private clearTimeoutTimer;
287
+ private ensureContainerPositioned;
288
+ private restoreContainerStyle;
289
+ private removeOverlay;
290
+ private showError;
291
+ private handleTryAgain;
292
+ /**
293
+ * Build (or rebuild) the overlay for the given phase. A single overlay element
294
+ * is reused so we never stack stale skeletons or cards.
295
+ */
296
+ private renderOverlay;
297
+ private buildSkeleton;
298
+ private buildErrorCard;
262
299
  private handleMessage;
263
300
  }
264
301
 
@@ -317,6 +354,16 @@ interface CheckoutHandoff {
317
354
  /** Convenience total in major units (Σ unitPrice × quantity). */
318
355
  total: number;
319
356
  }
357
+ /** Host-authoritative pricing — see {@link SeatPickerOptions.pricing}. */
358
+ interface SeatPickerPricing {
359
+ /** Unit prices by category key: a flat number, or `{ base, tiers: { tierId: price } }`. */
360
+ prices?: Record<string, number | {
361
+ base?: number;
362
+ tiers?: Record<string, number>;
363
+ }>;
364
+ /** Custom money renderer (e.g. `(n) => n + '€'`). Defaults to Intl currency formatting. */
365
+ formatter?: (amount: number, currency: string) => string;
366
+ }
320
367
  /** Host theme overrides — any subset; unset keys fall back to the org's chart theme, then defaults. */
321
368
  interface SeatPickerTheme {
322
369
  /** Brand accent (CTA, active chips, hold pill). */
@@ -349,6 +396,12 @@ interface SeatPickerOptions {
349
396
  event: string;
350
397
  /** API origin. Defaults to https://api.seatlayer.io. */
351
398
  apiBase?: string;
399
+ /**
400
+ * Custom data transport. Defaults to the CORS-trivial PubApi against
401
+ * `apiBase`. Inject to run the widget against another backend adapter (the
402
+ * SeatLayer dashboard's own transport) or a fully local mock (demos).
403
+ */
404
+ transport?: PickerTransport;
352
405
  /** Reserved for future authenticated rendering. */
353
406
  publicKey?: string;
354
407
  /** Max seats selectable at once (default 10). */
@@ -361,8 +414,23 @@ interface SeatPickerOptions {
361
414
  currency?: string;
362
415
  /** Colorblind-safe rendering (Okabe-Ito palette, hollow booked seats). */
363
416
  colorblindSafe?: boolean;
417
+ /**
418
+ * Hide the "Powered by SeatLayer" attribution badge in the side panel foot.
419
+ * The chart theme's own `hideBadge` flag (paid orgs) also hides it — the badge
420
+ * is shown only when BOTH this option and the theme flag are unset/false.
421
+ */
422
+ hideBadge?: boolean;
364
423
  /** Host theme overrides — see SeatPickerTheme. */
365
424
  theme?: SeatPickerTheme;
425
+ /**
426
+ * Host-authoritative pricing. When your shop charges different prices than
427
+ * the chart's stored category prices, pass them here so the buyer sees the
428
+ * price they will actually pay — on the map tooltip, confirm popover, price
429
+ * panel, tray, totals, and in the checkout handoff's line items. Keyed by
430
+ * category key; per-tier overrides nest under `tiers`. Unlisted categories
431
+ * fall back to the chart price.
432
+ */
433
+ pricing?: SeatPickerPricing;
366
434
  /** Hold TTL in ms passed to hold(); server clamps to its own limits. */
367
435
  holdTtlMs?: number;
368
436
  /**
@@ -454,23 +522,27 @@ declare class SeatPicker {
454
522
  private confirmEl;
455
523
  private confirmSeat;
456
524
  private srEl;
457
- private a11yFilter;
458
525
  private baQty;
459
526
  private baCat;
460
527
  private bestAvailableConfirm;
461
528
  private releasingHold;
529
+ /** Event sales window is closed (read-only load state / live close). */
530
+ private salesClosed;
531
+ /** Every seated category's live availability is 0 (sold-out overlay is up). */
532
+ private soldOut;
533
+ private soldoutEl;
534
+ /** Resolved colorblind-safe state — stored preference wins over the option. */
535
+ private cbSafe;
462
536
  private rungsEl;
463
537
  private floorsEl;
464
538
  private secCardEl;
465
539
  private viewEl;
466
540
  private viewCleanup;
467
541
  private allSeatsCache;
468
- private miniEl;
469
542
  private miniCanvas;
470
543
  private miniBase;
471
544
  private miniTf;
472
545
  private priceBandKeys;
473
- private priceFilterEl;
474
546
  /** Last surfaced section summary (re-rendered when the price band changes). */
475
547
  private lastSection;
476
548
  /** Section card collapsed to its slim pill (seat-picking has begun). */
@@ -489,6 +561,18 @@ declare class SeatPicker {
489
561
  private holdingLabels;
490
562
  private ctaPhase;
491
563
  private a11yChipsEl;
564
+ private fsFallback;
565
+ private fsChangeHandler;
566
+ private fsEscHandler;
567
+ /**
568
+ * Eager sightline preview for the confirm card: a cheap generated forward
569
+ * view (or the organizer's real photo) plus a "Nm to stage · clear
570
+ * sightline" line — the premium at-a-glance moment; click opens the 360.
571
+ */
572
+ private confirmThumbHtml;
573
+ /** Full screen via the native API, falling back to a fixed-position overlay (iOS Safari). */
574
+ private toggleFullscreen;
575
+ private setFsFallback;
492
576
  private cbEl;
493
577
  private modalScrim;
494
578
  private prevFocus;
@@ -516,6 +600,41 @@ declare class SeatPicker {
516
600
  private buildExtendPrompt;
517
601
  /** Success overlay + onBooked fire when the held seats settle to booked. */
518
602
  private buildBookedOverlay;
603
+ /**
604
+ * Localized string with a literal fallback. `t()` returns the key itself for
605
+ * unknown keys, so this collapses that to `fallback` — while still honoring a
606
+ * host `messages` override (which makes `t()` return the override, not the key).
607
+ */
608
+ private tf;
609
+ /** Sold-out overlay — centered over the map, disabled waitlist stub (Gap 2). */
610
+ private buildSoldoutOverlay;
611
+ /**
612
+ * Recompute the sold-out state on every price/availability sync. Sold-out ⇔
613
+ * every SEATED category's live free count is 0. Suppressed when the chart has
614
+ * GA areas (GA capacity isn't per-seat, so seated counts would read 0 and
615
+ * falsely block standing room) — mirrors the public page. Clears live when WS
616
+ * frees a seat up.
617
+ */
618
+ private syncSoldout;
619
+ /**
620
+ * Pure sold-out predicate: every SEATED category's free count is 0, there is at
621
+ * least one seated category, and there are no GA areas (GA capacity isn't
622
+ * per-seat, so seated counts read 0 and would falsely block standing room).
623
+ * `left` is seeded implicitly — a missing key means a fully-booked tier (0 free).
624
+ */
625
+ private isSoldOut;
626
+ /**
627
+ * Sales-closed read-only state (Gap 3): persistent header pill, disabled CTA
628
+ * with a closed label, and frozen best-available / GA controls. `setSalesClosed`
629
+ * is the reactive entry (live 409 event_closed); `applySalesClosed` is the
630
+ * idempotent DOM apply used at load and on transition.
631
+ */
632
+ private setSalesClosed;
633
+ private applySalesClosed;
634
+ /** The badge is hidden when the host opts out OR the org's theme sets hideBadge. */
635
+ private badgeHidden;
636
+ /** Attribution badge in the side-panel foot (Gap 7). Hidden per host/theme. */
637
+ private buildBadge;
519
638
  /**
520
639
  * Create the positioned flex containers that own every persistent map overlay.
521
640
  * Appended once after controller.render(); each chrome piece is then appended
@@ -571,7 +690,7 @@ declare class SeatPicker {
571
690
  private drawMinimapRect;
572
691
  /** Minimap click → focus the section under the point (or overview on a miss). */
573
692
  private minimapJump;
574
- /** Effective price of a category (first tier when tiered, else base price). */
693
+ /** Effective display price of a category: host pricing override → first tier base. */
575
694
  private catPrice;
576
695
  /** Derive price bands: one chip per distinct price (≤5), else quantile ranges. */
577
696
  private priceBands;
@@ -622,7 +741,22 @@ declare class SeatPicker {
622
741
  private openSeatView;
623
742
  private closeSeatView;
624
743
  private money;
744
+ /**
745
+ * The price the buyer will actually pay for a category (+tier): the host's
746
+ * `pricing` override when present, else the chart's stored price. Every
747
+ * price the widget DISPLAYS or hands off must flow through here — a map
748
+ * that shows one price while checkout charges another destroys trust.
749
+ */
750
+ private paidPrice;
625
751
  private syncPrices;
752
+ /**
753
+ * Live-activity strip: turn WS availability deltas into one quiet line of
754
+ * social proof ("2 seats just taken in VIP · 118 left"). Diffs per-category
755
+ * counts on every status change — no per-seat payload needed. Skips the very
756
+ * first computation (initial load is not "activity").
757
+ */
758
+ private narrateAvailability;
759
+ private lastCatAvail;
626
760
  /** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
627
761
  private evictTakenSelections;
628
762
  private syncTray;
@@ -649,6 +783,15 @@ declare class SeatPicker {
649
783
  private emitHoldChange;
650
784
  private toast;
651
785
  private placeTooltip;
786
+ /**
787
+ * Row label without the redundant section prefix. Charts commonly name row
788
+ * objects "104-A" while the Section column already shows "104" — so the Row
789
+ * cell repeats the section and, in the compact hover card, truncates to
790
+ * "10…". Strip a leading "<section><sep>" so Row reads a clean "A". Only when
791
+ * the prefix is exact (won't touch "1040-A" under section "104"); otherwise
792
+ * the label is shown verbatim.
793
+ */
794
+ private rowShort;
652
795
  private updateTooltip;
653
796
  getSelection(): PickerSeat[];
654
797
  /** Current active/restored hold reflected in the tray. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PickerSeat, SeatHoverDetails, ChartDoc, ChartTheme, ExpandedSeat } from '@seatlayer/core';
1
+ import { PickerSeat, SeatHoverDetails, PickerTransport, ChartDoc, ChartTheme, ExpandedSeat } from '@seatlayer/core';
2
2
  export { ExpandedSeat, SeatHoverDetails } from '@seatlayer/core';
3
3
 
4
4
  /**
@@ -242,6 +242,25 @@ interface EmbeddedDesignerOptions {
242
242
  style?: Partial<CSSStyleDeclaration>;
243
243
  allow?: string;
244
244
  referrerPolicy?: ReferrerPolicy;
245
+ /**
246
+ * Show the built-in branded loading skeleton and error/expiry card inside the
247
+ * container while the Designer boots. Defaults to `true`. Set `false` when the
248
+ * host renders its own loading and error chrome.
249
+ */
250
+ showLoadingState?: boolean;
251
+ /**
252
+ * If the Designer never posts `ready` within this many milliseconds, the host
253
+ * transitions to the error card with a timeout message. Defaults to `20000`.
254
+ * Only used when `showLoadingState` is enabled.
255
+ */
256
+ loadingTimeoutMs?: number;
257
+ /**
258
+ * Called when the user presses "Try again" on the error card. Use it to mint a
259
+ * fresh Designer session and call `setDesignerUrl()` with the new URL, which
260
+ * recreates the iframe and returns to the loading state. When omitted, "Try
261
+ * again" reloads the current `designerUrl` in place.
262
+ */
263
+ onRequestRelaunch?: () => void;
245
264
  onReady?: (message: EmbeddedDesignerMessage) => void;
246
265
  onSaved?: (message: EmbeddedDesignerMessage) => void;
247
266
  onPublished?: (message: EmbeddedDesignerMessage) => void;
@@ -253,12 +272,30 @@ declare class EmbeddedDesigner {
253
272
  private options;
254
273
  private frame;
255
274
  private designerOrigin;
275
+ private overlay;
276
+ private timeoutTimer;
277
+ private phase;
278
+ private restoreContainerPosition;
256
279
  constructor(options: EmbeddedDesignerOptions);
257
280
  mount(): HTMLIFrameElement;
258
281
  /** Replace the iframe instead of assigning a new fragment to an existing one. */
259
282
  setDesignerUrl(designerUrl: string): HTMLIFrameElement;
260
283
  getIframe(): HTMLIFrameElement | null;
261
284
  destroy(): void;
285
+ private loadingStateEnabled;
286
+ private clearTimeoutTimer;
287
+ private ensureContainerPositioned;
288
+ private restoreContainerStyle;
289
+ private removeOverlay;
290
+ private showError;
291
+ private handleTryAgain;
292
+ /**
293
+ * Build (or rebuild) the overlay for the given phase. A single overlay element
294
+ * is reused so we never stack stale skeletons or cards.
295
+ */
296
+ private renderOverlay;
297
+ private buildSkeleton;
298
+ private buildErrorCard;
262
299
  private handleMessage;
263
300
  }
264
301
 
@@ -317,6 +354,16 @@ interface CheckoutHandoff {
317
354
  /** Convenience total in major units (Σ unitPrice × quantity). */
318
355
  total: number;
319
356
  }
357
+ /** Host-authoritative pricing — see {@link SeatPickerOptions.pricing}. */
358
+ interface SeatPickerPricing {
359
+ /** Unit prices by category key: a flat number, or `{ base, tiers: { tierId: price } }`. */
360
+ prices?: Record<string, number | {
361
+ base?: number;
362
+ tiers?: Record<string, number>;
363
+ }>;
364
+ /** Custom money renderer (e.g. `(n) => n + '€'`). Defaults to Intl currency formatting. */
365
+ formatter?: (amount: number, currency: string) => string;
366
+ }
320
367
  /** Host theme overrides — any subset; unset keys fall back to the org's chart theme, then defaults. */
321
368
  interface SeatPickerTheme {
322
369
  /** Brand accent (CTA, active chips, hold pill). */
@@ -349,6 +396,12 @@ interface SeatPickerOptions {
349
396
  event: string;
350
397
  /** API origin. Defaults to https://api.seatlayer.io. */
351
398
  apiBase?: string;
399
+ /**
400
+ * Custom data transport. Defaults to the CORS-trivial PubApi against
401
+ * `apiBase`. Inject to run the widget against another backend adapter (the
402
+ * SeatLayer dashboard's own transport) or a fully local mock (demos).
403
+ */
404
+ transport?: PickerTransport;
352
405
  /** Reserved for future authenticated rendering. */
353
406
  publicKey?: string;
354
407
  /** Max seats selectable at once (default 10). */
@@ -361,8 +414,23 @@ interface SeatPickerOptions {
361
414
  currency?: string;
362
415
  /** Colorblind-safe rendering (Okabe-Ito palette, hollow booked seats). */
363
416
  colorblindSafe?: boolean;
417
+ /**
418
+ * Hide the "Powered by SeatLayer" attribution badge in the side panel foot.
419
+ * The chart theme's own `hideBadge` flag (paid orgs) also hides it — the badge
420
+ * is shown only when BOTH this option and the theme flag are unset/false.
421
+ */
422
+ hideBadge?: boolean;
364
423
  /** Host theme overrides — see SeatPickerTheme. */
365
424
  theme?: SeatPickerTheme;
425
+ /**
426
+ * Host-authoritative pricing. When your shop charges different prices than
427
+ * the chart's stored category prices, pass them here so the buyer sees the
428
+ * price they will actually pay — on the map tooltip, confirm popover, price
429
+ * panel, tray, totals, and in the checkout handoff's line items. Keyed by
430
+ * category key; per-tier overrides nest under `tiers`. Unlisted categories
431
+ * fall back to the chart price.
432
+ */
433
+ pricing?: SeatPickerPricing;
366
434
  /** Hold TTL in ms passed to hold(); server clamps to its own limits. */
367
435
  holdTtlMs?: number;
368
436
  /**
@@ -454,23 +522,27 @@ declare class SeatPicker {
454
522
  private confirmEl;
455
523
  private confirmSeat;
456
524
  private srEl;
457
- private a11yFilter;
458
525
  private baQty;
459
526
  private baCat;
460
527
  private bestAvailableConfirm;
461
528
  private releasingHold;
529
+ /** Event sales window is closed (read-only load state / live close). */
530
+ private salesClosed;
531
+ /** Every seated category's live availability is 0 (sold-out overlay is up). */
532
+ private soldOut;
533
+ private soldoutEl;
534
+ /** Resolved colorblind-safe state — stored preference wins over the option. */
535
+ private cbSafe;
462
536
  private rungsEl;
463
537
  private floorsEl;
464
538
  private secCardEl;
465
539
  private viewEl;
466
540
  private viewCleanup;
467
541
  private allSeatsCache;
468
- private miniEl;
469
542
  private miniCanvas;
470
543
  private miniBase;
471
544
  private miniTf;
472
545
  private priceBandKeys;
473
- private priceFilterEl;
474
546
  /** Last surfaced section summary (re-rendered when the price band changes). */
475
547
  private lastSection;
476
548
  /** Section card collapsed to its slim pill (seat-picking has begun). */
@@ -489,6 +561,18 @@ declare class SeatPicker {
489
561
  private holdingLabels;
490
562
  private ctaPhase;
491
563
  private a11yChipsEl;
564
+ private fsFallback;
565
+ private fsChangeHandler;
566
+ private fsEscHandler;
567
+ /**
568
+ * Eager sightline preview for the confirm card: a cheap generated forward
569
+ * view (or the organizer's real photo) plus a "Nm to stage · clear
570
+ * sightline" line — the premium at-a-glance moment; click opens the 360.
571
+ */
572
+ private confirmThumbHtml;
573
+ /** Full screen via the native API, falling back to a fixed-position overlay (iOS Safari). */
574
+ private toggleFullscreen;
575
+ private setFsFallback;
492
576
  private cbEl;
493
577
  private modalScrim;
494
578
  private prevFocus;
@@ -516,6 +600,41 @@ declare class SeatPicker {
516
600
  private buildExtendPrompt;
517
601
  /** Success overlay + onBooked fire when the held seats settle to booked. */
518
602
  private buildBookedOverlay;
603
+ /**
604
+ * Localized string with a literal fallback. `t()` returns the key itself for
605
+ * unknown keys, so this collapses that to `fallback` — while still honoring a
606
+ * host `messages` override (which makes `t()` return the override, not the key).
607
+ */
608
+ private tf;
609
+ /** Sold-out overlay — centered over the map, disabled waitlist stub (Gap 2). */
610
+ private buildSoldoutOverlay;
611
+ /**
612
+ * Recompute the sold-out state on every price/availability sync. Sold-out ⇔
613
+ * every SEATED category's live free count is 0. Suppressed when the chart has
614
+ * GA areas (GA capacity isn't per-seat, so seated counts would read 0 and
615
+ * falsely block standing room) — mirrors the public page. Clears live when WS
616
+ * frees a seat up.
617
+ */
618
+ private syncSoldout;
619
+ /**
620
+ * Pure sold-out predicate: every SEATED category's free count is 0, there is at
621
+ * least one seated category, and there are no GA areas (GA capacity isn't
622
+ * per-seat, so seated counts read 0 and would falsely block standing room).
623
+ * `left` is seeded implicitly — a missing key means a fully-booked tier (0 free).
624
+ */
625
+ private isSoldOut;
626
+ /**
627
+ * Sales-closed read-only state (Gap 3): persistent header pill, disabled CTA
628
+ * with a closed label, and frozen best-available / GA controls. `setSalesClosed`
629
+ * is the reactive entry (live 409 event_closed); `applySalesClosed` is the
630
+ * idempotent DOM apply used at load and on transition.
631
+ */
632
+ private setSalesClosed;
633
+ private applySalesClosed;
634
+ /** The badge is hidden when the host opts out OR the org's theme sets hideBadge. */
635
+ private badgeHidden;
636
+ /** Attribution badge in the side-panel foot (Gap 7). Hidden per host/theme. */
637
+ private buildBadge;
519
638
  /**
520
639
  * Create the positioned flex containers that own every persistent map overlay.
521
640
  * Appended once after controller.render(); each chrome piece is then appended
@@ -571,7 +690,7 @@ declare class SeatPicker {
571
690
  private drawMinimapRect;
572
691
  /** Minimap click → focus the section under the point (or overview on a miss). */
573
692
  private minimapJump;
574
- /** Effective price of a category (first tier when tiered, else base price). */
693
+ /** Effective display price of a category: host pricing override → first tier base. */
575
694
  private catPrice;
576
695
  /** Derive price bands: one chip per distinct price (≤5), else quantile ranges. */
577
696
  private priceBands;
@@ -622,7 +741,22 @@ declare class SeatPicker {
622
741
  private openSeatView;
623
742
  private closeSeatView;
624
743
  private money;
744
+ /**
745
+ * The price the buyer will actually pay for a category (+tier): the host's
746
+ * `pricing` override when present, else the chart's stored price. Every
747
+ * price the widget DISPLAYS or hands off must flow through here — a map
748
+ * that shows one price while checkout charges another destroys trust.
749
+ */
750
+ private paidPrice;
625
751
  private syncPrices;
752
+ /**
753
+ * Live-activity strip: turn WS availability deltas into one quiet line of
754
+ * social proof ("2 seats just taken in VIP · 118 left"). Diffs per-category
755
+ * counts on every status change — no per-seat payload needed. Skips the very
756
+ * first computation (initial load is not "activity").
757
+ */
758
+ private narrateAvailability;
759
+ private lastCatAvail;
626
760
  /** A live delta took one of OUR selected (not yet held) seats — evict + tell the buyer. */
627
761
  private evictTakenSelections;
628
762
  private syncTray;
@@ -649,6 +783,15 @@ declare class SeatPicker {
649
783
  private emitHoldChange;
650
784
  private toast;
651
785
  private placeTooltip;
786
+ /**
787
+ * Row label without the redundant section prefix. Charts commonly name row
788
+ * objects "104-A" while the Section column already shows "104" — so the Row
789
+ * cell repeats the section and, in the compact hover card, truncates to
790
+ * "10…". Strip a leading "<section><sep>" so Row reads a clean "A". Only when
791
+ * the prefix is exact (won't touch "1040-A" under section "104"); otherwise
792
+ * the label is shown verbatim.
793
+ */
794
+ private rowShort;
652
795
  private updateTooltip;
653
796
  getSelection(): PickerSeat[];
654
797
  /** Current active/restored hold reflected in the tray. */