@kerusiweb/angular 2.0.0 → 2.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/README.md +116 -31
- package/fesm2022/kerusiweb-angular.mjs +412 -10
- package/fesm2022/kerusiweb-angular.mjs.map +1 -1
- package/package.json +2 -2
- package/types/kerusiweb-angular.d.ts +95 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ npm install @kerusiweb/core @kerusiweb/angular
|
|
|
9
9
|
|
|
10
10
|
Requires Angular 22+. Standalone, signal-based, zoneless-friendly.
|
|
11
11
|
|
|
12
|
-
This package is the Angular binding only
|
|
12
|
+
This package is the Angular binding only: the components and the signal-backed
|
|
13
13
|
`KerusiStateStore`. The document types, the conformance validator, the render
|
|
14
14
|
model and its geometry live in its peer
|
|
15
15
|
[`@kerusiweb/core`](https://www.npmjs.com/package/@kerusiweb/core), which is
|
|
@@ -47,14 +47,14 @@ import { KerusiSeatmapComponent } from '@kerusiweb/angular';
|
|
|
47
47
|
|
|
48
48
|
**Selection**
|
|
49
49
|
|
|
50
|
-
| Input | Type | Default |
|
|
51
|
-
| -------------------- | ------------------------------- | --------------- |
|
|
52
|
-
| `selection` | `readonly string[]` | `[]` | Selected seat ids. Two-way
|
|
53
|
-
| `selectableStatuses` | `SeatRenderStatus[]` | `['available']` | Which statuses a seat may be picked in.
|
|
54
|
-
| `companionMode` | `'auto' \| 'independent'` | `'auto'` | `auto` selects a seat's whole companion closure together (§4.6).
|
|
55
|
-
| `maxSelection` | `number` | — | Cap on selected seats. Counts a companion closure as its full size.
|
|
56
|
-
| `seatSelectable` | `(seat: RenderSeat) => boolean` | — | A final say, applied after the status test.
|
|
57
|
-
| `interactive` | `boolean` | `true` | `false` renders read-only.
|
|
50
|
+
| Input | Type | Default | |
|
|
51
|
+
| -------------------- | ------------------------------- | --------------- | -------------------------------------------------------------------------- |
|
|
52
|
+
| `selection` | `readonly string[]` | `[]` | Selected seat ids. Two-way (`[(selection)]`) or read-only (`[selection]`). |
|
|
53
|
+
| `selectableStatuses` | `SeatRenderStatus[]` | `['available']` | Which statuses a seat may be picked in. |
|
|
54
|
+
| `companionMode` | `'auto' \| 'independent'` | `'auto'` | `auto` selects a seat's whole companion closure together (§4.6). |
|
|
55
|
+
| `maxSelection` | `number` | — | Cap on selected seats. Counts a companion closure as its full size. |
|
|
56
|
+
| `seatSelectable` | `(seat: RenderSeat) => boolean` | — | A final say, applied after the status test. |
|
|
57
|
+
| `interactive` | `boolean` | `true` | `false` renders read-only. |
|
|
58
58
|
|
|
59
59
|
**Appearance**
|
|
60
60
|
|
|
@@ -87,6 +87,16 @@ import { KerusiSeatmapComponent } from '@kerusiweb/angular';
|
|
|
87
87
|
| `expireHolds` | `boolean` | `false` | Revert a lapsed `holdExpires` to available on a ticker. |
|
|
88
88
|
| `expiryIntervalMs` | `number` | `1000` | |
|
|
89
89
|
|
|
90
|
+
**Zoom, pan and maximize** (both off by default; see [Zoom, pan and maximize](#zoom-pan-and-maximize))
|
|
91
|
+
|
|
92
|
+
| Input | Type | Default | |
|
|
93
|
+
| ------------------ | ------------------------------- | ------- | -------------------------------------------------------------------------------------- |
|
|
94
|
+
| `zoomable` | `boolean` | `false` | Pinch, Ctrl/Cmd + wheel and drag to zoom and pan; `+` `-` `0` keys; on-screen buttons. |
|
|
95
|
+
| `maxZoom` | `number` | `4` | The zoom ceiling, relative to fit. The floor is always fit. |
|
|
96
|
+
| `showZoomControls` | `boolean` | `true` | The built-in zoom buttons. Turn off to supply your own. |
|
|
97
|
+
| `maximizable` | `boolean` | `false` | A button that fills the screen with the map. |
|
|
98
|
+
| `controlStrings` | `Partial<KerusiControlStrings>` | English | The names of the zoom and maximize buttons, for translation. |
|
|
99
|
+
|
|
90
100
|
### Outputs
|
|
91
101
|
|
|
92
102
|
| Output | Payload | |
|
|
@@ -96,22 +106,24 @@ import { KerusiSeatmapComponent } from '@kerusiweb/angular';
|
|
|
96
106
|
| `seatDisallowed` | `SeatDisallowed` | `reason` is `booked`, `held`, `blocked`, `not-selectable`, `max-selection` or `companion-unavailable`. |
|
|
97
107
|
| `seatFocus` | `RenderSeat` | |
|
|
98
108
|
| `validationIssues` | `readonly KerusiViolation[]` | Emitted whenever the documents change. |
|
|
109
|
+
| `viewportChange` | `KerusiViewport` | `{ zoom, x, y }`, whenever the zoom or pan changes. Only with `zoomable`. |
|
|
110
|
+
| `maximizedChange` | `boolean` | When the map is maximized or restored. Only with `maximizable`. |
|
|
99
111
|
|
|
100
112
|
### The seat glyph
|
|
101
113
|
|
|
102
114
|
**Colour means seat type; shape means status.** A seat's fill is always its
|
|
103
|
-
`SeatType.color`, or the theme's `availableBg` when the type has none
|
|
115
|
+
`SeatType.color`, or the theme's `availableBg` when the type has none. It
|
|
104
116
|
does not change when the seat is held or booked. Overloading fill with both
|
|
105
117
|
type and status hid the type colour on exactly the seats a busy map has most
|
|
106
118
|
of; see the doc comment on `seatFill` for the full argument. `selected` is the
|
|
107
|
-
one
|
|
119
|
+
one exception: it still owns the seat's colour outright, so your own picks
|
|
108
120
|
are never ambiguous, and a purple fill on its own would in any case fail WCAG
|
|
109
|
-
1.4.1 (Use of Colour) for anyone who cannot distinguish it
|
|
121
|
+
1.4.1 (Use of Colour) for anyone who cannot distinguish it, hence the shape
|
|
110
122
|
cues below.
|
|
111
123
|
|
|
112
124
|
The seat body itself carries orientation: square-ish at the front, tapered at
|
|
113
125
|
the back, so which way a seat faces is legible from its outline alone.
|
|
114
|
-
`Seat.rotation` turns the whole group, and the taper turns with it
|
|
126
|
+
`Seat.rotation` turns the whole group, and the taper turns with it; no
|
|
115
127
|
separate frame is drawn on top. This is what makes a fanned lecture theatre or
|
|
116
128
|
a stadium stand read at a glance. The seat number counter-rotates and stays
|
|
117
129
|
upright regardless.
|
|
@@ -122,30 +134,29 @@ Status is read from two marks layered over the body, never from its colour:
|
|
|
122
134
|
| --------- | ---------------------------------------------- | ---------- | ---------------------------------------------------- |
|
|
123
135
|
| Available | type colour | — | — |
|
|
124
136
|
| Selected | `selectedBg` **frame** + `selectedFg` **core** | — | solid, tinted `selectedBg` |
|
|
125
|
-
| Held | type colour | `heldBg` | **hollow**, stroked `heldFg`
|
|
126
|
-
| Booked | type colour | `bookedBg` | solid, tinted `bookedFg`
|
|
137
|
+
| Held | type colour | `heldBg` | **hollow**, stroked `heldFg` (a hold is provisional) |
|
|
138
|
+
| Booked | type colour | `bookedBg` | solid, tinted `bookedFg` (settled, someone else's) |
|
|
127
139
|
| Blocked | `blockedBg` | — | — (withheld by the venue; no one is there) |
|
|
128
140
|
|
|
129
|
-
Solid vs. hollow is the
|
|
141
|
+
Solid vs. hollow is the main distinction (settled vs. still in progress), and
|
|
130
142
|
every figure leans with the seat rather than staying upright, so it doubles as
|
|
131
143
|
an orientation cue too.
|
|
132
144
|
|
|
133
|
-
Selection is the only state drawn from **two** tones rather than one
|
|
134
|
-
is deliberate. `selectedBg` frames the seat; `selectedFg` fills a core inside
|
|
145
|
+
Selection is the only state drawn from **two** tones rather than one. `selectedBg` frames the seat; `selectedFg` fills a core inside
|
|
135
146
|
that frame and is what the number and the figure are read against. Because the
|
|
136
147
|
treatment always holds a light tone and a dark one at once, one of the two
|
|
137
|
-
separates from the page whichever way you have themed it
|
|
148
|
+
separates from the page whichever way you have themed it, so the library never
|
|
138
149
|
has to detect a colour scheme, and there is no `prefers-color-scheme` default to
|
|
139
150
|
fight with your own. An earlier revision had this inverted, a near-white rim
|
|
140
151
|
around a mid-purple middle, which put the low-contrast tone on the outside
|
|
141
152
|
boundary: a selected seat dissolved into a light page and sank into a dark one.
|
|
142
153
|
|
|
143
|
-
Both tokens therefore carry
|
|
154
|
+
Both tokens therefore carry visual weight, so override them as a pair. Swapping
|
|
144
155
|
the two gives the inverse treatment, a light frame around a dark core, which works
|
|
145
156
|
just as well.
|
|
146
157
|
|
|
147
158
|
A theme that overrides `heldFg`/`bookedFg` owns the contrast of that mark
|
|
148
|
-
against whatever `SeatType.color` the document supplies
|
|
159
|
+
against whatever `SeatType.color` the document supplies. The library cannot
|
|
149
160
|
know the pairing in advance, since the figure tint is fixed while the type
|
|
150
161
|
colour underneath it is not. The shipped defaults are light marks over a
|
|
151
162
|
darkened wash, which reads across the tier colours in the demo fixtures.
|
|
@@ -154,7 +165,7 @@ The geometry is exported, if you want to draw a matching seat elsewhere:
|
|
|
154
165
|
`seatBodyPath`, `seatSelectedFrame`, `seatOccupantPath`, `seatOccupantStroke`.
|
|
155
166
|
(`seatRingStroke` is the old name for `seatSelectedFrame` and still resolves to
|
|
156
167
|
it.) A seat is always square, which makes `seatBodyPath`'s `inset` an exact
|
|
157
|
-
scaled copy about the centre
|
|
168
|
+
scaled copy about the centre. That is how the selected core is derived, and why
|
|
158
169
|
marks drawn to the core's box keep every clearance they had against the body.
|
|
159
170
|
|
|
160
171
|
### Theming
|
|
@@ -162,13 +173,13 @@ marks drawn to the core's box keep every clearance they had against the body.
|
|
|
162
173
|
Colors resolve through three tiers, highest first:
|
|
163
174
|
|
|
164
175
|
1. A `--kerusi-*` custom property in **your** stylesheet.
|
|
165
|
-
2. The **`[colors]` input
|
|
176
|
+
2. The **`[colors]` input**: a partial `KerusiSeatmapColors`, merged over the defaults.
|
|
166
177
|
3. The library default.
|
|
167
178
|
|
|
168
179
|
The library never writes a `--kerusi-*` property onto its own host, which is
|
|
169
180
|
what keeps that order true: every fill is emitted as
|
|
170
181
|
`var(--kerusi-selected-bg, <the resolved input value>)`. Use whichever tier
|
|
171
|
-
fits
|
|
182
|
+
fits: the input for values known at build time, CSS for anything that has to
|
|
172
183
|
respond to a media query or a theme class.
|
|
173
184
|
|
|
174
185
|
```css
|
|
@@ -199,15 +210,15 @@ Every key of `KerusiSeatmapColors` has a property, kebab-cased:
|
|
|
199
210
|
| `focusRing` | `--kerusi-focus-ring` |
|
|
200
211
|
| `backdrop` | `--kerusi-backdrop` |
|
|
201
212
|
|
|
202
|
-
A `SeatType.color` from the document is
|
|
213
|
+
A `SeatType.color` from the document is **not** themable, because it is
|
|
203
214
|
the map's own value under §4.7. Turn it off wholesale with
|
|
204
215
|
`[typeColors]="false"` if you want the theme to own every fill.
|
|
205
216
|
|
|
206
217
|
Overriding `selectedBg` does not cost you the selected cue: the core and the
|
|
207
|
-
figure are shape, not color. Do set `selectedFg` alongside it, though
|
|
218
|
+
figure are shape, not color. Do set `selectedFg` alongside it, though. It is the
|
|
208
219
|
core's fill, so it is most of a selected seat's area rather than just a label
|
|
209
220
|
tint, and the two are read against each other. `heldBg`/`bookedBg` are the wash
|
|
210
|
-
drawn over a taken seat's type colour, not the seat's own fill
|
|
221
|
+
drawn over a taken seat's type colour, not the seat's own fill. See
|
|
211
222
|
[The seat glyph](#the-seat-glyph) for the full state table.
|
|
212
223
|
|
|
213
224
|
If you need the pre-1.1 flat selected seat back, the core is its own node:
|
|
@@ -239,10 +250,82 @@ For anything the palette does not cover, each seat group carries class hooks:
|
|
|
239
250
|
Inside a seat: `.kerusi-seat__box`, `__wash`, `__core`, `__occupant`
|
|
240
251
|
(`__occupant--selected` / `--held` / `--booked`), `__label`, `__wheelchair`.
|
|
241
252
|
|
|
253
|
+
### Zoom, pan and maximize
|
|
254
|
+
|
|
255
|
+
Two features, both **off by default**: with them off the markup and behaviour are
|
|
256
|
+
exactly what they were. They are independent; turn on either or both.
|
|
257
|
+
|
|
258
|
+
On a phone a map of several hundred seats is drawn at fit-to-width, so a seat is
|
|
259
|
+
too small to tap. `[zoomable]="true"` lets the reader zoom in and pan; `[maximizable]="true"` adds a button
|
|
260
|
+
that fills the screen.
|
|
261
|
+
|
|
262
|
+
**Zoom and pan.**
|
|
263
|
+
|
|
264
|
+
- **Pinch** with two fingers on a touch screen. **Ctrl/Cmd + wheel**, or a
|
|
265
|
+
trackpad pinch (Safari's included), on desktop. A plain wheel is left alone, so
|
|
266
|
+
the page still scrolls.
|
|
267
|
+
- **Drag** with one finger or the primary mouse button to pan.
|
|
268
|
+
- **A tap still selects**, at any zoom level. A touch or click that moves less
|
|
269
|
+
than 8 CSS pixels is a tap, and reaches the seat exactly as it does without
|
|
270
|
+
`zoomable`. Anything that moves further is a drag, and a drag never selects the
|
|
271
|
+
seat under it.
|
|
272
|
+
- **Limits.** The floor is fit-to-container, the ceiling is `maxZoom` (default
|
|
273
|
+
`4`), and panning stays inside the map. A map smaller than its container, which
|
|
274
|
+
happens when it is maximized on a large screen, is centred.
|
|
275
|
+
- **Gestures and the page.** `touch-action` is `pan-y` at fit, so a one-finger
|
|
276
|
+
vertical swipe still scrolls the page while a pinch is the map's; once zoomed in
|
|
277
|
+
or maximized it is `none`. With `zoomable` off, no `touch-action` is set.
|
|
278
|
+
- **Reduced motion.** The short transition on the zoom buttons, the keys and
|
|
279
|
+
reset is dropped under `prefers-reduced-motion: reduce`. A finger or wheel moves
|
|
280
|
+
the map directly and never eases.
|
|
281
|
+
|
|
282
|
+
| Key | |
|
|
283
|
+
| --------------- | ------------------------------------------------------------ |
|
|
284
|
+
| `+` / `=`, `-` | Zoom in / out, from anywhere inside the map |
|
|
285
|
+
| `0` | Reset to fit |
|
|
286
|
+
| `←` `→` `↑` `↓` | Pan, when the zoomable viewport itself has focus (Tab to it) |
|
|
287
|
+
|
|
288
|
+
On a seat, the arrow keys keep their usual job of moving seat focus, and a seat
|
|
289
|
+
that takes keyboard focus is scrolled into view. Ctrl, Cmd and Alt combinations
|
|
290
|
+
are left to the browser, and keys typed into a field you put inside the map are
|
|
291
|
+
ignored.
|
|
292
|
+
|
|
293
|
+
**Controls.** The zoom buttons (zoom in, zoom out, reset to fit) are real
|
|
294
|
+
`<button>`s with accessible names. At a limit they set `aria-disabled` rather than
|
|
295
|
+
`disabled`, so keyboard focus is not thrown away mid-press. Their names come from
|
|
296
|
+
`controlStrings`, for translation. They use the theme's existing tokens
|
|
297
|
+
(`--kerusi-element-bg`, `--kerusi-element-fg`, `--kerusi-element-muted-fg`,
|
|
298
|
+
`--kerusi-focus-ring`), so there is nothing new to theme.
|
|
299
|
+
|
|
300
|
+
To put the controls somewhere else, turn the built-in ones off
|
|
301
|
+
(`[showZoomControls]="false"`) and drive the map yourself:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
readonly map = viewChild.required(KerusiSeatmapComponent);
|
|
305
|
+
// this.map().zoomIn(); this.map().zoomOut(); this.map().resetZoom(); this.map().toggleMaximized();
|
|
306
|
+
// this.map().viewport() -> { zoom, x, y }
|
|
307
|
+
// this.map().maximized() -> boolean
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`viewportChange` reports `{ zoom, x, y }` whenever the view changes, for a host that wants
|
|
311
|
+
to display or persist it. (Restoring a saved view is not supported.)
|
|
312
|
+
|
|
313
|
+
**Maximize.** The button fills the screen with the map and its legend, freezes the
|
|
314
|
+
page behind it, and swaps to an "Exit full screen" button. The layout is a fixed
|
|
315
|
+
overlay, which is all iPhone Safari offers; where the browser has the Fullscreen
|
|
316
|
+
API it is requested on top, and leaving it with the browser's own Esc leaves the
|
|
317
|
+
map's maximized state too. `Escape` from the map also leaves, except that on a
|
|
318
|
+
seat with a non-empty selection the first `Escape` still clears the selection, as
|
|
319
|
+
it always has. The backdrop is `--kerusi-backdrop`, or the page's own canvas colour
|
|
320
|
+
while that is transparent; `--kerusi-maximized-z-index` (default `1000`) sets the
|
|
321
|
+
stacking. It is not a modal dialog: focus is not trapped, and an ancestor with a
|
|
322
|
+
`transform` or `filter` would anchor the fixed layout to itself instead of the
|
|
323
|
+
screen.
|
|
324
|
+
|
|
242
325
|
### Keyboard
|
|
243
326
|
|
|
244
327
|
Tab moves between sections; each section keeps its own tab stop. Within a
|
|
245
|
-
section, arrow keys follow the §4.3.1 `col` order
|
|
328
|
+
section, arrow keys follow the §4.3.1 `col` order, so an aisle, which is a
|
|
246
329
|
column no seat occupies, is stepped across rather than into.
|
|
247
330
|
|
|
248
331
|
| Key | |
|
|
@@ -259,6 +342,8 @@ Each seat is a `role="button"` announcing its position, type, price, status,
|
|
|
259
342
|
every `Seat.accessibility` property and its attributes. A polite live region
|
|
260
343
|
reports the running selection and any disallowed reason.
|
|
261
344
|
|
|
345
|
+
With `zoomable` on there are a few more keys: see [Zoom, pan and maximize](#zoom-pan-and-maximize).
|
|
346
|
+
|
|
262
347
|
---
|
|
263
348
|
|
|
264
349
|
## `<kerusi-legend>`
|
|
@@ -270,7 +355,7 @@ Rendered inline by `[showLegend]="true"`, or placed anywhere yourself:
|
|
|
270
355
|
```
|
|
271
356
|
|
|
272
357
|
It resolves swatches through the same path as the seat fills, so the two cannot
|
|
273
|
-
drift apart. Every availability swatch
|
|
358
|
+
drift apart. Every availability swatch, not just **Selected**, draws the
|
|
274
359
|
seat glyph itself (wash, ring, occupant, as it applies) rather than a flat
|
|
275
360
|
colour, because shape is the cue the map uses for status.
|
|
276
361
|
|
|
@@ -278,7 +363,7 @@ colour, because shape is the cue the map uses for status.
|
|
|
278
363
|
|
|
279
364
|
## Working with the format directly
|
|
280
365
|
|
|
281
|
-
All of this lives in `@kerusiweb/core` and is pure
|
|
366
|
+
All of this lives in `@kerusiweb/core` and is pure (no Angular import), so it
|
|
282
367
|
can run in a test, a build step or on a server.
|
|
283
368
|
|
|
284
369
|
```ts
|
|
@@ -327,7 +412,7 @@ Deltas that are stale, duplicate or scoped to another session are discarded.
|
|
|
327
412
|
> **Gap detection needs a sequence.** §5.2 requires `updatedAt` to be strictly
|
|
328
413
|
> increasing but not contiguous, so it cannot by itself distinguish "a delta was
|
|
329
414
|
> lost" from "nothing happened for a while". The store detects gaps when the
|
|
330
|
-
> transport supplies a monotonic sequence
|
|
415
|
+
> transport supplies a monotonic sequence: `metadata.seq` by default, or your
|
|
331
416
|
> own `sequenceOf` reader. A gapped delta is still applied, so the map degrades
|
|
332
417
|
> rather than freezes while you re-fetch.
|
|
333
418
|
|