@kerusiweb/angular 2.3.0 → 3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerusiweb/angular",
3
- "version": "2.3.0",
3
+ "version": "3.0.0",
4
4
  "description": "An Angular seat-map component that renders Kerusi Seat Map & Availability Format documents as interactive, accessible inline SVG.",
5
5
  "keywords": [
6
6
  "angular",
@@ -20,14 +20,14 @@
20
20
  "author": "Arsyad \"Shad Ahm\" Ahmad",
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "https://github.com/ShadAhm/kerusiweb.git",
23
+ "url": "git+https://github.com/ShadAhm/kerusiweb.git",
24
24
  "directory": "projects/angular"
25
25
  },
26
26
  "homepage": "https://github.com/ShadAhm/kerusiweb",
27
27
  "peerDependencies": {
28
28
  "@angular/common": "^22.1.0",
29
29
  "@angular/core": "^22.1.0",
30
- "@kerusiweb/core": "^2.3.0"
30
+ "@kerusiweb/core": "^3.0.0"
31
31
  },
32
32
  "dependencies": {
33
33
  "tslib": "^2.3.0"
@@ -1,5 +1,5 @@
1
1
  import * as _kerusiweb_core from '@kerusiweb/core';
2
- import { KerusiSeatContext, KerusiMap, KerusiState, KerusiSession, SeatAriaStrings, KerusiSeatmapColors, SeatStyleFn, RenderSeat, KerusiControlStrings, DisallowedReason, KerusiViolation, KerusiViewport, RenderMap, SectionLayout, RenderSection, PlacedSeat, PlacedElement, RenderLegendEntry, SeatOccupantVariant, SequenceReader, HeldSeat, KerusiStateDelta, DeltaApplication } from '@kerusiweb/core';
2
+ import { KerusiSeatContext, KerusiMap, KerusiState, KerusiSession, SeatAriaStrings, KerusiSeatmapColors, SeatStyleFn, RenderSeat, KerusiControlStrings, DisallowedReason, KerusiViolation, KerusiViewport, RenderMap, MapLayout, RenderSection, PlacedSection, PlacedSeat, PlacedElement, RenderLegendEntry, SeatOccupantVariant, SequenceReader, HeldSeat, KerusiStateDelta, DeltaApplication } from '@kerusiweb/core';
3
3
  import * as _angular_core from '@angular/core';
4
4
  import { TemplateRef, Signal } from '@angular/core';
5
5
 
@@ -59,11 +59,8 @@ type ValidationMode =
59
59
  | 'off';
60
60
  /** Per-section rendering overrides, keyed by `Section.id`. */
61
61
  interface SectionRenderOptions {
62
+ /** Leaves the section out. Its space on the canvas stays empty (D4). */
62
63
  hidden?: boolean;
63
- /** Overrides `Section.aspectRatio`. */
64
- aspectRatio?: string;
65
- /** Overrides the seat size for this section only. */
66
- seatSize?: number;
67
64
  /** Overrides the localized `Section.label`. */
68
65
  label?: string;
69
66
  }
@@ -83,8 +80,10 @@ interface SeatDisallowed {
83
80
  * Renders a Kerusi seat map.
84
81
  *
85
82
  * Takes a `KerusiMap` and its `KerusiState` directly — no adapter, no
86
- * intermediate row model. Each `Section` becomes its own `<svg>` with its own
87
- * layout mode and proportions, seats take their color from the map's legend,
83
+ * intermediate row model. The map is one `<svg>` whose `viewBox` is the
84
+ * document's canvas (§4.5), each `Section` is a group on it, every seat is
85
+ * drawn exactly where and as large as the document says, seats take their
86
+ * color from the map's legend,
88
87
  * every seat is keyboard-reachable and announced with its type, price, status
89
88
  * and §4.3.4 accessibility properties, and selection is an immutable set of
90
89
  * seat ids the caller can two-way bind.
@@ -131,17 +130,18 @@ declare class KerusiSeatmapComponent {
131
130
  * of every seat in place of the stock glyph. See {@link KerusiSeatTemplateDirective}.
132
131
  */
133
132
  protected readonly seatTemplate: _angular_core.Signal<KerusiSeatTemplateDirective | undefined>;
134
- /** Grid cell edge, in viewBox units. */
135
- readonly seatSize: _angular_core.InputSignal<number>;
136
- /** Gap between grid cells, in viewBox units. */
137
- readonly seatGap: _angular_core.InputSignal<number>;
138
- /** Freeform viewBox width; height follows the section's aspect ratio. */
139
- readonly freeformBasis: _angular_core.InputSignal<number>;
140
133
  /**
141
- * CSS pixels per viewBox unit. Caps each section at its natural size so a
142
- * narrow section and a wide one in the same map draw seats the same size.
134
+ * CSS pixels per canvas unit, at most: the map never draws wider than
135
+ * `canvas.width × unitScale`, and scales down to a narrower container. The
136
+ * document fixes every seat's size and position (§4.5); this is the only
137
+ * sizing control. Default `1`.
143
138
  */
144
139
  readonly unitScale: _angular_core.InputSignal<number>;
140
+ /**
141
+ * Draws each section's label inside the map, at the top-left of its `bounds`.
142
+ * Only sections that declare `bounds` get one; every section's label is its
143
+ * group's accessible name either way. Default `true`.
144
+ */
145
145
  readonly showSectionLabels: _angular_core.InputSignal<boolean>;
146
146
  readonly showLegend: _angular_core.InputSignal<boolean>;
147
147
  readonly showLegendPrices: _angular_core.InputSignal<boolean>;
@@ -149,7 +149,7 @@ declare class KerusiSeatmapComponent {
149
149
  readonly sectionIds: _angular_core.InputSignal<readonly string[] | undefined>;
150
150
  readonly sectionOverrides: _angular_core.InputSignal<Readonly<Record<string, SectionRenderOptions>>>;
151
151
  /** Statuses in which a seat may be picked. Default: `['available']`. */
152
- readonly selectableStatuses: _angular_core.InputSignal<readonly ("available" | "blocked" | "held" | "booked")[]>;
152
+ readonly selectableStatuses: _angular_core.InputSignal<readonly ("held" | "booked" | "blocked" | "available")[]>;
153
153
  /** `auto` selects a seat's companion closure together (§4.6). */
154
154
  readonly companionMode: _angular_core.InputSignal<"auto" | "independent">;
155
155
  readonly maxSelection: _angular_core.InputSignal<number | undefined>;
@@ -231,8 +231,14 @@ declare class KerusiSeatmapComponent {
231
231
  protected readonly showToolbar: _angular_core.Signal<boolean>;
232
232
  protected readonly atMaxZoom: _angular_core.Signal<boolean>;
233
233
  protected readonly atFit: _angular_core.Signal<boolean>;
234
- /** Placed geometry per section, keyed by section id. */
235
- protected readonly layouts: _angular_core.Signal<ReadonlyMap<string, SectionLayout>>;
234
+ /** Every seat and element placed on the canvas, mirrored as a whole in RTL (D3). */
235
+ protected readonly layout: _angular_core.Signal<MapLayout>;
236
+ protected readonly viewBox: _angular_core.Signal<string>;
237
+ /** `unitScale` CSS pixels per canvas unit, at most. */
238
+ protected readonly naturalWidth: _angular_core.Signal<number>;
239
+ /** The canvas's width over its height, so a maximized map can fit the whole canvas. */
240
+ protected readonly canvasAspect: _angular_core.Signal<string>;
241
+ protected readonly backdrop: _angular_core.Signal<string>;
236
242
  /** Navigation graphs per section — arrows move within, Tab moves between. */
237
243
  private readonly graphs;
238
244
  protected readonly ariaLabels: _angular_core.Signal<ReadonlyMap<string, string>>;
@@ -241,17 +247,15 @@ declare class KerusiSeatmapComponent {
241
247
  protected readonly liveMessage: _angular_core.Signal<string>;
242
248
  /** The selection's seats and total, for callers that want it off the template. */
243
249
  readonly summary: _angular_core.Signal<_kerusiweb_core.SelectionSummary>;
244
- protected layoutFor(section: RenderSection): SectionLayout;
245
250
  protected labelFor(section: RenderSection): string | undefined;
246
- protected labelId(section: RenderSection): string;
247
251
  /** The seat holding this section's tab stop. */
248
252
  protected focusedSeatIn(section: RenderSection): string | null;
249
253
  protected onSeatActivate(seatId: string): void;
250
254
  protected onSeatFocused(seatId: string): void;
251
255
  /**
252
- * Arrow keys walk the navigation graph, which orders seats by `col` in grid
253
- * and mixed sections — §4.3.1's logical adjacency — so the aisle that is a
254
- * skipped column is stepped across rather than into.
256
+ * Arrow keys walk the navigation graph, which follows §4.3.5 adjacency:
257
+ * along a row by `col`, across an aisle (a skipped `col`) rather than into
258
+ * it, and between rows to the seat nearest on the canvas (D6).
255
259
  */
256
260
  protected onKeydown(section: RenderSection, event: KeyboardEvent): void;
257
261
  private setFocusedSeat;
@@ -272,8 +276,15 @@ declare class KerusiSeatmapComponent {
272
276
  zoomIn(): void;
273
277
  /** Zooms out one step about the centre. No-op unless `zoomable`. */
274
278
  zoomOut(): void;
275
- /** Back to fit-to-container: the map's original view. No-op unless `zoomable`. */
279
+ /** Back to fit-to-container: the whole canvas. No-op unless `zoomable`. */
276
280
  resetZoom(): void;
281
+ /**
282
+ * Zooms to one section: its declared `bounds`, or the box around its seats
283
+ * and elements when it declares none. No-op unless `zoomable`, or for a
284
+ * section that is not rendered.
285
+ */
286
+ zoomToSection(sectionId: string): void;
287
+ private zoomToCanvasRect;
277
288
  private stepZoom;
278
289
  /** Sizes in CSS pixels, read when needed: `offset*` ignores the zoom transform. */
279
290
  private measure;
@@ -309,23 +320,32 @@ declare class KerusiSeatmapComponent {
309
320
  private announce;
310
321
  private visibleSectionIds;
311
322
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<KerusiSeatmapComponent, never>;
312
- 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; }; "seatTypeStyles": { "alias": "seatTypeStyles"; "required": false; "isSignal": true; }; "seatStyle": { "alias": "seatStyle"; "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; }; "zoomable": { "alias": "zoomable"; "required": false; "isSignal": true; }; "maxZoom": { "alias": "maxZoom"; "required": false; "isSignal": true; }; "showZoomControls": { "alias": "showZoomControls"; "required": false; "isSignal": true; }; "maximizable": { "alias": "maximizable"; "required": false; "isSignal": true; }; "controlStrings": { "alias": "controlStrings"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; }, { "selection": "selectionChange"; "seatSelect": "seatSelect"; "seatDeselect": "seatDeselect"; "seatDisallowed": "seatDisallowed"; "seatFocus": "seatFocus"; "validationIssues": "validationIssues"; "viewportChange": "viewportChange"; "maximizedChange": "maximizedChange"; }, ["seatTemplate"], never, true, never>;
323
+ 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; }; "seatTypeStyles": { "alias": "seatTypeStyles"; "required": false; "isSignal": true; }; "seatStyle": { "alias": "seatStyle"; "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; }; "zoomable": { "alias": "zoomable"; "required": false; "isSignal": true; }; "maxZoom": { "alias": "maxZoom"; "required": false; "isSignal": true; }; "showZoomControls": { "alias": "showZoomControls"; "required": false; "isSignal": true; }; "maximizable": { "alias": "maximizable"; "required": false; "isSignal": true; }; "controlStrings": { "alias": "controlStrings"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; }, { "selection": "selectionChange"; "seatSelect": "seatSelect"; "seatDeselect": "seatDeselect"; "seatDisallowed": "seatDisallowed"; "seatFocus": "seatFocus"; "validationIssues": "validationIssues"; "viewportChange": "viewportChange"; "maximizedChange": "maximizedChange"; }, ["seatTemplate"], never, true, never>;
313
324
  }
314
325
 
315
326
  /**
316
- * Draws one Kerusi `Section` as a single `<svg>`.
327
+ * Draws one Kerusi `Section` as a `<g>` on the map's shared canvas (D1).
317
328
  *
318
- * One SVG per section — rather than section groups inside a shared canvas — is
319
- * what makes per-section `aspectRatio` and mixed grid/freeform modes work: each
320
- * gets its own `viewBox` and its own intrinsic proportions, and CSS is free to
321
- * stack, wrap or column them. It also puts the section heading in real DOM,
322
- * where a screen reader can use it as a landmark.
329
+ * Sections are zones on one canvas in rev 15 (§4.1), and two may sit side by
330
+ * side, so a section is a group inside the map's single `<svg>` rather than an
331
+ * SVG of its own. Use it on a `<g>` inside an `<svg>` whose `viewBox` is the
332
+ * canvas: `<g kerusi-section [layout]="placed"></g>`. Its template prefixes
333
+ * every element with `svg:`, because Angular cannot tell from a component's
334
+ * own template that it will be stamped into SVG.
323
335
  *
324
336
  * This component holds no state. It takes a placed layout and emits intent.
325
337
  */
326
338
  declare class KerusiSectionComponent {
327
- readonly section: _angular_core.InputSignal<RenderSection>;
328
- readonly layout: _angular_core.InputSignal<SectionLayout>;
339
+ /** The section with its seats and elements placed on the map's canvas. */
340
+ readonly layout: _angular_core.InputSignal<PlacedSection>;
341
+ /** The section's name: its accessible name, and its drawn label when one is shown. */
342
+ readonly label: _angular_core.InputSignal<string | undefined>;
343
+ /**
344
+ * Draw `label` inside the SVG, at the top-left of the section's bounds. Only
345
+ * a section that declares `bounds` gets one (D5); otherwise the label is the
346
+ * group's accessible name alone.
347
+ */
348
+ readonly showLabel: _angular_core.InputSignal<boolean>;
329
349
  /** Currently selected seat ids. */
330
350
  readonly selection: _angular_core.InputSignal<ReadonlySet<string>>;
331
351
  /** The seat holding the section's single tab stop, if any. */
@@ -343,22 +363,12 @@ declare class KerusiSectionComponent {
343
363
  readonly seatTemplate: _angular_core.InputSignal<TemplateRef<SeatTemplateOutletContext> | undefined>;
344
364
  /** When false, seats render but do not respond to pointer or keyboard. */
345
365
  readonly interactive: _angular_core.InputSignal<boolean>;
346
- /**
347
- * CSS pixels per viewBox unit, at the section's natural size.
348
- *
349
- * Without a cap every section stretches to the full container width, so in a
350
- * four-tier theatre the narrow box row would draw seats several times the
351
- * size of the orchestra's. Capping each section at its own intrinsic width
352
- * keeps a seat the same size everywhere; a section too wide to fit still
353
- * scales down to the container.
354
- */
355
- readonly unitScale: _angular_core.InputSignal<number>;
356
366
  readonly seatActivate: _angular_core.OutputEmitterRef<string>;
357
367
  readonly seatFocused: _angular_core.OutputEmitterRef<string>;
358
368
  readonly seatKeydown: _angular_core.OutputEmitterRef<KeyboardEvent>;
359
- protected readonly viewBox: _angular_core.Signal<string>;
360
- /** The section's natural width in CSS pixels; it never draws wider. */
361
- protected readonly naturalWidth: _angular_core.Signal<number>;
369
+ /** Whether the label is drawn: asked for, present, and with declared bounds to sit in. */
370
+ protected readonly drawsLabel: _angular_core.Signal<boolean>;
371
+ protected readonly labelFill: _angular_core.Signal<string>;
362
372
  /**
363
373
  * Every seat with its glyph resolved, once per change of layout, selection or
364
374
  * theme rather than once per binding per change detection.
@@ -368,7 +378,6 @@ declare class KerusiSectionComponent {
368
378
  selected: boolean;
369
379
  glyph: _kerusiweb_core.SeatGlyph;
370
380
  }[]>;
371
- protected backdrop(): string;
372
381
  /** The section's single tab stop; every other seat is reachable by arrow key. */
373
382
  protected tabIndex(placed: PlacedSeat): number;
374
383
  protected ariaLabel(placed: PlacedSeat): string;
@@ -390,7 +399,7 @@ declare class KerusiSectionComponent {
390
399
  protected onSeatFocus(placed: PlacedSeat): void;
391
400
  protected onKeydown(event: KeyboardEvent): void;
392
401
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<KerusiSectionComponent, never>;
393
- 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; }; "seatTypeStyles": { "alias": "seatTypeStyles"; "required": false; "isSignal": true; }; "seatStyle": { "alias": "seatStyle"; "required": false; "isSignal": true; }; "seatTemplate": { "alias": "seatTemplate"; "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>;
402
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<KerusiSectionComponent, "g[kerusi-section]", never, { "layout": { "alias": "layout"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "showLabel": { "alias": "showLabel"; "required": false; "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; }; "seatTypeStyles": { "alias": "seatTypeStyles"; "required": false; "isSignal": true; }; "seatStyle": { "alias": "seatStyle"; "required": false; "isSignal": true; }; "seatTemplate": { "alias": "seatTemplate"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; }, { "seatActivate": "seatActivate"; "seatFocused": "seatFocused"; "seatKeydown": "seatKeydown"; }, never, never, true, never>;
394
403
  }
395
404
 
396
405
  /** Which marks, beyond the body fill, a status swatch draws. */