@kerusiweb/angular 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +336 -0
- package/fesm2022/kerusiweb-angular.mjs +871 -0
- package/fesm2022/kerusiweb-angular.mjs.map +1 -0
- package/package.json +51 -0
- package/types/kerusiweb-angular.d.ts +390 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import * as _kerusiweb_core from '@kerusiweb/core';
|
|
2
|
+
import { KerusiMap, KerusiState, KerusiSession, SeatAriaStrings, KerusiSeatmapColors, RenderSeat, DisallowedReason, KerusiViolation, RenderMap, SectionLayout, RenderSection, PlacedSeat, SeatOccupantVariant, PlacedElement, RenderLegendEntry, SequenceReader, HeldSeat, KerusiStateDelta, DeltaApplication } from '@kerusiweb/core';
|
|
3
|
+
import * as _angular_core from '@angular/core';
|
|
4
|
+
import { Signal } from '@angular/core';
|
|
5
|
+
|
|
6
|
+
/** How a document with validation errors is handled. */
|
|
7
|
+
type ValidationMode =
|
|
8
|
+
/** Report through `validationIssues` and render anyway. The default. */
|
|
9
|
+
'collect'
|
|
10
|
+
/** Throw a `KerusiValidationError`, as `validateKerusiMap` does. */
|
|
11
|
+
| 'throw'
|
|
12
|
+
/** Skip validation entirely, for a document validated upstream. */
|
|
13
|
+
| 'off';
|
|
14
|
+
/** Per-section rendering overrides, keyed by `Section.id`. */
|
|
15
|
+
interface SectionRenderOptions {
|
|
16
|
+
hidden?: boolean;
|
|
17
|
+
/** Overrides `Section.aspectRatio`. */
|
|
18
|
+
aspectRatio?: string;
|
|
19
|
+
/** Overrides the seat size for this section only. */
|
|
20
|
+
seatSize?: number;
|
|
21
|
+
/** Overrides the localized `Section.label`. */
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
/** What a seat event carries. Everything the document knows, already resolved. */
|
|
25
|
+
interface SeatInteraction {
|
|
26
|
+
seat: RenderSeat;
|
|
27
|
+
/** The selection after this event. */
|
|
28
|
+
selection: readonly string[];
|
|
29
|
+
/** Seats whose membership changed, including companions (§4.6). */
|
|
30
|
+
changed: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
interface SeatDisallowed {
|
|
33
|
+
seat: RenderSeat;
|
|
34
|
+
reason: DisallowedReason;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Renders a Kerusi seat map.
|
|
38
|
+
*
|
|
39
|
+
* Takes a `KerusiMap` and its `KerusiState` directly — no adapter, no
|
|
40
|
+
* intermediate row model. Each `Section` becomes its own `<svg>` with its own
|
|
41
|
+
* layout mode and proportions, seats take their color from the map's legend,
|
|
42
|
+
* every seat is keyboard-reachable and announced with its type, price, status
|
|
43
|
+
* and §4.3.4 accessibility properties, and selection is an immutable set of
|
|
44
|
+
* seat ids the caller can two-way bind.
|
|
45
|
+
*
|
|
46
|
+
* ```html
|
|
47
|
+
* <kerusi-seatmap [map]="map" [state]="state" [(selection)]="picked" />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare class KerusiSeatmapComponent {
|
|
51
|
+
private readonly host;
|
|
52
|
+
readonly map: _angular_core.InputSignal<KerusiMap>;
|
|
53
|
+
readonly state: _angular_core.InputSignal<KerusiState | undefined>;
|
|
54
|
+
/** The optional map↔event join (§5.3). Validated against the map and state. */
|
|
55
|
+
readonly session: _angular_core.InputSignal<KerusiSession | undefined>;
|
|
56
|
+
/** Selected seat ids. Two-way: `[(selection)]`, or read-only with `[selection]`. */
|
|
57
|
+
readonly selection: _angular_core.ModelSignal<readonly string[]>;
|
|
58
|
+
/** Overrides `KerusiMap.locale` (BCP-47). */
|
|
59
|
+
readonly locale: _angular_core.InputSignal<string | undefined>;
|
|
60
|
+
/** Right-to-left rendering. `auto` derives it from the resolved locale. */
|
|
61
|
+
readonly rtl: _angular_core.InputSignal<boolean | "auto">;
|
|
62
|
+
/** Overrides the built-in English announcement strings. */
|
|
63
|
+
readonly ariaStrings: _angular_core.InputSignal<SeatAriaStrings>;
|
|
64
|
+
readonly colors: _angular_core.InputSignal<KerusiSeatmapColors>;
|
|
65
|
+
/** Let an available seat take its `SeatType.color` (§4.7). */
|
|
66
|
+
readonly typeColors: _angular_core.InputSignal<boolean>;
|
|
67
|
+
/** Grid cell edge, in viewBox units. */
|
|
68
|
+
readonly seatSize: _angular_core.InputSignal<number>;
|
|
69
|
+
/** Gap between grid cells, in viewBox units. */
|
|
70
|
+
readonly seatGap: _angular_core.InputSignal<number>;
|
|
71
|
+
/** Freeform viewBox width; height follows the section's aspect ratio. */
|
|
72
|
+
readonly freeformBasis: _angular_core.InputSignal<number>;
|
|
73
|
+
/**
|
|
74
|
+
* CSS pixels per viewBox unit. Caps each section at its natural size so a
|
|
75
|
+
* narrow section and a wide one in the same map draw seats the same size.
|
|
76
|
+
*/
|
|
77
|
+
readonly unitScale: _angular_core.InputSignal<number>;
|
|
78
|
+
readonly showSectionLabels: _angular_core.InputSignal<boolean>;
|
|
79
|
+
readonly showLegend: _angular_core.InputSignal<boolean>;
|
|
80
|
+
readonly showLegendPrices: _angular_core.InputSignal<boolean>;
|
|
81
|
+
/** Render only these sections, in this order. Default: all, by `Section.index`. */
|
|
82
|
+
readonly sectionIds: _angular_core.InputSignal<readonly string[] | undefined>;
|
|
83
|
+
readonly sectionOverrides: _angular_core.InputSignal<Readonly<Record<string, SectionRenderOptions>>>;
|
|
84
|
+
/** Statuses in which a seat may be picked. Default: `['available']`. */
|
|
85
|
+
readonly selectableStatuses: _angular_core.InputSignal<readonly ("held" | "booked" | "available" | "blocked")[]>;
|
|
86
|
+
/** `auto` selects a seat's companion closure together (§4.6). */
|
|
87
|
+
readonly companionMode: _angular_core.InputSignal<"auto" | "independent">;
|
|
88
|
+
readonly maxSelection: _angular_core.InputSignal<number | undefined>;
|
|
89
|
+
/** A final say on selectability, applied after the status test. */
|
|
90
|
+
readonly seatSelectable: _angular_core.InputSignal<((seat: RenderSeat) => boolean) | undefined>;
|
|
91
|
+
/** When false the map renders read-only: no pointer or keyboard interaction. */
|
|
92
|
+
readonly interactive: _angular_core.InputSignal<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Revert a lapsed `holdExpires` to available on a ticker (§5.1). Off by
|
|
95
|
+
* default — a renderer should stay passive unless asked.
|
|
96
|
+
*/
|
|
97
|
+
readonly expireHolds: _angular_core.InputSignal<boolean>;
|
|
98
|
+
readonly expiryIntervalMs: _angular_core.InputSignal<number>;
|
|
99
|
+
readonly validate: _angular_core.InputSignal<ValidationMode>;
|
|
100
|
+
readonly seatSelect: _angular_core.OutputEmitterRef<SeatInteraction>;
|
|
101
|
+
readonly seatDeselect: _angular_core.OutputEmitterRef<SeatInteraction>;
|
|
102
|
+
readonly seatDisallowed: _angular_core.OutputEmitterRef<SeatDisallowed>;
|
|
103
|
+
readonly seatFocus: _angular_core.OutputEmitterRef<RenderSeat>;
|
|
104
|
+
/** Emitted whenever the documents change, in `collect` mode. */
|
|
105
|
+
readonly validationIssues: _angular_core.OutputEmitterRef<readonly KerusiViolation[]>;
|
|
106
|
+
/**
|
|
107
|
+
* The seat holding each section's tab stop, keyed by section id.
|
|
108
|
+
*
|
|
109
|
+
* Per section, not per map: arrow keys never cross a section boundary, so a
|
|
110
|
+
* single map-wide tab stop would leave every section after the first
|
|
111
|
+
* unreachable by keyboard. Tab moves between sections, arrows move within.
|
|
112
|
+
*/
|
|
113
|
+
private readonly focusedSeatIds;
|
|
114
|
+
private readonly announcement;
|
|
115
|
+
/** Bumped by the hold ticker to force the state to be re-derived. */
|
|
116
|
+
private readonly expiryTick;
|
|
117
|
+
constructor();
|
|
118
|
+
protected readonly issues: _angular_core.Signal<readonly KerusiViolation[]>;
|
|
119
|
+
/** The state actually rendered — with lapsed holds reverted when asked. */
|
|
120
|
+
private readonly effectiveState;
|
|
121
|
+
protected readonly renderMap: _angular_core.Signal<RenderMap>;
|
|
122
|
+
protected readonly resolvedLocale: _angular_core.Signal<string>;
|
|
123
|
+
protected readonly isRtl: _angular_core.Signal<boolean>;
|
|
124
|
+
protected readonly resolvedColors: _angular_core.Signal<Required<KerusiSeatmapColors>>;
|
|
125
|
+
protected readonly selectionSet: _angular_core.Signal<Set<string>>;
|
|
126
|
+
/** Placed geometry per section, keyed by section id. */
|
|
127
|
+
protected readonly layouts: _angular_core.Signal<ReadonlyMap<string, SectionLayout>>;
|
|
128
|
+
/** Navigation graphs per section — arrows move within, Tab moves between. */
|
|
129
|
+
private readonly graphs;
|
|
130
|
+
protected readonly ariaLabels: _angular_core.Signal<ReadonlyMap<string, string>>;
|
|
131
|
+
protected readonly mapLabel: _angular_core.Signal<string>;
|
|
132
|
+
/** Live-region text: the running selection, or the last disallowed reason. */
|
|
133
|
+
protected readonly liveMessage: _angular_core.Signal<string>;
|
|
134
|
+
/** The selection's seats and total, for callers that want it off the template. */
|
|
135
|
+
readonly summary: _angular_core.Signal<_kerusiweb_core.SelectionSummary>;
|
|
136
|
+
protected layoutFor(section: RenderSection): SectionLayout;
|
|
137
|
+
protected labelFor(section: RenderSection): string | undefined;
|
|
138
|
+
protected labelId(section: RenderSection): string;
|
|
139
|
+
/** The seat holding this section's tab stop. */
|
|
140
|
+
protected focusedSeatIn(section: RenderSection): string | null;
|
|
141
|
+
protected onSeatActivate(seatId: string): void;
|
|
142
|
+
protected onSeatFocused(seatId: string): void;
|
|
143
|
+
/**
|
|
144
|
+
* Arrow keys walk the navigation graph, which orders seats by `col` in grid
|
|
145
|
+
* and mixed sections — §4.3.1's logical adjacency — so the aisle that is a
|
|
146
|
+
* skipped column is stepped across rather than into.
|
|
147
|
+
*/
|
|
148
|
+
protected onKeydown(section: RenderSection, event: KeyboardEvent): void;
|
|
149
|
+
private setFocusedSeat;
|
|
150
|
+
/**
|
|
151
|
+
* Moves the roving tab stop, then the DOM focus that follows it.
|
|
152
|
+
*
|
|
153
|
+
* Seat ids come from the document, so they cannot be interpolated into a
|
|
154
|
+
* selector unescaped — and `CSS.escape` is absent in jsdom and under SSR.
|
|
155
|
+
* Scanning the rendered nodes avoids both problems.
|
|
156
|
+
*/
|
|
157
|
+
private moveFocusTo;
|
|
158
|
+
private announceSelection;
|
|
159
|
+
private announce;
|
|
160
|
+
private visibleSectionIds;
|
|
161
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<KerusiSeatmapComponent, never>;
|
|
162
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<KerusiSeatmapComponent, "kerusi-seatmap", never, { "map": { "alias": "map"; "required": true; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "session": { "alias": "session"; "required": false; "isSignal": true; }; "selection": { "alias": "selection"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "rtl": { "alias": "rtl"; "required": false; "isSignal": true; }; "ariaStrings": { "alias": "ariaStrings"; "required": false; "isSignal": true; }; "colors": { "alias": "colors"; "required": false; "isSignal": true; }; "typeColors": { "alias": "typeColors"; "required": false; "isSignal": true; }; "seatSize": { "alias": "seatSize"; "required": false; "isSignal": true; }; "seatGap": { "alias": "seatGap"; "required": false; "isSignal": true; }; "freeformBasis": { "alias": "freeformBasis"; "required": false; "isSignal": true; }; "unitScale": { "alias": "unitScale"; "required": false; "isSignal": true; }; "showSectionLabels": { "alias": "showSectionLabels"; "required": false; "isSignal": true; }; "showLegend": { "alias": "showLegend"; "required": false; "isSignal": true; }; "showLegendPrices": { "alias": "showLegendPrices"; "required": false; "isSignal": true; }; "sectionIds": { "alias": "sectionIds"; "required": false; "isSignal": true; }; "sectionOverrides": { "alias": "sectionOverrides"; "required": false; "isSignal": true; }; "selectableStatuses": { "alias": "selectableStatuses"; "required": false; "isSignal": true; }; "companionMode": { "alias": "companionMode"; "required": false; "isSignal": true; }; "maxSelection": { "alias": "maxSelection"; "required": false; "isSignal": true; }; "seatSelectable": { "alias": "seatSelectable"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "expireHolds": { "alias": "expireHolds"; "required": false; "isSignal": true; }; "expiryIntervalMs": { "alias": "expiryIntervalMs"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; }, { "selection": "selectionChange"; "seatSelect": "seatSelect"; "seatDeselect": "seatDeselect"; "seatDisallowed": "seatDisallowed"; "seatFocus": "seatFocus"; "validationIssues": "validationIssues"; }, never, never, true, never>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Draws one Kerusi `Section` as a single `<svg>`.
|
|
167
|
+
*
|
|
168
|
+
* One SVG per section — rather than section groups inside a shared canvas — is
|
|
169
|
+
* what makes per-section `aspectRatio` and mixed grid/freeform modes work: each
|
|
170
|
+
* gets its own `viewBox` and its own intrinsic proportions, and CSS is free to
|
|
171
|
+
* stack, wrap or column them. It also puts the section heading in real DOM,
|
|
172
|
+
* where a screen reader can use it as a landmark.
|
|
173
|
+
*
|
|
174
|
+
* This component holds no state. It takes a placed layout and emits intent.
|
|
175
|
+
*/
|
|
176
|
+
declare class KerusiSectionComponent {
|
|
177
|
+
readonly section: _angular_core.InputSignal<RenderSection>;
|
|
178
|
+
readonly layout: _angular_core.InputSignal<SectionLayout>;
|
|
179
|
+
/** Currently selected seat ids. */
|
|
180
|
+
readonly selection: _angular_core.InputSignal<ReadonlySet<string>>;
|
|
181
|
+
/** The seat holding the section's single tab stop, if any. */
|
|
182
|
+
readonly focusedSeatId: _angular_core.InputSignal<string | null>;
|
|
183
|
+
/** Per-seat accessible names, keyed by seat id. */
|
|
184
|
+
readonly ariaLabels: _angular_core.InputSignal<ReadonlyMap<string, string>>;
|
|
185
|
+
readonly colors: _angular_core.InputSignal<Required<KerusiSeatmapColors>>;
|
|
186
|
+
/** Whether an available seat may take its `SeatType.color` (§4.7). */
|
|
187
|
+
readonly typeColors: _angular_core.InputSignal<boolean>;
|
|
188
|
+
/** When false, seats render but do not respond to pointer or keyboard. */
|
|
189
|
+
readonly interactive: _angular_core.InputSignal<boolean>;
|
|
190
|
+
/**
|
|
191
|
+
* CSS pixels per viewBox unit, at the section's natural size.
|
|
192
|
+
*
|
|
193
|
+
* Without a cap every section stretches to the full container width, so in a
|
|
194
|
+
* four-tier theatre the narrow box row would draw seats several times the
|
|
195
|
+
* size of the orchestra's. Capping each section at its own intrinsic width
|
|
196
|
+
* keeps a seat the same size everywhere; a section too wide to fit still
|
|
197
|
+
* scales down to the container.
|
|
198
|
+
*/
|
|
199
|
+
readonly unitScale: _angular_core.InputSignal<number>;
|
|
200
|
+
readonly seatActivate: _angular_core.OutputEmitterRef<string>;
|
|
201
|
+
readonly seatFocused: _angular_core.OutputEmitterRef<string>;
|
|
202
|
+
readonly seatKeydown: _angular_core.OutputEmitterRef<KeyboardEvent>;
|
|
203
|
+
protected readonly viewBox: _angular_core.Signal<string>;
|
|
204
|
+
/** The section's natural width in CSS pixels; it never draws wider. */
|
|
205
|
+
protected readonly naturalWidth: _angular_core.Signal<number>;
|
|
206
|
+
protected isSelected(placed: PlacedSeat): boolean;
|
|
207
|
+
protected fill(placed: PlacedSeat): string;
|
|
208
|
+
protected textFill(placed: PlacedSeat): string;
|
|
209
|
+
protected backdrop(): string;
|
|
210
|
+
/** The seat's outline: square-ish front, tapered back — shape carries facing. */
|
|
211
|
+
protected bodyPath(placed: PlacedSeat): string;
|
|
212
|
+
/** `null` for an available or blocked seat — nothing to mark. */
|
|
213
|
+
protected occupantVariant(placed: PlacedSeat): SeatOccupantVariant | null;
|
|
214
|
+
/**
|
|
215
|
+
* The box a seat's decorative marks are drawn to: the seat's own, or — when
|
|
216
|
+
* it is selected — the core plate's, which is inset by the frame width.
|
|
217
|
+
*
|
|
218
|
+
* Everything over a selected seat sits on the core, so it all has to be
|
|
219
|
+
* measured from the core. Drawing to the seat's box instead leaves the
|
|
220
|
+
* occupant's hips crossing the frame, where a figure tinted `selectedBg`
|
|
221
|
+
* lands on a frame filled `selectedBg` and reads as a hard nub. Since the
|
|
222
|
+
* core is an exact scaled copy of the body (see {@link seatSelectedFrame}),
|
|
223
|
+
* routing through here reproduces every clearance the mark had against the
|
|
224
|
+
* body, scaled — so the frame width stays free to change.
|
|
225
|
+
*/
|
|
226
|
+
private markBox;
|
|
227
|
+
/** The bright plate inside a selected seat's frame. */
|
|
228
|
+
protected corePath(placed: PlacedSeat): string;
|
|
229
|
+
protected coreFill(): string;
|
|
230
|
+
/** The dimming wash over a held or booked seat's type colour. */
|
|
231
|
+
protected washFill(placed: PlacedSeat): string;
|
|
232
|
+
/** The occupant figure for a marked seat, sized to whatever it sits on. */
|
|
233
|
+
protected occupantPath(placed: PlacedSeat): string;
|
|
234
|
+
protected occupantStroke(placed: PlacedSeat): number;
|
|
235
|
+
protected wheelchairCx(placed: PlacedSeat): number;
|
|
236
|
+
protected wheelchairCy(placed: PlacedSeat): number;
|
|
237
|
+
protected wheelchairRadius(placed: PlacedSeat): number;
|
|
238
|
+
/**
|
|
239
|
+
* A core-coloured outline behind a selected seat's number, painted under the
|
|
240
|
+
* glyphs via `paint-order`.
|
|
241
|
+
*
|
|
242
|
+
* The label is centred on the occupant's shoulder line, so without this it is
|
|
243
|
+
* `selectedBg` over a `selectedBg` silhouette — the figure, not the plate, is
|
|
244
|
+
* what it actually has to contrast against. The halo also rescues a label too
|
|
245
|
+
* long for the core, which would otherwise run onto the frame and vanish into
|
|
246
|
+
* it.
|
|
247
|
+
*
|
|
248
|
+
* Bound here rather than in the stylesheet on purpose: a CSS
|
|
249
|
+
* `var(--kerusi-selected-fg, …)` rule would carry the library default as its
|
|
250
|
+
* fallback and silently skip the `[colors]` input tier, which only the
|
|
251
|
+
* TypeScript side can resolve.
|
|
252
|
+
*/
|
|
253
|
+
protected labelHalo(placed: PlacedSeat): string;
|
|
254
|
+
protected labelHaloWidth(placed: PlacedSeat): number;
|
|
255
|
+
/** Solid for selected/booked; `none` for held, which draws hollow instead. */
|
|
256
|
+
protected occupantFill(variant: SeatOccupantVariant): string;
|
|
257
|
+
protected occupantStrokeColor(variant: SeatOccupantVariant): string;
|
|
258
|
+
/** The section's single tab stop; every other seat is reachable by arrow key. */
|
|
259
|
+
protected tabIndex(placed: PlacedSeat): number;
|
|
260
|
+
protected ariaLabel(placed: PlacedSeat): string;
|
|
261
|
+
protected seatClasses(placed: PlacedSeat): string;
|
|
262
|
+
/** SVG rotate() about the item's center; null when it does not rotate. */
|
|
263
|
+
protected transform(item: PlacedSeat | PlacedElement): string | null;
|
|
264
|
+
/** Cancels the parent <g>'s rotation so the label stays upright and stays put. */
|
|
265
|
+
protected counterRotate(item: PlacedSeat | PlacedElement): string | null;
|
|
266
|
+
protected style(placed: PlacedElement): _kerusiweb_core.ElementStyle;
|
|
267
|
+
protected elementFillOf(placed: PlacedElement): string;
|
|
268
|
+
protected elementTextFillOf(placed: PlacedElement): string;
|
|
269
|
+
protected screenPathOf(placed: PlacedElement): string;
|
|
270
|
+
/** The corner radius for a rectangular element; a stage reads as squarer. */
|
|
271
|
+
protected radiusOf(placed: PlacedElement): number;
|
|
272
|
+
protected onSeatClick(placed: PlacedSeat): void;
|
|
273
|
+
protected onSeatFocus(placed: PlacedSeat): void;
|
|
274
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
275
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<KerusiSectionComponent, never>;
|
|
276
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<KerusiSectionComponent, "kerusi-section", never, { "section": { "alias": "section"; "required": true; "isSignal": true; }; "layout": { "alias": "layout"; "required": true; "isSignal": true; }; "selection": { "alias": "selection"; "required": false; "isSignal": true; }; "focusedSeatId": { "alias": "focusedSeatId"; "required": false; "isSignal": true; }; "ariaLabels": { "alias": "ariaLabels"; "required": false; "isSignal": true; }; "colors": { "alias": "colors"; "required": false; "isSignal": true; }; "typeColors": { "alias": "typeColors"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "unitScale": { "alias": "unitScale"; "required": false; "isSignal": true; }; }, { "seatActivate": "seatActivate"; "seatFocused": "seatFocused"; "seatKeydown": "seatKeydown"; }, never, never, true, never>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Which marks, beyond the body fill, a status swatch draws. */
|
|
280
|
+
type StatusKind = 'plain' | 'blocked' | SeatOccupantVariant;
|
|
281
|
+
/** One availability swatch in the legend's second block. */
|
|
282
|
+
interface StatusEntry {
|
|
283
|
+
key: string;
|
|
284
|
+
label: string;
|
|
285
|
+
kind: StatusKind;
|
|
286
|
+
/** The swatch body's fill. */
|
|
287
|
+
fill: string;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* A key for a seat map: its seat types on one side, its availability states on
|
|
291
|
+
* the other.
|
|
292
|
+
*
|
|
293
|
+
* This ships in the library rather than being left to each application because
|
|
294
|
+
* the swatch colors must come from the *same* resolution path as the seat
|
|
295
|
+
* fills — `SeatType.color` when the document supplies one, the theme's
|
|
296
|
+
* available color when it does not. Re-deriving that in application code is how
|
|
297
|
+
* a legend ends up disagreeing with the map it describes.
|
|
298
|
+
*
|
|
299
|
+
* It is opt-in: `<kerusi-seatmap [showLegend]="true">` renders it inline, or
|
|
300
|
+
* use `<kerusi-legend>` directly to place it anywhere.
|
|
301
|
+
*/
|
|
302
|
+
declare class KerusiLegendComponent {
|
|
303
|
+
/** From `RenderMap.legend`. */
|
|
304
|
+
readonly legend: _angular_core.InputSignal<readonly RenderLegendEntry[]>;
|
|
305
|
+
readonly locale: _angular_core.InputSignal<string>;
|
|
306
|
+
readonly colors: _angular_core.InputSignal<Required<KerusiSeatmapColors>>;
|
|
307
|
+
/** Whether an available seat takes its type color — mirrors the seatmap input. */
|
|
308
|
+
readonly typeColors: _angular_core.InputSignal<boolean>;
|
|
309
|
+
readonly showPrices: _angular_core.InputSignal<boolean>;
|
|
310
|
+
/** Hide types no seat in the rendered map actually uses. */
|
|
311
|
+
readonly hideUnusedTypes: _angular_core.InputSignal<boolean>;
|
|
312
|
+
readonly headingSeatTypes: _angular_core.InputSignal<string>;
|
|
313
|
+
readonly headingAvailability: _angular_core.InputSignal<string>;
|
|
314
|
+
protected readonly types: _angular_core.Signal<RenderLegendEntry[]>;
|
|
315
|
+
protected readonly statuses: _angular_core.Signal<StatusEntry[]>;
|
|
316
|
+
protected swatch(entry: RenderLegendEntry): string;
|
|
317
|
+
protected swatchText(entry: RenderLegendEntry): string;
|
|
318
|
+
/** The swatch's viewBox edge; the seat glyph is drawn to fill it. */
|
|
319
|
+
protected readonly glyphSize = 16;
|
|
320
|
+
protected readonly glyphBody: string;
|
|
321
|
+
/** The selected swatch's frame width, and the core it leaves inside itself. */
|
|
322
|
+
private readonly glyphFrame;
|
|
323
|
+
private readonly glyphCoreSize;
|
|
324
|
+
protected readonly glyphCore: string;
|
|
325
|
+
protected readonly glyphOccupant: string;
|
|
326
|
+
protected readonly glyphOccupantStroke: number;
|
|
327
|
+
/** The selected swatch's figure sits on the core, so it is drawn to the core. */
|
|
328
|
+
private readonly glyphCoreOccupant;
|
|
329
|
+
private readonly glyphCoreOccupantStroke;
|
|
330
|
+
protected occupantPathOf(variant: SeatOccupantVariant): string;
|
|
331
|
+
protected occupantStrokeWidthOf(variant: SeatOccupantVariant): number;
|
|
332
|
+
protected hasWash(kind: StatusKind): kind is 'held' | 'booked';
|
|
333
|
+
protected hasOccupant(kind: StatusKind): kind is SeatOccupantVariant;
|
|
334
|
+
protected washFillOf(kind: 'held' | 'booked'): string;
|
|
335
|
+
protected occupantFillOf(variant: SeatOccupantVariant): string;
|
|
336
|
+
protected occupantStrokeOf(variant: SeatOccupantVariant): string;
|
|
337
|
+
protected coreFill(): string;
|
|
338
|
+
protected price(entry: RenderLegendEntry): string;
|
|
339
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<KerusiLegendComponent, never>;
|
|
340
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<KerusiLegendComponent, "kerusi-legend", never, { "legend": { "alias": "legend"; "required": true; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "colors": { "alias": "colors"; "required": false; "isSignal": true; }; "typeColors": { "alias": "typeColors"; "required": false; "isSignal": true; }; "showPrices": { "alias": "showPrices"; "required": false; "isSignal": true; }; "hideUnusedTypes": { "alias": "hideUnusedTypes"; "required": false; "isSignal": true; }; "headingSeatTypes": { "alias": "headingSeatTypes"; "required": false; "isSignal": true; }; "headingAvailability": { "alias": "headingAvailability"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* A signal-backed live view of a {@link KerusiState}.
|
|
345
|
+
*
|
|
346
|
+
* Construct it directly — `new KerusiStateStore(initialState)` — and feed it
|
|
347
|
+
* deltas from whatever transport you use. It is deliberately not an
|
|
348
|
+
* `@Injectable`: this is a rendering library, and an application that already
|
|
349
|
+
* owns its availability state should keep owning it.
|
|
350
|
+
*/
|
|
351
|
+
declare class KerusiStateStore {
|
|
352
|
+
private readonly _state;
|
|
353
|
+
private readonly _needsRefetch;
|
|
354
|
+
private readonly _now;
|
|
355
|
+
private readonly clock;
|
|
356
|
+
private readonly sequenceOf;
|
|
357
|
+
private lastSequence;
|
|
358
|
+
constructor(initial: KerusiState, opts?: {
|
|
359
|
+
now?: () => Date;
|
|
360
|
+
sequenceOf?: SequenceReader;
|
|
361
|
+
});
|
|
362
|
+
/** The current availability snapshot. */
|
|
363
|
+
get state(): Signal<KerusiState>;
|
|
364
|
+
/** The timestamp of the most recently applied document. */
|
|
365
|
+
readonly updatedAt: Signal<string>;
|
|
366
|
+
/**
|
|
367
|
+
* True once a gap was detected in the delta stream. The consumer SHOULD
|
|
368
|
+
* re-fetch a full `KerusiState` and hand it to {@link reset} (§5.2).
|
|
369
|
+
*/
|
|
370
|
+
get needsRefetch(): Signal<boolean>;
|
|
371
|
+
/** Every currently-held seat, with the time left on its hold. */
|
|
372
|
+
readonly heldSeats: Signal<readonly HeldSeat[]>;
|
|
373
|
+
/** Offers a delta to the store. Returns what happened to it. */
|
|
374
|
+
apply(delta: KerusiStateDelta): DeltaApplication;
|
|
375
|
+
/**
|
|
376
|
+
* Replaces the state wholesale — the post-refetch path. Clears the
|
|
377
|
+
* refetch flag and forgets the delta sequence.
|
|
378
|
+
*/
|
|
379
|
+
reset(state: KerusiState): void;
|
|
380
|
+
/** Reverts lapsed holds and refreshes `heldSeats` countdowns. */
|
|
381
|
+
tick(): void;
|
|
382
|
+
/**
|
|
383
|
+
* Runs {@link tick} on an interval. Returns a function that stops it — call
|
|
384
|
+
* that from `DestroyRef.onDestroy` or `ngOnDestroy`.
|
|
385
|
+
*/
|
|
386
|
+
startExpiryTicker(intervalMs?: number): () => void;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export { KerusiLegendComponent, KerusiSeatmapComponent, KerusiSectionComponent, KerusiStateStore };
|
|
390
|
+
export type { SeatDisallowed, SeatInteraction, SectionRenderOptions, ValidationMode };
|