@kerusiweb/core 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.
Files changed (95) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -0
  3. package/dist/kerusi/index.d.ts +19 -0
  4. package/dist/kerusi/index.d.ts.map +1 -0
  5. package/dist/kerusi/index.js +19 -0
  6. package/dist/kerusi/index.js.map +1 -0
  7. package/dist/kerusi/kerusi-layout-mode.d.ts +58 -0
  8. package/dist/kerusi/kerusi-layout-mode.d.ts.map +1 -0
  9. package/dist/kerusi/kerusi-layout-mode.js +117 -0
  10. package/dist/kerusi/kerusi-layout-mode.js.map +1 -0
  11. package/dist/kerusi/kerusi-locale.d.ts +43 -0
  12. package/dist/kerusi/kerusi-locale.d.ts.map +1 -0
  13. package/dist/kerusi/kerusi-locale.js +111 -0
  14. package/dist/kerusi/kerusi-locale.js.map +1 -0
  15. package/dist/kerusi/kerusi-map.model.d.ts +258 -0
  16. package/dist/kerusi/kerusi-map.model.d.ts.map +1 -0
  17. package/dist/kerusi/kerusi-map.model.js +11 -0
  18. package/dist/kerusi/kerusi-map.model.js.map +1 -0
  19. package/dist/kerusi/kerusi-price.d.ts +22 -0
  20. package/dist/kerusi/kerusi-price.d.ts.map +1 -0
  21. package/dist/kerusi/kerusi-price.js +48 -0
  22. package/dist/kerusi/kerusi-price.js.map +1 -0
  23. package/dist/kerusi/kerusi-rows.d.ts +25 -0
  24. package/dist/kerusi/kerusi-rows.d.ts.map +1 -0
  25. package/dist/kerusi/kerusi-rows.js +36 -0
  26. package/dist/kerusi/kerusi-rows.js.map +1 -0
  27. package/dist/kerusi/kerusi-state.d.ts +85 -0
  28. package/dist/kerusi/kerusi-state.d.ts.map +1 -0
  29. package/dist/kerusi/kerusi-state.js +117 -0
  30. package/dist/kerusi/kerusi-state.js.map +1 -0
  31. package/dist/kerusi/kerusi-state.model.d.ts +79 -0
  32. package/dist/kerusi/kerusi-state.model.d.ts.map +1 -0
  33. package/dist/kerusi/kerusi-state.model.js +7 -0
  34. package/dist/kerusi/kerusi-state.model.js.map +1 -0
  35. package/dist/kerusi/kerusi-validator.d.ts +96 -0
  36. package/dist/kerusi/kerusi-validator.d.ts.map +1 -0
  37. package/dist/kerusi/kerusi-validator.js +525 -0
  38. package/dist/kerusi/kerusi-validator.js.map +1 -0
  39. package/dist/kerusi/kerusi-violation.d.ts +36 -0
  40. package/dist/kerusi/kerusi-violation.d.ts.map +1 -0
  41. package/dist/kerusi/kerusi-violation.js +13 -0
  42. package/dist/kerusi/kerusi-violation.js.map +1 -0
  43. package/dist/public-api.d.ts +4 -0
  44. package/dist/public-api.d.ts.map +1 -0
  45. package/dist/public-api.js +13 -0
  46. package/dist/public-api.js.map +1 -0
  47. package/dist/render/build-render-model.d.ts +36 -0
  48. package/dist/render/build-render-model.d.ts.map +1 -0
  49. package/dist/render/build-render-model.js +258 -0
  50. package/dist/render/build-render-model.js.map +1 -0
  51. package/dist/render/element-shapes.d.ts +34 -0
  52. package/dist/render/element-shapes.d.ts.map +1 -0
  53. package/dist/render/element-shapes.js +41 -0
  54. package/dist/render/element-shapes.js.map +1 -0
  55. package/dist/render/index.d.ts +14 -0
  56. package/dist/render/index.d.ts.map +1 -0
  57. package/dist/render/index.js +14 -0
  58. package/dist/render/index.js.map +1 -0
  59. package/dist/render/navigation-order.d.ts +49 -0
  60. package/dist/render/navigation-order.d.ts.map +1 -0
  61. package/dist/render/navigation-order.js +109 -0
  62. package/dist/render/navigation-order.js.map +1 -0
  63. package/dist/render/render-model.d.ts +151 -0
  64. package/dist/render/render-model.d.ts.map +1 -0
  65. package/dist/render/render-model.js +2 -0
  66. package/dist/render/render-model.js.map +1 -0
  67. package/dist/render/seat-shapes.d.ts +72 -0
  68. package/dist/render/seat-shapes.d.ts.map +1 -0
  69. package/dist/render/seat-shapes.js +161 -0
  70. package/dist/render/seat-shapes.js.map +1 -0
  71. package/dist/render/section-layout.d.ts +66 -0
  72. package/dist/render/section-layout.d.ts.map +1 -0
  73. package/dist/render/section-layout.js +173 -0
  74. package/dist/render/section-layout.js.map +1 -0
  75. package/dist/view/index.d.ts +12 -0
  76. package/dist/view/index.d.ts.map +1 -0
  77. package/dist/view/index.js +12 -0
  78. package/dist/view/index.js.map +1 -0
  79. package/dist/view/kerusi-seatmap-colors.d.ts +176 -0
  80. package/dist/view/kerusi-seatmap-colors.d.ts.map +1 -0
  81. package/dist/view/kerusi-seatmap-colors.js +215 -0
  82. package/dist/view/kerusi-seatmap-colors.js.map +1 -0
  83. package/dist/view/rtl.d.ts +17 -0
  84. package/dist/view/rtl.d.ts.map +1 -0
  85. package/dist/view/rtl.js +29 -0
  86. package/dist/view/rtl.js.map +1 -0
  87. package/dist/view/seat-aria.d.ts +44 -0
  88. package/dist/view/seat-aria.d.ts.map +1 -0
  89. package/dist/view/seat-aria.js +103 -0
  90. package/dist/view/seat-aria.js.map +1 -0
  91. package/dist/view/selection.d.ts +84 -0
  92. package/dist/view/selection.d.ts.map +1 -0
  93. package/dist/view/selection.js +98 -0
  94. package/dist/view/selection.js.map +1 -0
  95. package/package.json +42 -0
@@ -0,0 +1,103 @@
1
+ import { formatMoney } from '../kerusi/kerusi-locale.js';
2
+ export const DEFAULT_SEAT_ARIA_STRINGS = {
3
+ rowSeat: (row, seat) => `Row ${row}, seat ${seat}`,
4
+ seatOnly: (seat) => `Seat ${seat}`,
5
+ noPrice: 'no price',
6
+ available: 'available',
7
+ booked: 'booked, unavailable',
8
+ held: 'on hold, unavailable',
9
+ heldUntil: (time) => `on hold until ${time}, unavailable`,
10
+ blocked: 'blocked by the venue, unavailable',
11
+ wheelchairAccessible: 'wheelchair accessible',
12
+ transferArmrest: (side) => `transfer armrest ${side}`,
13
+ aisleChairCompatible: 'aisle chair compatible',
14
+ companionRequired: 'companion seat required',
15
+ selectionSummary: (count, total) => `${count} ${count === 1 ? 'seat' : 'seats'} selected${total ? `, ${total} total` : ''}`,
16
+ selectionEmpty: 'No seats selected',
17
+ disallowed: {
18
+ booked: 'is already booked',
19
+ held: 'is held by another customer',
20
+ blocked: 'is blocked by the venue',
21
+ 'not-selectable': 'cannot be selected',
22
+ 'max-selection': 'would exceed the seat limit',
23
+ 'companion-unavailable': 'must be booked with a seat that is unavailable',
24
+ },
25
+ };
26
+ /**
27
+ * Builds a seat's accessible name, in the order a listener needs it: where the
28
+ * seat is, what kind it is, what it costs, whether it can be had, what
29
+ * accessibility it offers, and finally its descriptive tags.
30
+ */
31
+ export function seatAriaLabel(seat, locale, strings = DEFAULT_SEAT_ARIA_STRINGS) {
32
+ const parts = [];
33
+ parts.push(seat.rowLabel ? strings.rowSeat(seat.rowLabel, seat.label) : strings.seatOnly(seat.label));
34
+ if (seat.typeLabel && seat.typeLabel !== seat.type.id) {
35
+ parts.push(seat.typeLabel);
36
+ }
37
+ else if (seat.type.id) {
38
+ parts.push(seat.type.id);
39
+ }
40
+ parts.push(seat.price ? formatMoney(seat.price, locale) : strings.noPrice);
41
+ parts.push(statusPhrase(seat, locale, strings));
42
+ parts.push(...accessibilityPhrases(seat, strings));
43
+ if (seat.attributes.length > 0) {
44
+ parts.push(seat.attributes.join(', '));
45
+ }
46
+ return parts.filter(Boolean).join(', ') + '.';
47
+ }
48
+ function statusPhrase(seat, locale, strings) {
49
+ switch (seat.status) {
50
+ case 'booked':
51
+ return strings.booked;
52
+ case 'blocked':
53
+ return strings.blocked;
54
+ case 'held':
55
+ return seat.holdExpires
56
+ ? strings.heldUntil(formatTime(seat.holdExpires, locale))
57
+ : strings.held;
58
+ default:
59
+ return strings.available;
60
+ }
61
+ }
62
+ /** Every §4.3.4 property the seat declares, as phrases. */
63
+ function accessibilityPhrases(seat, strings) {
64
+ const a11y = seat.accessibility;
65
+ if (!a11y) {
66
+ return [];
67
+ }
68
+ const phrases = [];
69
+ if (a11y.wheelchairAccessible) {
70
+ phrases.push(strings.wheelchairAccessible);
71
+ }
72
+ if (a11y.transferArmrest) {
73
+ phrases.push(strings.transferArmrest(a11y.transferArmrest));
74
+ }
75
+ if (a11y.aisleChairCompatible) {
76
+ phrases.push(strings.aisleChairCompatible);
77
+ }
78
+ if (a11y.companionRequired) {
79
+ phrases.push(strings.companionRequired);
80
+ }
81
+ return phrases;
82
+ }
83
+ /** A hold expiry as a local clock time; falls back to the raw value. */
84
+ export function formatTime(iso, locale) {
85
+ const date = new Date(iso);
86
+ if (Number.isNaN(date.getTime())) {
87
+ return iso;
88
+ }
89
+ try {
90
+ return new Intl.DateTimeFormat(locale, { timeStyle: 'short' }).format(date);
91
+ }
92
+ catch {
93
+ return date.toISOString();
94
+ }
95
+ }
96
+ /** The live-region announcement when a seat cannot be selected. */
97
+ export function disallowedAnnouncement(seat, reason, strings = DEFAULT_SEAT_ARIA_STRINGS) {
98
+ const where = seat.rowLabel
99
+ ? strings.rowSeat(seat.rowLabel, seat.label)
100
+ : strings.seatOnly(seat.label);
101
+ return `${where} ${strings.disallowed[reason]}.`;
102
+ }
103
+ //# sourceMappingURL=seat-aria.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-aria.js","sourceRoot":"","sources":["../../src/view/seat-aria.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { Money } from '../kerusi/kerusi-map.model.js';
2
+ import { RenderMap, RenderSeat } from '../render/render-model.js';
3
+ /**
4
+ * Seat selection, as a pure reducer over a set of seat ids.
5
+ *
6
+ * The pre-1.0 component mutated `SeatNode.selected` in place, which forced
7
+ * every caller to re-set its rows signal after each click just to make change
8
+ * detection notice. Here the selection is a plain `readonly string[]` the
9
+ * component owns as a `model()`, and this module never touches a `RenderSeat`.
10
+ */
11
+ /** Why a seat could not be picked. */
12
+ export type DisallowedReason =
13
+ /** The seat is sold. */
14
+ 'booked'
15
+ /** Someone else has it mid-checkout. */
16
+ | 'held'
17
+ /** The venue withheld it. */
18
+ | 'blocked'
19
+ /** Selectable statuses or the caller's predicate excluded it. */
20
+ | 'not-selectable'
21
+ /** Taking it would exceed `maxSelection`. */
22
+ | 'max-selection'
23
+ /** A seat it must be booked with is unavailable (§4.6). */
24
+ | 'companion-unavailable';
25
+ export type SelectionOutcomeKind = 'select' | 'deselect' | 'disallowed';
26
+ export interface SelectionOutcome {
27
+ kind: SelectionOutcomeKind;
28
+ /** The selection after the attempt — unchanged when disallowed. */
29
+ selection: readonly string[];
30
+ /** Set when `kind` is `"disallowed"`. */
31
+ reason?: DisallowedReason;
32
+ /**
33
+ * Every seat whose membership changed, including companions pulled in or
34
+ * dropped alongside the seat that was clicked.
35
+ */
36
+ changed: readonly string[];
37
+ }
38
+ export interface SelectionContext {
39
+ seatsById: ReadonlyMap<string, RenderSeat>;
40
+ /**
41
+ * `auto` (default) selects and deselects a seat's whole companion closure
42
+ * together; `independent` treats every seat separately, leaving the pairing
43
+ * rule to the application.
44
+ */
45
+ companionMode?: 'auto' | 'independent';
46
+ /** Cap on how many seats may be selected at once. */
47
+ maxSelection?: number;
48
+ }
49
+ /**
50
+ * Toggles a seat, returning the resulting selection.
51
+ *
52
+ * With `companionMode: 'auto'`, the unit of selection is the seat's *companion
53
+ * closure* — the transitive set of seats it must be booked with (§4.6). A love
54
+ * seat is picked and dropped as a pair, `maxSelection` counts the pair, and a
55
+ * pair whose other half is already sold cannot be picked at all.
56
+ */
57
+ export declare function toggleSeatSelection(selection: readonly string[], seatId: string, ctx: SelectionContext): SelectionOutcome;
58
+ /**
59
+ * Every seat transitively linked to this one by `companions`, including itself.
60
+ *
61
+ * §4.6 requires the relation to be symmetric, so a breadth-first walk closes
62
+ * over the whole bay — a three-seat family pod is one unit, not three pairs.
63
+ * An id that does not resolve is skipped rather than throwing; the validator is
64
+ * where a dangling companion is reported.
65
+ */
66
+ export declare function companionClosure(seatId: string, seatsById: ReadonlyMap<string, RenderSeat>): string[];
67
+ export interface SelectionSummary {
68
+ /** The selected seats, in the map's render order. */
69
+ seats: readonly RenderSeat[];
70
+ /** The total, when every selected seat is priced. */
71
+ total?: Money;
72
+ /** How many selected seats carry no price — a valid state (§4.9). */
73
+ unpriced: number;
74
+ }
75
+ /**
76
+ * Summarizes a selection: the seats, their total, and how many were unpriced.
77
+ *
78
+ * Exported instead of shipping a cart component — a cart carries far too many
79
+ * product opinions for a rendering library. The total respects the currency's
80
+ * minor-unit exponent via `formatMoney`; it is returned as `Money`, in minor
81
+ * units, so no precision is lost on the way out.
82
+ */
83
+ export declare function summarizeSelection(map: RenderMap, selection: readonly string[]): SelectionSummary;
84
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../src/view/selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAElE;;;;;;;GAOG;AAEH,sCAAsC;AACtC,MAAM,MAAM,gBAAgB;AAC1B,wBAAwB;AACtB,QAAQ;AACV,wCAAwC;GACtC,MAAM;AACR,6BAA6B;GAC3B,SAAS;AACX,iEAAiE;GAC/D,gBAAgB;AAClB,6CAA6C;GAC3C,eAAe;AACjB,2DAA2D;GACzD,uBAAuB,CAAC;AAE5B,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,mEAAmE;IACnE,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,yCAAyC;IACzC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;OAGG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC3C;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IACvC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,gBAAgB,GACpB,gBAAgB,CA4ClB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GACzC,MAAM,EAAE,CAeV;AAcD,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,qDAAqD;IACrD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAUjG"}
@@ -0,0 +1,98 @@
1
+ import { sumMoney } from '../kerusi/kerusi-locale.js';
2
+ /**
3
+ * Toggles a seat, returning the resulting selection.
4
+ *
5
+ * With `companionMode: 'auto'`, the unit of selection is the seat's *companion
6
+ * closure* — the transitive set of seats it must be booked with (§4.6). A love
7
+ * seat is picked and dropped as a pair, `maxSelection` counts the pair, and a
8
+ * pair whose other half is already sold cannot be picked at all.
9
+ */
10
+ export function toggleSeatSelection(selection, seatId, ctx) {
11
+ const seat = ctx.seatsById.get(seatId);
12
+ const unchanged = { kind: 'disallowed', selection, changed: [] };
13
+ if (!seat) {
14
+ return { ...unchanged, reason: 'not-selectable' };
15
+ }
16
+ const current = new Set(selection);
17
+ const group = ctx.companionMode === 'independent' ? [seat.id] : companionClosure(seat.id, ctx.seatsById);
18
+ // Deselecting never needs permission — drop the whole group.
19
+ if (current.has(seat.id)) {
20
+ const next = selection.filter((id) => !group.includes(id));
21
+ return {
22
+ kind: 'deselect',
23
+ selection: next,
24
+ changed: group.filter((id) => current.has(id)),
25
+ };
26
+ }
27
+ if (!seat.selectable) {
28
+ return { ...unchanged, reason: reasonFor(seat) };
29
+ }
30
+ // Every seat in the closure must be takeable, or none of them is.
31
+ const blocked = group
32
+ .map((id) => ctx.seatsById.get(id))
33
+ .find((member) => member && !member.selectable && !current.has(member.id));
34
+ if (blocked) {
35
+ return { ...unchanged, reason: 'companion-unavailable' };
36
+ }
37
+ const additions = group.filter((id) => !current.has(id));
38
+ if (ctx.maxSelection !== undefined && current.size + additions.length > ctx.maxSelection) {
39
+ return { ...unchanged, reason: 'max-selection' };
40
+ }
41
+ return {
42
+ kind: 'select',
43
+ selection: [...selection, ...additions],
44
+ changed: additions,
45
+ };
46
+ }
47
+ /**
48
+ * Every seat transitively linked to this one by `companions`, including itself.
49
+ *
50
+ * §4.6 requires the relation to be symmetric, so a breadth-first walk closes
51
+ * over the whole bay — a three-seat family pod is one unit, not three pairs.
52
+ * An id that does not resolve is skipped rather than throwing; the validator is
53
+ * where a dangling companion is reported.
54
+ */
55
+ export function companionClosure(seatId, seatsById) {
56
+ const seen = new Set([seatId]);
57
+ const queue = [seatId];
58
+ while (queue.length > 0) {
59
+ const current = seatsById.get(queue.shift());
60
+ for (const companionId of current?.companions ?? []) {
61
+ if (!seen.has(companionId) && seatsById.has(companionId)) {
62
+ seen.add(companionId);
63
+ queue.push(companionId);
64
+ }
65
+ }
66
+ }
67
+ return [...seen];
68
+ }
69
+ /** The reason an unselectable seat gives, preferring its availability status. */
70
+ function reasonFor(seat) {
71
+ switch (seat.status) {
72
+ case 'booked':
73
+ case 'held':
74
+ case 'blocked':
75
+ return seat.status;
76
+ default:
77
+ return 'not-selectable';
78
+ }
79
+ }
80
+ /**
81
+ * Summarizes a selection: the seats, their total, and how many were unpriced.
82
+ *
83
+ * Exported instead of shipping a cart component — a cart carries far too many
84
+ * product opinions for a rendering library. The total respects the currency's
85
+ * minor-unit exponent via `formatMoney`; it is returned as `Money`, in minor
86
+ * units, so no precision is lost on the way out.
87
+ */
88
+ export function summarizeSelection(map, selection) {
89
+ const selected = new Set(selection);
90
+ const seats = [...map.seatsById.values()].filter((seat) => selected.has(seat.id));
91
+ const prices = seats.map((s) => s.price).filter((p) => p !== undefined);
92
+ return {
93
+ seats,
94
+ total: sumMoney(prices),
95
+ unpriced: seats.length - prices.length,
96
+ };
97
+ }
98
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/view/selection.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kerusiweb/core",
3
+ "version": "2.0.0",
4
+ "description": "Framework-agnostic core for the Kerusi Seat Map & Availability Format: document types, a conformance validator, and the render model and geometry a seat-map renderer consumes.",
5
+ "keywords": [
6
+ "kerusi",
7
+ "seat-map",
8
+ "seatmap",
9
+ "seat-picker",
10
+ "seat-selection",
11
+ "booking",
12
+ "ticketing",
13
+ "cinema",
14
+ "airline",
15
+ "venue"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Arsyad \"Shad Ahm\" Ahmad",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/ShadAhm/kerusiweb.git",
22
+ "directory": "projects/core"
23
+ },
24
+ "homepage": "https://github.com/ShadAhm/kerusiweb",
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/public-api.d.ts",
30
+ "default": "./dist/public-api.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "types": "./dist/public-api.d.ts",
35
+ "main": "./dist/public-api.js",
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }