@seatlayer/core 0.53.0 → 0.55.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 (78) hide show
  1. package/README.md +2 -2
  2. package/dist/categoryInventory-BwarniiK.d.ts +109 -0
  3. package/dist/categoryInventory-D3qPwfKz.d.cts +109 -0
  4. package/dist/chunk-55NOSZK5.js +1 -0
  5. package/dist/chunk-6VTTBC5F.js +1 -0
  6. package/dist/chunk-6XJN4REG.js +1 -0
  7. package/dist/chunk-7PT7OQUO.js +1 -0
  8. package/dist/chunk-AQKYUJBX.js +1 -0
  9. package/dist/chunk-CXF32VPG.js +1 -0
  10. package/dist/chunk-DNGP2HDT.js +1 -0
  11. package/dist/chunk-EQM4FFN5.js +1 -0
  12. package/dist/chunk-HHM3PB66.js +1 -0
  13. package/dist/chunk-PXWKSYCK.js +1 -0
  14. package/dist/chunk-VDZ24QVV.js +1 -0
  15. package/dist/chunk-VI2CLV3Y.js +1 -0
  16. package/dist/core/chart-ops/index.cjs +1 -0
  17. package/dist/core/chart-ops/index.d.cts +1235 -0
  18. package/dist/core/chart-ops/index.d.ts +1235 -0
  19. package/dist/core/chart-ops/index.js +1 -0
  20. package/dist/core/color.cjs +1 -0
  21. package/dist/core/color.d.cts +192 -0
  22. package/dist/core/color.d.ts +192 -0
  23. package/dist/core/color.js +1 -0
  24. package/dist/core/inventory/index.cjs +1 -0
  25. package/dist/core/inventory/index.d.cts +209 -0
  26. package/dist/core/inventory/index.d.ts +209 -0
  27. package/dist/core/inventory/index.js +1 -0
  28. package/dist/core/layout/index.cjs +1 -0
  29. package/dist/core/layout/index.d.cts +376 -0
  30. package/dist/core/layout/index.d.ts +376 -0
  31. package/dist/core/layout/index.js +1 -0
  32. package/dist/core/quality/index.cjs +1 -0
  33. package/dist/core/quality/index.d.cts +132 -0
  34. package/dist/core/quality/index.d.ts +132 -0
  35. package/dist/core/quality/index.js +1 -0
  36. package/dist/core/render-assets/index.cjs +1 -0
  37. package/dist/core/render-assets/index.d.cts +99 -0
  38. package/dist/core/render-assets/index.d.ts +99 -0
  39. package/dist/core/render-assets/index.js +1 -0
  40. package/dist/core/seatConfidence.cjs +1 -1
  41. package/dist/core/seatConfidence.d.cts +1 -1
  42. package/dist/core/seatConfidence.d.ts +1 -1
  43. package/dist/core/seatConfidence.js +1 -1
  44. package/dist/core/sections/index.cjs +1 -0
  45. package/dist/core/sections/index.d.cts +346 -0
  46. package/dist/core/sections/index.d.ts +346 -0
  47. package/dist/core/sections/index.js +1 -0
  48. package/dist/{de-75NWRKC5.js → de-WPX4D3SY.js} +1 -1
  49. package/dist/{es-BM7EJ7PW.js → es-6KAJQVVX.js} +1 -1
  50. package/dist/{fr-HPXQHIB4.js → fr-5DTMRBN5.js} +1 -1
  51. package/dist/index.cjs +1 -1
  52. package/dist/index.d.cts +85 -534
  53. package/dist/index.d.ts +85 -534
  54. package/dist/index.js +1 -1
  55. package/dist/labeling-BmCzW50y.d.ts +90 -0
  56. package/dist/labeling-VfJBEOn0.d.cts +90 -0
  57. package/dist/layout-ClQkmixY.d.ts +181 -0
  58. package/dist/layout-Dlv5bIf2.d.cts +181 -0
  59. package/dist/renderedQuality-B3jgdgPO.d.ts +113 -0
  60. package/dist/renderedQuality-bDkUOyBL.d.cts +113 -0
  61. package/dist/renderer-B_g-W_QB.d.ts +534 -0
  62. package/dist/renderer-DySMcKAR.d.cts +534 -0
  63. package/dist/sections-DIzgtDjF.d.ts +123 -0
  64. package/dist/sections-Dna1nZgR.d.cts +123 -0
  65. package/dist/segmentedRowModel-BNXEUeHp.d.cts +75 -0
  66. package/dist/segmentedRowModel-COXw4GCh.d.ts +75 -0
  67. package/dist/ticketRestrictions-CzchbX_o.d.cts +72 -0
  68. package/dist/ticketRestrictions-DaYOCpEw.d.ts +72 -0
  69. package/dist/{types-tLOunI-B.d.cts → types-DTSG6_Hq.d.cts} +238 -761
  70. package/dist/{types-tLOunI-B.d.ts → types-DTSG6_Hq.d.ts} +238 -761
  71. package/dist/view3d/crossfade/panorama.d.cts +1 -1
  72. package/dist/view3d/crossfade/panorama.d.ts +1 -1
  73. package/dist/view3d/index.cjs +27 -27
  74. package/dist/view3d/index.d.cts +109 -2
  75. package/dist/view3d/index.d.ts +109 -2
  76. package/dist/view3d/index.js +26 -26
  77. package/package.json +94 -5
  78. package/dist/chunk-54A7LIL5.js +0 -1
@@ -0,0 +1,376 @@
1
+ import { P as Point, r as CubicPath, s as TextObject } from '../../types-DTSG6_Hq.js';
2
+ export { E as ExpandChartOptions, R as RowSeatSlot, T as TableSeatSlot, a as allObjects, c as chartBounds, e as expandBooth, b as expandChart, d as expandRow, f as expandRowSlots, g as expandTable, h as expandTableSlots, i as floorObjects, j as floorOverviewTranslations, k as floorsOf, l as layoutFloorOverview, o as objectCenter, m as owningSectionForObject, p as pointInPolygon, n as pointInPolygonWithHoles, q as polygonLabelPoint, r as rowInventoryCount, s as rowSeatPositions, t as stackFloors, u as tableInventoryCount, v as tableSeatCountsBySide, w as translateObject } from '../../layout-ClQkmixY.js';
3
+ export { a as SegmentedRowComponent, S as SegmentedRowPlacement, r as resolveSegmentedRowGroups, s as segmentedRowComponentSeats, b as segmentedRowComponents, c as segmentedRowSeatTotal } from '../../segmentedRowModel-COXw4GCh.js';
4
+ import { E as ExpandedSeat } from '../../renderer-B_g-W_QB.js';
5
+
6
+ type SectionShellMode = 'auto' | 'rectangle' | 'tapered' | 'bezier';
7
+ declare function cubicPoint(path: CubicPath, t: number): Point;
8
+ /**
9
+ * Build a predictable single-bend cubic whose midpoint passes exactly through
10
+ * `middle`. At t=.5 a cubic weighs both controls by 3/8, so moving both
11
+ * controls together by 4/3 of the requested midpoint offset makes the visible
12
+ * curve pass through the point the author dragged to.
13
+ *
14
+ * Lives here rather than in the Designer because two features need the same
15
+ * answer: the row bend handle (`renderRowPathHandles`) and the row curvature
16
+ * operations in `controller/rowOpsFacet`.
17
+ */
18
+ declare function smoothCubicThroughMiddle(start: Point, end: Point, middle: Point): CubicPath;
19
+ /** Equal-arc-length points on a cubic path, including both endpoints. */
20
+ declare function distributeAlongCubic(path: CubicPath, count: number, resolution?: number): Point[];
21
+ /**
22
+ * Convert a server-mapped rectangle into an editable complex shell. The four
23
+ * corners must be clockwise. No caller-supplied control points are accepted.
24
+ */
25
+ declare function buildSectionShell(corners: Point[], focal: Point, requested: SectionShellMode): {
26
+ mode: Exclude<SectionShellMode, 'auto'>;
27
+ outline: Point[];
28
+ };
29
+ /** Server-owned straight aisle cutouts spanning the near/far edges. */
30
+ declare function buildAisleHoles(corners: Point[], focal: Point, count: number, mode: Exclude<SectionShellMode, 'auto'>): Point[][];
31
+
32
+ /** Axis-aligned chart bounds used to derive a stable, document-relative camera. */
33
+ interface PerspectiveBounds {
34
+ x: number;
35
+ y: number;
36
+ width: number;
37
+ height: number;
38
+ }
39
+ /**
40
+ * A deterministic pinhole camera for the projected 2.5D buyer/Designer views.
41
+ * Coordinates stay in chart units so the ordinary Stage camera (pan/zoom) can
42
+ * remain the only screen-space transform.
43
+ */
44
+ interface PerspectiveCamera {
45
+ target: Point;
46
+ /** Horizontal distance from target to camera, in chart units. */
47
+ distance: number;
48
+ /** Camera height above the chart datum, in chart units. */
49
+ height: number;
50
+ /** Pinhole focal length, in chart units. */
51
+ focalLength: number;
52
+ /** Small plan rotation which keeps the venue from reading as a flat elevation. */
53
+ yawRad: number;
54
+ }
55
+ /** Canvas/Konva affine: x' = a·x + c·y + e; y' = b·x + d·y + f. */
56
+ interface Affine2D {
57
+ a: number;
58
+ b: number;
59
+ c: number;
60
+ d: number;
61
+ e: number;
62
+ f: number;
63
+ }
64
+ interface PerspectivePoint extends Point {
65
+ /** Positive camera-space distance; larger values are farther away. */
66
+ depth: number;
67
+ /** Perspective size multiplier at this point (1 at the camera target). */
68
+ scale: number;
69
+ }
70
+ /**
71
+ * Choose a camera from venue bounds rather than fixed pixels. The camera rises
72
+ * above the tallest authored surface, so even the (deliberately generous)
73
+ * 120-m validation ceiling cannot cross the near plane.
74
+ */
75
+ declare function createPerspectiveCamera(bounds: PerspectiveBounds, maxSurfaceHeightWorld?: number): PerspectiveCamera;
76
+ /** Project one 3D chart point through the pinhole camera. */
77
+ declare function projectPerspectivePoint(camera: PerspectiveCamera, point: Point, heightWorld?: number): PerspectivePoint;
78
+ declare function applyAffine(affine: Affine2D, point: Point): Point;
79
+ declare function invertAffine(affine: Affine2D): Affine2D;
80
+ /** Return `left(right(point))`. */
81
+ declare function composeAffine(left: Affine2D, right: Affine2D): Affine2D;
82
+ /**
83
+ * First-order projection around one object/section anchor. The height callback
84
+ * may describe a raked surface; sampling one chart unit along x/y captures its
85
+ * local slope. Per-section tangents are the intentional shell-performance
86
+ * contract. Buyer seat anchors are projected exactly by `projectPerspectivePoint`;
87
+ * only non-interactive section surfaces use this bounded approximation.
88
+ */
89
+ declare function perspectiveTangentAffine(camera: PerspectiveCamera, anchor: Point, heightAt?: (point: Point) => number): Affine2D;
90
+ /** Parameters accepted by Konva's rotation/scale/skew decomposition. */
91
+ declare function decomposeAffineLinear(affine: Affine2D): {
92
+ rotationDeg: number;
93
+ scaleX: number;
94
+ scaleY: number;
95
+ skewX: number;
96
+ };
97
+
98
+ /**
99
+ * Where a label belongs inside a polygon.
100
+ *
101
+ * A polygon's average-of-vertices centroid is not a label anchor. Two things
102
+ * break it on real venue geometry:
103
+ *
104
+ * - Concavity. An L-shaped concourse block, a C-shaped bowl tier, or a wedge
105
+ * with an aisle cut out can have its centroid land in the notch — outside
106
+ * the shape entirely.
107
+ * - Vertex density. A traced contour samples a curved outer arc with dozens of
108
+ * points and the two straight radial edges with two. The average is dragged
109
+ * onto the arc, so the label sits hard against the boundary and its pill
110
+ * spills into the neighbouring section.
111
+ *
112
+ * The right anchor is the *pole of inaccessibility*: the interior point
113
+ * furthest from every edge — the centre of the largest circle that fits inside
114
+ * the polygon. That is the fattest part of the shape, which is exactly where a
115
+ * label has room. This is the Mapbox `polylabel` algorithm: subdivide the
116
+ * bounding box into cells, keep the cell whose optimistic upper bound is best,
117
+ * and stop once no cell can beat the incumbent by more than `precision`.
118
+ *
119
+ * The returned `radius` is as useful as the point: it is the half-width of the
120
+ * room available, so a caller can shrink a label to fit — or decide it never
121
+ * will — without measuring glyphs.
122
+ */
123
+ interface InscribedAnchor {
124
+ /** Interior point furthest from every ring (outer boundary and holes). */
125
+ point: Point;
126
+ /** Distance from `point` to the nearest edge — the inscribed circle radius. */
127
+ radius: number;
128
+ }
129
+ /** Distance from `p` to the closest edge of any ring (unsigned). */
130
+ declare function distanceToRings(p: Point, rings: readonly (readonly Point[])[]): number;
131
+ /** Area of a ring (absolute, shoelace). */
132
+ declare function polygonArea(ring: readonly Point[]): number;
133
+ /** Net area of a polygon with holes — the space a section actually occupies. */
134
+ declare function polygonNetArea(outer: readonly Point[], holes?: readonly (readonly Point[])[]): number;
135
+ /**
136
+ * Largest-inscribed-circle anchor for a polygon (with optional holes).
137
+ *
138
+ * Deterministic: no randomness, no dependence on vertex order or winding, so
139
+ * the same geometry always produces the same anchor. Callers can therefore
140
+ * memoize on geometry alone.
141
+ */
142
+ declare function polygonInscribedAnchor(outer: readonly Point[], holes?: readonly (readonly Point[])[]): InscribedAnchor;
143
+
144
+ /**
145
+ * Pure-TypeScript spatial index for seat hit-testing.
146
+ *
147
+ * WHY THIS EXISTS
148
+ * ---------------
149
+ * `src/engine/SeatmapRenderer.ts` currently leans on Konva's built-in hit graph
150
+ * (the `listening` property, ~58 call sites) for "which seat did the pointer
151
+ * land on". No other renderer we care about ships an equivalent: React Native
152
+ * Skia has nothing at all, and Flutter / SwiftUI / Compose canvases all expect
153
+ * the app to own hit-testing. So the index has to exist on every path — and on
154
+ * the web it additionally lets us drop Konva's hit graph, which is a real win at
155
+ * 5k–13k seats (no per-seat hit-canvas rasterisation, no hit-graph traversal).
156
+ *
157
+ * This module is deliberately DEPENDENCY-FREE: no DOM, no Konva, no browser
158
+ * globals. It runs unchanged in a Web Worker, in workerd, and under React
159
+ * Native. All coordinates are WORLD space — converting screen→world is the
160
+ * caller's job, exactly as `SeatmapRenderer.screenToWorld` does today.
161
+ *
162
+ * STRUCTURE: uniform grid, not a quadtree
163
+ * ---------------------------------------
164
+ * Seats in a venue chart are close to uniformly dense by construction — they sit
165
+ * in rows on a near-regular pitch. That is the single case a uniform grid is
166
+ * optimal for, and it beats a quadtree on every axis that matters here:
167
+ * O(n) build with no rebalancing, flat typed-array storage (one contiguous
168
+ * scan per query, no pointer chasing), and O(1) expected candidates per point
169
+ * query. A quadtree only starts to pay when density varies by orders of
170
+ * magnitude across the extent, which a seating chart does not do. If a future
171
+ * chart shape ever breaks that assumption, `gridStats()` exposes the occupancy
172
+ * numbers needed to prove it before reaching for a tree.
173
+ *
174
+ * Each seat is inserted into EVERY cell its axis-aligned bounding box overlaps
175
+ * (not just the cell holding its centre). That is what makes {@link hitTest}
176
+ * examine exactly one cell: any seat whose shape contains a point also has an
177
+ * AABB containing that point, so it is guaranteed to be registered in that
178
+ * point's cell.
179
+ */
180
+
181
+ /**
182
+ * Default seat radius in world units. Mirrors `SEAT_RADIUS` in
183
+ * `SeatmapRenderer.ts` and `DesignerController.ts` (both 9). The renderer scales
184
+ * this by `theme.seatScale` clamped to [0.7, 1.6]; callers that do the same must
185
+ * pass the scaled value via {@link BuildOptions.seatRadius} so the index agrees
186
+ * with what is actually drawn.
187
+ */
188
+ declare const DEFAULT_SEAT_RADIUS = 9;
189
+ /**
190
+ * Hit geometry for one seat. Real seats are circles; trade-show booths render as
191
+ * rotated rounded rects whose dimensions live on the booth object in the
192
+ * ChartDoc, not on `ExpandedSeat` — so the caller supplies them through
193
+ * {@link BuildOptions.shapeOf} rather than this module reaching for the doc.
194
+ */
195
+ type SeatShape = {
196
+ kind: 'circle';
197
+ r: number;
198
+ } | {
199
+ kind: 'rect';
200
+ width: number;
201
+ height: number;
202
+ rotation?: number;
203
+ };
204
+ interface BuildOptions {
205
+ /** Radius for circular seats when {@link shapeOf} is absent. Default {@link DEFAULT_SEAT_RADIUS}. */
206
+ seatRadius?: number;
207
+ /**
208
+ * Per-seat hit geometry. Return `null`/`undefined` to fall back to a circle of
209
+ * `seatRadius`. Called exactly once per seat during construction.
210
+ */
211
+ shapeOf?: (seat: ExpandedSeat) => SeatShape | null | undefined;
212
+ /**
213
+ * Upper bound on grid cells, to stop a chart with one far-flung stray object
214
+ * from allocating a huge sparse grid. Default `max(64, 4 * seats.length)`.
215
+ * When the natural cell size would exceed this, cells are grown until it fits.
216
+ */
217
+ maxCells?: number;
218
+ }
219
+ interface QueryOptions {
220
+ /**
221
+ * Restrict results to seats this predicate accepts — used to reproduce the
222
+ * renderer's `isSelectable(id) || selection.has(id)` gate. Kept as a query
223
+ * argument rather than build state on purpose: seat statuses change on every
224
+ * websocket tick, and the geometry does not.
225
+ */
226
+ filter?: (id: string, seat: ExpandedSeat) => boolean;
227
+ }
228
+ interface Rect {
229
+ x: number;
230
+ y: number;
231
+ width: number;
232
+ height: number;
233
+ }
234
+ interface RectQueryOptions extends QueryOptions {
235
+ /**
236
+ * `'center'` (default) keeps a seat when its CENTRE falls inside the rect,
237
+ * bounds inclusive. This is what `SeatmapRenderer.finishMarquee` does today,
238
+ * so it is the default in order to be a drop-in replacement.
239
+ *
240
+ * `'overlap'` keeps a seat when its hit SHAPE intersects the rect at all —
241
+ * the right mode for "which seats are on screen" viewport culling and
242
+ * label-rendering passes, where a partially visible seat still counts.
243
+ */
244
+ mode?: 'center' | 'overlap';
245
+ }
246
+ interface IndexedSeat {
247
+ seat: ExpandedSeat;
248
+ shape: SeatShape;
249
+ /** Half-extents of the shape's axis-aligned bounding box (rotation applied). */
250
+ hx: number;
251
+ hy: number;
252
+ }
253
+ interface SeatIndex {
254
+ readonly seats: readonly ExpandedSeat[];
255
+ /** @internal */
256
+ readonly entries: readonly IndexedSeat[];
257
+ /** @internal */
258
+ readonly minX: number;
259
+ /** @internal */
260
+ readonly minY: number;
261
+ /** @internal */
262
+ readonly cell: number;
263
+ /** @internal */
264
+ readonly cols: number;
265
+ /** @internal */
266
+ readonly rows: number;
267
+ /** @internal Prefix-sum offsets into {@link items}; length cols*rows+1. */
268
+ readonly cellStart: Int32Array;
269
+ /** @internal Seat indices grouped by cell. */
270
+ readonly items: Int32Array;
271
+ /** @internal Per-seat visit stamps, for dedup across multi-cell scans. */
272
+ readonly stamp: Int32Array;
273
+ /** @internal Monotonically increasing stamp epoch. */
274
+ epoch: number;
275
+ }
276
+ /**
277
+ * Build a uniform-grid index over `seats`. O(n) in time and memory.
278
+ *
279
+ * CELL SIZE
280
+ * ---------
281
+ * The nominal cell is `4 × maxHalfExtent`, i.e. twice the largest seat's
282
+ * bounding-box width. Two properties fall out of that choice:
283
+ *
284
+ * - A seat's AABB is at most half a cell across, so it lands in at most 2×2 = 4
285
+ * cells. Build stays O(n) with a small constant, and the index does not blow
286
+ * up in memory from duplicated references.
287
+ * - For the default 9px seat radius the cell is 36 world units, versus a real
288
+ * row/seat pitch of roughly 20–26. That works out to ~1–2 seats per occupied
289
+ * cell, so a point query examines about one or two candidates. Halving the
290
+ * cell would quadruple cell count to shave a fraction of a candidate;
291
+ * doubling it would put ~6 seats in every cell. 4× the half-extent is the
292
+ * flat part of that curve.
293
+ *
294
+ * The one pathological input is a chart whose bounds are enormous relative to
295
+ * its seat count — a single stray object parked far from the venue. `maxCells`
296
+ * caps the grid and grows the cell size until it fits, trading query sharpness
297
+ * for bounded memory. Real charts never hit it.
298
+ */
299
+ declare function buildSeatIndex(seats: readonly ExpandedSeat[], opts?: BuildOptions): SeatIndex;
300
+ /**
301
+ * The seat whose hit shape contains `p`, or null.
302
+ *
303
+ * On overlap — seats drawn close enough that their circles intersect — the seat
304
+ * whose CENTRE is nearest to `p` wins. That matches what a user perceives (you
305
+ * hit the thing you aimed at) and, unlike Konva, is order-independent: Konva's
306
+ * hit graph resolves overlap by draw order, returning the topmost/last-added
307
+ * node regardless of how far off-centre the pointer was.
308
+ */
309
+ declare function hitTest(index: SeatIndex, p: Point, opts?: QueryOptions): string | null;
310
+ /**
311
+ * Nearest seat CENTRE strictly within `radius` world units of `p`, or null.
312
+ *
313
+ * This is the near-miss rescue from `SeatmapRenderer.nearestSeatToScreen`,
314
+ * lifted verbatim. The caller computes the radius the same way the renderer
315
+ * does today:
316
+ *
317
+ * const reachWorld = seatR + slopPx / stageScale; // SEAT_TAP_SLOP_PX = 14
318
+ * nearestWithin(index, screenToWorld(pointer), reachWorld, { filter });
319
+ *
320
+ * Note the comparison is STRICTLY less-than, mirroring the renderer's
321
+ * `bestD = reachWorld; ... if (d < bestD)` loop: a seat centre sitting exactly
322
+ * `reachWorld` away does not qualify. Distance is measured to the seat centre,
323
+ * not the shape edge — so for the default circle, "centre within seatR + slop"
324
+ * is identical to "edge within slop", which is what the renderer's docstring
325
+ * describes.
326
+ */
327
+ declare function nearestWithin(index: SeatIndex, p: Point, radius: number, opts?: QueryOptions): string | null;
328
+ /**
329
+ * Seat ids inside a world-space rect. Negative width/height are normalised, so a
330
+ * marquee dragged up-and-left works without the caller sorting the corners.
331
+ *
332
+ * Default `mode: 'center'` reproduces `SeatmapRenderer.finishMarquee` exactly:
333
+ * a seat is kept when its centre is within the rect, bounds INCLUSIVE. Use
334
+ * `mode: 'overlap'` for viewport culling, where a seat straddling the edge
335
+ * should still be drawn.
336
+ *
337
+ * Results come back in ascending seat-array order, so the output is stable
338
+ * across calls regardless of grid layout.
339
+ */
340
+ declare function queryRect(index: SeatIndex, rect: Rect, opts?: RectQueryOptions): string[];
341
+ /**
342
+ * Occupancy stats for the built grid. Diagnostic only — this is the evidence to
343
+ * check before anyone argues the uniform grid has stopped being good enough and
344
+ * a tree is warranted.
345
+ */
346
+ declare function gridStats(index: SeatIndex): {
347
+ cell: number;
348
+ cols: number;
349
+ rows: number;
350
+ cells: number;
351
+ occupiedCells: number;
352
+ refs: number;
353
+ maxPerCell: number;
354
+ meanPerOccupiedCell: number;
355
+ };
356
+
357
+ declare const TEXT_WIDTH_MIN = 40;
358
+ declare const TEXT_WIDTH_MAX = 1200;
359
+ declare const TEXT_LINE_HEIGHT_MIN = 0.8;
360
+ declare const TEXT_LINE_HEIGHT_MAX = 3;
361
+ declare const TEXT_LETTER_SPACING_MIN = -2;
362
+ declare const TEXT_LETTER_SPACING_MAX = 20;
363
+ declare const TEXT_OPACITY_MIN = 0.1;
364
+ declare const TEXT_BACKGROUND_PADDING_MAX = 40;
365
+ declare const TEXT_BACKGROUND_RADIUS_MAX = 40;
366
+ interface TextLayoutMetrics {
367
+ width: number;
368
+ height: number;
369
+ lineCount: number;
370
+ padding: number;
371
+ }
372
+ /** Conservative renderer-independent box used by chart bounds, quality and
373
+ * collision checks. Konva remains authoritative for exact glyph metrics. */
374
+ declare function textLayoutMetrics(text: TextObject): TextLayoutMetrics;
375
+
376
+ export { type Affine2D, type BuildOptions, DEFAULT_SEAT_RADIUS, type InscribedAnchor, type PerspectiveBounds, type PerspectiveCamera, type PerspectivePoint, type QueryOptions, type Rect, type RectQueryOptions, type SeatIndex, type SeatShape, type SectionShellMode, TEXT_BACKGROUND_PADDING_MAX, TEXT_BACKGROUND_RADIUS_MAX, TEXT_LETTER_SPACING_MAX, TEXT_LETTER_SPACING_MIN, TEXT_LINE_HEIGHT_MAX, TEXT_LINE_HEIGHT_MIN, TEXT_OPACITY_MIN, TEXT_WIDTH_MAX, TEXT_WIDTH_MIN, type TextLayoutMetrics, applyAffine, buildAisleHoles, buildSeatIndex, buildSectionShell, composeAffine, createPerspectiveCamera, cubicPoint, decomposeAffineLinear, distanceToRings, distributeAlongCubic, gridStats, hitTest, invertAffine, nearestWithin, perspectiveTangentAffine, polygonArea, polygonInscribedAnchor, polygonNetArea, projectPerspectivePoint, queryRect, smoothCubicThroughMiddle, textLayoutMetrics };
@@ -0,0 +1 @@
1
+ import{a as U,b as V,c as W,d as X,e as Y,f as Z,g as _,h as $,i as oo,j as ro,k as eo,l as fo,m as mo}from"../../chunk-6VTTBC5F.js";import{Aa as O,Ba as P,Ca as Q,Da as R,Ea as S,Fa as T,I as q,J as s,K as u,L as v,a as o,b as r,c as e,d as f,e as m,f as p,g as t,h as x,i as a,j as b,ja as w,ka as y,la as z,ma as A,n as c,na as B,o as d,oa as C,p as g,pa as D,q as h,qa as E,r as i,ra as F,s as j,sa as G,t as k,ta as H,u as l,ua as I,v as n,va as J,wa as K,xa as L,ya as M,za as N}from"../../chunk-VDZ24QVV.js";export{U as DEFAULT_SEAT_RADIUS,x as TEXT_BACKGROUND_PADDING_MAX,a as TEXT_BACKGROUND_RADIUS_MAX,p as TEXT_LETTER_SPACING_MAX,m as TEXT_LETTER_SPACING_MIN,f as TEXT_LINE_HEIGHT_MAX,e as TEXT_LINE_HEIGHT_MIN,t as TEXT_OPACITY_MIN,r as TEXT_WIDTH_MAX,o as TEXT_WIDTH_MIN,N as allObjects,oo as applyAffine,i as buildAisleHoles,V as buildSeatIndex,h as buildSectionShell,T as chartBounds,eo as composeAffine,_ as createPerspectiveCamera,c as cubicPoint,mo as decomposeAffineLinear,j as distanceToRings,g as distributeAlongCubic,F as expandBooth,S as expandChart,A as expandRow,z as expandRowSlots,E as expandTable,C as expandTableSlots,M as floorObjects,R as floorOverviewTranslations,L as floorsOf,Z as gridStats,W as hitTest,ro as invertAffine,Q as layoutFloorOverview,X as nearestWithin,J as objectCenter,K as owningSectionForObject,fo as perspectiveTangentAffine,G as pointInPolygon,H as pointInPolygonWithHoles,k as polygonArea,n as polygonInscribedAnchor,I as polygonLabelPoint,l as polygonNetArea,$ as projectPerspectivePoint,Y as queryRect,v as resolveSegmentedRowGroups,y as rowInventoryCount,w as rowSeatPositions,s as segmentedRowComponentSeats,q as segmentedRowComponents,u as segmentedRowSeatTotal,d as smoothCubicThroughMiddle,P as stackFloors,D as tableInventoryCount,B as tableSeatCountsBySide,b as textLayoutMetrics,O as translateObject};
@@ -0,0 +1 @@
1
+ "use strict";var V=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var Le=(t,n)=>{for(var o in n)V(t,o,{get:n[o],enumerable:!0})},ye=(t,n,o,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let a of fe(n))!he.call(t,a)&&a!==o&&V(t,a,{get:()=>n[a],enumerable:!(s=pe(n,a))||s.enumerable});return t};var xe=t=>ye(V({},"__esModule",{value:!0}),t);var Ge={};Le(Ge,{ACCESS_GLYPH_PATH:()=>Ee,ACCESS_GLYPH_VIEWBOX:()=>Ce,ACCESS_TYPE_GLYPH_PATHS:()=>K,AUTHORED_LABEL_MIN_CONTRAST:()=>Ne,BOOTH_LABEL_FONT_SIZE:()=>Ie,DARK_BOOKABLE_LABEL_INK:()=>S,DARK_NEUTRAL_SECTION_FILL:()=>ae,GA_CAPACITY_LABEL_FONT_SIZE:()=>Se,GA_FILL_OPACITY:()=>we,GA_LABEL_FONT_SIZE:()=>ke,LARGE_TEXT_CONTRAST:()=>re,LIGHT_BOOKABLE_LABEL_INK:()=>w,LIGHT_NEUTRAL_SECTION_FILL:()=>se,MIN_VISIBLE_BOOKABLE_LABEL_PX:()=>T,RENDERED_QUALITY_REPORT_VERSION:()=>ue,SEAT_LABEL_FONT_SIZE:()=>Re,SMALL_TEXT_CONTRAST:()=>P,accessGlyphInk:()=>Oe,accessGlyphPath:()=>_e,bookableMarkerLabel:()=>Fe,compositeHexOver:()=>Pe,inspectRenderedQualityEvidence:()=>Ve,isBookableLabelLegibleAtScale:()=>Be,isLightFill:()=>oe,minimumTextContrast:()=>Me,neutralSectionFill:()=>He,renderedTextContrast:()=>d,stateAwareBookableLabelInk:()=>ie,summarizeLabelInkContrast:()=>Ze});module.exports=xe(Ge);var ve=/^#?([\da-f]{3}|[\da-f]{6})$/i;function x(t){let n=ve.exec((t??"").trim());if(!n)return null;let o=n[1].length===3?n[1].split("").map(a=>a+a).join(""):n[1],s=parseInt(o,16);return[s>>16&255,s>>8&255,s&255]}function G(t){let n=t/255;return n<=.04045?n/12.92:((n+.055)/1.055)**2.4}function F(t){return .2126*G(t[0])+.7152*G(t[1])+.0722*G(t[2])}function te(t,n){let o=x(t),s=x(n);if(!o||!s)return null;let a=F(o),u=F(s);return(Math.max(a,u)+.05)/(Math.min(a,u)+.05)}var Re=7,Ie=10,ke=15,Se=11,we=.85,T=12,P=4.5,re=3,S="#000000",w="#ffffff",Te=24,Ae=18.67;function Me(t,n=400){return t>=Te||n>=700&&t>=Ae?re:P}function Be(t,n){return t*n>=T}var Ce=24,K={wheelchair:"M7.8 4.5a2.2 2.2 0 1 0 4.4 0a2.2 2.2 0 1 0 -4.4 0zM8 7L10.5 7L10.5 15L8 15ZM8 12.5L18 12.5L18 15L8 15ZM16 15L18 15L18 18L16 18ZM5.5 16a5.5 5.5 0 1 0 11 0a5.5 5.5 0 1 0 -11 0zM8 16a3 3 0 1 1 6 0a3 3 0 1 1 -6 0z",companion:"M5.5 6.5a3 3 0 1 0 6 0a3 3 0 1 0 -6 0zM4.5 18.5L6 11.5L11 11.5L12.5 18.5ZM12.5 7.5a3 3 0 1 0 6 0a3 3 0 1 0 -6 0zM11.5 19L13 12.5L18 12.5L19.5 19Z","semi-ambulatory":"M8.3 3.8a2.2 2.2 0 1 0 4.4 0a2.2 2.2 0 1 0 -4.4 0zM9 6.2L12 6.6L11 13.5L8.6 13L9 6.2ZM8.6 13L11 13L9.5 21L7 21ZM10.4 13L12.6 13L13.6 21L11.2 21ZM11.2 7.2L14 8L14.6 9.6L11.9 8.9ZM14.4 6.8L15.9 6.8L16.4 21L14.8 21Z","designated-aisle":"M3 5L6 5L6 14L3 14ZM3 12L13 12L13 15L3 15ZM5 15L7 15L7 20L5 20ZM11 15L13 15L13 20L11 20ZM13 7L20 7L17 4L19 4L23 8.5L19 13L17 13L20 10L13 10Z","step-free":"M4 4.5a2.2 2.2 0 1 0 4.4 0a2.2 2.2 0 1 0 -4.4 0zM4.4 7L7.5 7L7.5 13L11 15L9.5 17L5 14L4.4 7ZM2 19L8 19L17 10L22 10L22 13L18 13L10 21L2 21Z",hearing:"M15.5 3.5C9 2.2 5.5 6 5.5 11.5C5.5 16 8 19.5 10.5 21C13.2 22.6 16.4 20.8 15.4 18C14.8 16.4 12.9 16.1 13 14C13.1 11.9 15.8 11.4 15.8 8.5C15.8 5.4 13 4 15.5 3.5Z",cart:"M5.5 6L18.5 6a2.5 2.5 0 0 1 2.5 2.5L21 15.5a2.5 2.5 0 0 1 -2.5 2.5L11 18L7 20.5L7.5 18L5.5 18a2.5 2.5 0 0 1 -2.5 -2.5L3 8.5a2.5 2.5 0 0 1 2.5 -2.5zM6.5 11L17.5 11L17.5 12.6L6.5 12.6zM6.5 14L17.5 14L17.5 15.6L6.5 15.6z","sign-language":"M7 11L17 11L17 18a2 2 0 0 1 -2 2L9 20a2 2 0 0 1 -2 -2zM7.6 5L9.4 5L9.4 12L7.6 12zM9.9 3.5L11.7 3.5L11.7 12L9.9 12zM12.3 3.5L14.1 3.5L14.1 12L12.3 12zM14.6 5L16.4 5L16.4 12L14.6 12zM7.2 12.5L5 10.2L6.4 8.9L8.6 11.2Z","low-vision":"M2.5 12C6 6 18 6 21.5 12C18 18 6 18 2.5 12ZM9 12a3 3 0 1 0 6 0a3 3 0 1 0 -6 0zM17.5 3L19 3L19 6L17.5 6ZM19 4L22 4L22 5.5L19 5.5Z","sensory-friendly":"M4 11C4 5 7 2.5 12 2.5S20 5 20 11L17 11C17 7 15.5 5.5 12 5.5S7 7 7 11ZM3 10L8 10L8 19L5 19a2 2 0 0 1 -2 -2ZM16 10L21 10L21 17a2 2 0 0 1 -2 2L16 19ZM9 12L15 12L15 14L9 14ZM10 16L14 16L14 18L10 18Z","plus-size":"M9.4 4.3a2.6 2.6 0 1 0 5.2 0a2.6 2.6 0 1 0 -5.2 0zM5 20L4 13C4 10.5 7 9.5 12 9.5C17 9.5 20 10.5 20 13L19 20Z","lift-armrest":"M5 6L8 6L8 19L5 19zM5 16L18 16L18 19L5 19zM8 12L17 12L17 14.5L8 14.5zM14 7L16 7L16 12L14 12zM12 7L18 7L15 3Z"},Ee=K.wheelchair;function _e(t){if(!t?.length)return null;let n=t.includes("wheelchair")?"wheelchair":t[0];return K[n]??null}function Fe(t){return/-(\d{1,5})$/.exec(t)?.[1]??t}var d=te;function Pe(t,n,o){let s=x(t),a=x(n);if(!s||!a)return n;let u=Math.max(0,Math.min(1,o));return`#${s.map((p,A)=>Math.round(p*u+a[A]*(1-u))).map(p=>p.toString(16).padStart(2,"0")).join("")}`}var Ne=3;function ie(t,n,o=P){let s=d(n,t);if(s!=null&&s>=o)return n;let a=d(S,t)??0,u=d(w,t)??0;return a===0&&u===0?n:a>=u?S:w}var ze=3;function Oe(t){let n=d("#ffffff",t);if(n!=null&&n>=ze)return"#ffffff";let o=d(S,t)??0,s=d(w,t)??0;return o===0&&s===0?"#ffffff":o>=s?S:w}var ne=/^#[0-9a-f]{6}$/i;function oe(t){let n=x(t);return n!=null&&F(n)>.5}var se="#e5e7eb",ae="#273142";function He(t){return oe(t)?se:ae}function Ze(t,n,o=P){let s=ne.test(t.trim()),a=t.trim().toLowerCase(),u=0,m=0,p=null;for(let M of n){if(!s||!ne.test(M.trim()))continue;u+=1;let B=ie(M,t,o);B.toLowerCase()!==a&&(m+=1,p=B)}return{total:u,overridden:m,status:u===0?"none":m===0?"applies":m===u?"overridden":"mixed",overrideInk:p}}var ue=3,v=4.5,N=3,le=24,Qe=20;function $(t){return t.filter(n=>n.visible&&!!n.screenBox)}function R(t,n){return t.x<n.x+n.width&&t.x+t.width>n.x&&t.y<n.y+n.height&&t.y+t.height>n.y}function g(t){return t.length?Math.round(Math.min(...t)*100)/100:null}function c(t,n,o){return o.length?{code:t,message:n,count:o.length,samples:o.slice(0,Qe)}:null}function z(t,n,o){return{primaryId:t.seatId,primaryLabel:t.label,...n==null?{}:{measured:Math.round(n*100)/100},...o==null?{}:{minimum:o}}}function U(t,n,o){return{primaryId:t.id,primaryLabel:t.label,...n==null?{}:{measured:Math.round(n*100)/100},...o==null?{}:{minimum:o}}}function ce(t,n,o){return{primaryId:t.objectId,primaryLabel:t.text,...n==null?{}:{measured:Math.round(n*100)/100},...o==null?{}:{minimum:o}}}function I(t,n,o,s){return{primaryId:t,primaryLabel:n,secondaryId:o,secondaryLabel:s}}function Ve(t,n="overview",o=null){let s=$(t.labels),a=$(t.hierarchyLabels),u=$(t.freeTextLabels),m=t.gaAreas.filter(e=>e.visible),p=s.map(e=>d(e.ink,e.fill)??0),A=a.map(e=>d(e.ink,e.fill)??0),M=u.map(e=>d(e.ink,e.background)??0),B=m.map(e=>d(e.effectiveBackground,t.canvasBackground)??0),b=o?t.labels.filter(e=>e.categoryKey===o):t.labels,h=o?t.gaAreas.filter(e=>e.categoryKey===o):t.gaAreas,f=b.filter(e=>e.selected),L=b.filter(e=>e.status==="held"),y=b.filter(e=>e.status==="booked"),C=b.filter(e=>e.pointerTarget.active),j=new Set([...t.labels.filter(e=>e.visible&&(e.opacity>.25||e.selected||e.status!=="free")).map(e=>e.categoryKey),...t.gaAreas.filter(e=>e.visible&&e.opacity>.1).map(e=>e.categoryKey)]),E=f.length?g(f.flatMap(e=>[d(t.selectionRingColor,t.canvasBackground)??0,d(t.selectionRingColor,e.fill)??0])):null,l=[],k=(e,r)=>Array.from({length:e},(O,_)=>({primaryId:`overview:${_+1}`,primaryLabel:r}));if(n==="overview"&&t.rung==="sections"&&(l.push(c("overview-section-category-paint","Section overview shells must use the neutral hierarchy palette, not category paint",k(t.overviewStyle.categoryPaintedSectionShells,"category-painted section shell"))),l.push(c("overview-category-detail-visible","Category-tinted section detail must wait until section focus or seat zoom",k(t.overviewStyle.visibleCategoryDetailOutlines,"category detail outline"))),l.push(c("overview-row-hints-visible","Row and seat patterns must not clutter the section overview",k(t.overviewStyle.visibleSectionRowHints,"section row hint"))),l.push(c("overview-availability-clutter","Live availability counts belong in focused detail, not on section overview shells",k(t.overviewStyle.visibleSectionAvailabilityLabels,"section availability label"))),l.push(c("overview-ga-detail-visible","Section-contained standing paint belongs in focused detail, not on section overview shells",k(t.overviewStyle.visibleSectionGADetails,"section-contained GA detail")))),l.push(c("bookable-label-undersized","Visible bookable labels must meet the rendered small-text size floor",s.filter(e=>e.renderedFontPx<t.minimumVisibleLabelPx).map(e=>z(e,e.renderedFontPx,t.minimumVisibleLabelPx)))),n==="interaction"){let e=b.length>0,r=h.length>0,O=e||r,_=h.filter(i=>i.visible),de=new Set([...b.flatMap(i=>i.sectionId?[i.sectionId]:[]),...h.flatMap(i=>i.sectionId?[i.sectionId]:[])]),be=new Set([...t.labels.map(i=>i.categoryKey),...t.gaAreas.map(i=>i.categoryKey)]),H=i=>i.screenCenter.x>=0&&i.screenCenter.x<=t.viewport.width&&i.screenCenter.y>=0&&i.screenCenter.y<=t.viewport.height;l.push(c("detail-rung-missing","Interaction evidence with bookable inventory must render the seat-detail rung",O&&t.rung!=="seats"?[{primaryId:"renderer",primaryLabel:t.rung}]:[])),l.push(c("detail-inventory-not-visible","Interaction evidence must frame at least one real bookable unit",O&&!b.some(H)&&!_.length?[{primaryId:"renderer",primaryLabel:"no target inventory in viewport"}]:[])),l.push(c("pointer-target-inactive","Interaction evidence must expose a live production pointer target",e&&!C.some(H)||!e&&r&&!_.some(i=>i.interactive)?[{primaryId:"renderer",primaryLabel:"no active pointer target in viewport"}]:[])),l.push(c("pointer-target-undersized","Every active production pointer target must reach at least 24 CSS pixels",C.filter(i=>H(i)&&i.pointerTarget.effectiveMinimumPx<le).map(i=>z(i,i.pointerTarget.effectiveMinimumPx,le)))),l.push(c("selected-state-missing","Interaction evidence must paint a selected unit and its renderer-owned ring",e&&(!f.length||!f.some(i=>t.selectionRingSeatIds.includes(i.seatId)))?[{primaryId:"renderer",primaryLabel:"selected state"}]:[])),l.push(c("selected-state-contrast-low","The selected-state ring must maintain 3:1 graphical contrast with the canvas",f.length&&(E??0)<N?[{primaryId:f[0].seatId,primaryLabel:f[0].label,measured:Math.round((E??0)*100)/100,minimum:N}]:[])),l.push(c("held-state-missing","Interaction evidence must paint a held state when the floor has at least two status-managed units",b.length>=2&&!L.length?[{primaryId:"renderer",primaryLabel:"held state"}]:[])),l.push(c("booked-state-missing","Interaction evidence must paint a taken state when the floor has at least three status-managed units",b.length>=3&&!y.length?[{primaryId:"renderer",primaryLabel:"booked state"}]:[]));let me=new Set(L.map(i=>`${i.fill.toLowerCase()}:${i.opacity}`)),ge=new Set(y.map(i=>`${i.fill.toLowerCase()}:${i.opacity}`));l.push(c("status-state-indistinct","Held and taken evidence must resolve to distinct renderer paint",L.length&&y.length&&[...me].some(i=>ge.has(i))?[{primaryId:L[0].seatId,primaryLabel:L[0].label,secondaryId:y[0].seatId,secondaryLabel:y[0].label}]:[])),l.push(c("section-focus-missing","Interaction evidence must exercise section focus and its backdrop when section membership exists",de.size&&(!t.focusedSectionId||!t.focusBackdropVisible)?[{primaryId:"renderer",primaryLabel:"section focus"}]:[])),l.push(c("category-filter-missing","Interaction evidence must exercise a category filter when multiple categories exist",be.size>=2&&(!t.categoryFilterKeys||!t.categoryFilterKeys.length)?[{primaryId:"renderer",primaryLabel:"category filter"}]:[]));let Z=t.labels.filter(i=>i.status==="free"&&!i.selected&&t.categoryFilterKeys!=null&&!t.categoryFilterKeys.includes(i.categoryKey)),Q=t.gaAreas.filter(i=>t.categoryFilterKeys!=null&&!t.categoryFilterKeys.includes(i.categoryKey));l.push(c("category-filter-ineffective","The active category filter must visibly dim excluded free inventory",(Z.length||Q.length)&&!Z.some(i=>i.opacity<=.25)&&!Q.some(i=>i.opacity<=.1)?[...Z.map(i=>z(i,i.opacity)),...Q.map(i=>({primaryId:i.areaId,primaryLabel:i.label,measured:i.opacity}))]:[])),l.push(c("target-category-not-visible","A category-specific interaction scene must visibly paint its exact target category",o&&!j.has(o)?[{primaryId:o,primaryLabel:o}]:[])),l.push(c("target-category-filter-mismatch","A category-specific interaction scene must bind its filter to only the exact target category",o&&(t.categoryFilterKeys?.length!==1||t.categoryFilterKeys[0]!==o)?[{primaryId:o,primaryLabel:o}]:[]))}l.push(c("bookable-label-contrast-low","Visible bookable labels must meet 4.5:1 contrast against their actual paint",s.flatMap(e=>{let r=d(e.ink,e.fill)??0;return r<v?[z(e,r,v)]:[]})));let X=[];for(let e=0;e<s.length;e+=1)for(let r=0;r<e;r+=1)R(s[e].screenBox,s[r].screenBox)&&X.push(I(s[r].seatId,s[r].label,s[e].seatId,s[e].label));l.push(c("bookable-label-collision","Visible bookable labels must not overlap",X)),l.push(c("hierarchy-label-undersized","Visible section and zone labels must meet the rendered small-text size floor",a.filter(e=>e.renderedFontPx<T).map(e=>U(e,e.renderedFontPx,T)))),l.push(c("hierarchy-label-contrast-low","Visible section and zone labels must meet 4.5:1 contrast against their backing paint",a.flatMap(e=>{let r=d(e.ink,e.fill)??0;return r<v?[U(e,r,v)]:[]}))),l.push(c("hierarchy-label-outside-section","Visible section labels must remain inside the filled section surface and outside holes",a.filter(e=>e.kind==="section"&&e.fitsContainer===!1).map(e=>U(e))));let D=[];for(let e=0;e<a.length;e+=1)for(let r=0;r<e;r+=1)R(a[e].screenBox,a[r].screenBox)&&D.push(I(a[r].id,a[r].label,a[e].id,a[e].label));l.push(c("hierarchy-label-collision","Visible hierarchy labels must not overlap each other",D));let Y=[];for(let e of a)for(let r of s)R(e.screenBox,r.screenBox)&&Y.push(I(e.id,e.label,r.seatId,r.label));l.push(c("hierarchy-bookable-collision","Visible hierarchy labels must not overlap bookable labels",Y)),l.push(c("free-text-undersized","Visible chart text must meet the rendered small-text size floor",u.filter(e=>e.renderedFontPx<t.minimumVisibleLabelPx).map(e=>ce(e,e.renderedFontPx,t.minimumVisibleLabelPx)))),l.push(c("free-text-contrast-low","Visible chart text must meet 4.5:1 contrast against its measured background",u.flatMap(e=>{let r=d(e.ink,e.background)??0;return r<v?[ce(e,r,v)]:[]})));let W=[];for(let e=0;e<u.length;e+=1)for(let r=0;r<e;r+=1)R(u[e].screenBox,u[r].screenBox)&&W.push(I(u[r].objectId,u[r].text,u[e].objectId,u[e].text));l.push(c("free-text-collision","Visible chart text labels must not overlap each other",W));let J=[];for(let e of u)for(let r of s)R(e.screenBox,r.screenBox)&&J.push(I(e.objectId,e.text,r.seatId,r.label));l.push(c("free-text-bookable-collision","Visible chart text must not overlap bookable labels",J));let q=[];for(let e of u)for(let r of a)R(e.screenBox,r.screenBox)&&q.push(I(e.objectId,e.text,r.id,r.label));l.push(c("free-text-hierarchy-collision","Visible chart text must not overlap hierarchy labels",q)),l.push(c("ga-contrast-low","Visible GA surfaces must maintain 3:1 graphical contrast with the canvas",m.flatMap(e=>{let r=d(e.effectiveBackground,t.canvasBackground)??0;return r<N?[{primaryId:e.areaId,primaryLabel:e.label,measured:Math.round(r*100)/100,minimum:N}]:[]})));let ee=l.filter(e=>!!e);return{version:ue,passed:ee.length===0,state:n,targetCategoryKey:o,resolvedRules:["rendered-overview-label-size","rendered-overview-label-contrast","rendered-overview-label-collision","rendered-overview-hierarchy-containment","rendered-overview-section-first-style","rendered-overview-ga-contrast",...n==="interaction"?["rendered-detail-inventory","rendered-pointer-target","rendered-selected-held-taken-states","rendered-section-focus","rendered-category-filter"]:[]],viewport:t.viewport,canvasBackground:t.canvasBackground,effectiveScale:t.effectiveScale,rung:t.rung,inventory:{totalBookableUnits:t.totalBookableUnits,totalLabelledBookableUnits:t.totalLabelledBookableUnits,visibleBookableLabels:s.length,hiddenBookableLabels:t.hiddenLabels,visibleHierarchyLabels:a.length,visibleFreeTextLabels:u.length,visibleGAAreas:m.length},overviewStyle:t.overviewStyle,composition:{hierarchy:t.hierarchyLabels.filter(e=>e.role==="name").map(e=>({id:e.id,kind:e.kind,label:e.label,visible:e.visible})).sort((e,r)=>e.kind.localeCompare(r.kind)||e.id.localeCompare(r.id)),labelledObjects:t.freeTextLabels.map(e=>({objectId:e.objectId,kind:e.kind,text:e.text,visible:e.visible})).sort((e,r)=>e.objectId.localeCompare(r.objectId)||e.kind.localeCompare(r.kind)),gaAreas:t.gaAreas.map(e=>({areaId:e.areaId,label:e.label,categoryKey:e.categoryKey,...e.sectionId?{sectionId:e.sectionId}:{},visible:e.visible})).sort((e,r)=>e.areaId.localeCompare(r.areaId)),bookableSectionIds:[...new Set(t.labels.flatMap(e=>e.sectionId?[e.sectionId]:[]))].sort(),categoryKeys:[...new Set([...t.labels.map(e=>e.categoryKey),...t.gaAreas.map(e=>e.categoryKey)])].sort(),activeCategoryKeys:[...j].sort()},metrics:{minimumRenderedBookableLabelPx:g(s.map(e=>e.renderedFontPx)),minimumBookableLabelContrast:g(p),minimumRenderedHierarchyLabelPx:g(a.map(e=>e.renderedFontPx)),minimumHierarchyLabelContrast:g(A),minimumRenderedFreeTextPx:g(u.map(e=>e.renderedFontPx)),minimumFreeTextContrast:g(M),minimumGAContrast:g(B),minimumEffectivePointerTargetPx:g(C.map(e=>e.pointerTarget.effectiveMinimumPx)),selectedRingContrast:E==null?null:Math.round(E*100)/100},interaction:{applicable:{detail:b.length>0||h.length>0,pointer:b.length>0||h.length>0,held:b.length>=2,booked:b.length>=3,sectionFocus:b.some(e=>!!e.sectionId)||h.some(e=>!!e.sectionId),categoryFilter:new Set([...t.labels.map(e=>e.categoryKey),...t.gaAreas.map(e=>e.categoryKey)]).size>=2},selectedUnits:f.length,heldUnits:L.length,bookedUnits:y.length,activePointerTargets:C.length,focusedSectionId:t.focusedSectionId,focusBackdropVisible:t.focusBackdropVisible,categoryFilterKeys:t.categoryFilterKeys},findings:ee}}0&&(module.exports={ACCESS_GLYPH_PATH,ACCESS_GLYPH_VIEWBOX,ACCESS_TYPE_GLYPH_PATHS,AUTHORED_LABEL_MIN_CONTRAST,BOOTH_LABEL_FONT_SIZE,DARK_BOOKABLE_LABEL_INK,DARK_NEUTRAL_SECTION_FILL,GA_CAPACITY_LABEL_FONT_SIZE,GA_FILL_OPACITY,GA_LABEL_FONT_SIZE,LARGE_TEXT_CONTRAST,LIGHT_BOOKABLE_LABEL_INK,LIGHT_NEUTRAL_SECTION_FILL,MIN_VISIBLE_BOOKABLE_LABEL_PX,RENDERED_QUALITY_REPORT_VERSION,SEAT_LABEL_FONT_SIZE,SMALL_TEXT_CONTRAST,accessGlyphInk,accessGlyphPath,bookableMarkerLabel,compositeHexOver,inspectRenderedQualityEvidence,isBookableLabelLegibleAtScale,isLightFill,minimumTextContrast,neutralSectionFill,renderedTextContrast,stateAwareBookableLabelInk,summarizeLabelInkContrast});
@@ -0,0 +1,132 @@
1
+ import { A as AccessibilityType } from '../../types-DTSG6_Hq.cjs';
2
+ import { contrastRatioOrNull } from '../color.cjs';
3
+ export { R as RENDERED_QUALITY_REPORT_VERSION, a as RenderedEvidenceState, b as RenderedQualityFinding, c as RenderedQualityFindingCode, d as RenderedQualityReport, e as RenderedQualitySample, i as inspectRenderedQualityEvidence } from '../../renderedQuality-bDkUOyBL.cjs';
4
+ import '../../renderer-DySMcKAR.cjs';
5
+
6
+ /**
7
+ * Rendering constants that quality inspection must agree with.
8
+ *
9
+ * Keep these in core rather than inside the Konva renderer so headless catalog,
10
+ * API, and MCP checks can evaluate the text that the buyer/designer will see.
11
+ */
12
+
13
+ /** Seat and table-seat labels are authored at this many chart units. */
14
+ declare const SEAT_LABEL_FONT_SIZE = 7;
15
+ /** Booth labels are authored at this many chart units. */
16
+ declare const BOOTH_LABEL_FONT_SIZE = 10;
17
+ /** GA area captions and their capacity sublabels. */
18
+ declare const GA_LABEL_FONT_SIZE = 15;
19
+ declare const GA_CAPACITY_LABEL_FONT_SIZE = 11;
20
+ /**
21
+ * GA category paint remains slightly translucent, but must still separate from
22
+ * both supported canvas surfaces by the shared 3:1 graphical-object floor.
23
+ */
24
+ declare const GA_FILL_OPACITY = 0.85;
25
+ /**
26
+ * Small labels below this rendered CSS-pixel size are hidden by LOD instead of
27
+ * being painted as unreadable specks.
28
+ */
29
+ declare const MIN_VISIBLE_BOOKABLE_LABEL_PX = 12;
30
+ /** WCAG contrast for normal/small text. */
31
+ declare const SMALL_TEXT_CONTRAST = 4.5;
32
+ /** WCAG contrast for genuinely large text. */
33
+ declare const LARGE_TEXT_CONTRAST = 3;
34
+ /** Dark/light label candidates used only when a transient rendered state
35
+ * changes the authored category fill (selected, held, unavailable, and so on). */
36
+ declare const DARK_BOOKABLE_LABEL_INK = "#000000";
37
+ declare const LIGHT_BOOKABLE_LABEL_INK = "#ffffff";
38
+ declare function minimumTextContrast(renderedFontPx: number, fontWeight?: number): number;
39
+ declare function isBookableLabelLegibleAtScale(fontSize: number, effectiveScale: number): boolean;
40
+ /** Square viewBox every accessibility glyph path's coordinates live in. */
41
+ declare const ACCESS_GLYPH_VIEWBOX = 24;
42
+ /**
43
+ * Per-accommodation pictograms drawn centred on accessible seats. Each is a
44
+ * SOLID filled silhouette in the 0 0 24 24 viewBox, designed to stay legible at
45
+ * the ~13px it renders on a seat dot (bold shapes, no thin strokes). A vector
46
+ * path renders crisply at small sizes on every platform, unlike a colour emoji
47
+ * whose canvas metrics/alignment are inconsistent cross-browser.
48
+ *
49
+ * The owner directive (OV-89): every accessibility type must render ITS OWN
50
+ * meaningful glyph so a buyer *sees* what the seat provides — the coloured ring
51
+ * still encodes the type by colour, but the on-seat icon now matches the
52
+ * filter's meaning instead of every non-wheelchair type showing a bare ring.
53
+ *
54
+ * Every path below is numerically validated (bbox centred within 12±1.5, longer
55
+ * axis spans 14–22 units) — see scratchpad/validate-glyphs.mjs in the authoring
56
+ * session. Keep any edit passing that check.
57
+ */
58
+ declare const ACCESS_TYPE_GLYPH_PATHS: Record<AccessibilityType, string>;
59
+ /**
60
+ * Legacy default access pictogram — now the ISO-style wheelchair symbol. Kept as
61
+ * an alias of {@link ACCESS_TYPE_GLYPH_PATHS.wheelchair} for back-compat with
62
+ * callers that drew a single wheelchair glyph.
63
+ */
64
+ declare const ACCESS_GLYPH_PATH: string;
65
+ /**
66
+ * The glyph to draw for a seat's PRIMARY accommodation. Wheelchair wins whenever
67
+ * present (its physical provision is the headline fact); otherwise the first
68
+ * listed accommodation is shown. Returns null for a seat with no accommodations,
69
+ * so callers can skip drawing a glyph.
70
+ */
71
+ declare function accessGlyphPath(accessibility: AccessibilityType[]): string | null;
72
+ /**
73
+ * Keep the public seat label as the inventory identity while rendering only
74
+ * its terminal seat number inside the physical marker. Generated row seats use
75
+ * `<row label>-<number>`; a custom label without that suffix remains unchanged.
76
+ */
77
+ declare function bookableMarkerLabel(publicLabel: string): string;
78
+ /** WCAG contrast for the actual opaque hex paints used by bookable labels. */
79
+ declare const renderedTextContrast: typeof contrastRatioOrNull;
80
+ /**
81
+ * Resolve the opaque pixel under text painted over a translucent category
82
+ * shape. Quality evidence must compare against this composite—not the raw
83
+ * category colour or the canvas alone.
84
+ */
85
+ declare function compositeHexOver(foreground: string, background: string, opacity: number): string;
86
+ /**
87
+ * Bookable fills can vary per category and per transient state, while a chart
88
+ * exposes only one preferred label token. Preserve that preference whenever it
89
+ * passes, then deterministically choose the strongest normal-text candidate.
90
+ */
91
+ /**
92
+ * Acceptance bar for AUTHORED seat-number / row-label colors (the theme
93
+ * swatches). These are short bold marks whose information is duplicated in
94
+ * tooltips/chips, so the WCAG non-text 3:1 bar applies — under the 4.5:1
95
+ * small-text bar a normal mid-tone category palette rejects EVERY authored
96
+ * color (each seat silently falls back to black/white and the control feels
97
+ * broken; owner-found 07-23). Auto-swap still guards anything below 3:1.
98
+ */
99
+ declare const AUTHORED_LABEL_MIN_CONTRAST = 3;
100
+ declare function stateAwareBookableLabelInk(fill: string, preferred: string, minContrast?: number): string;
101
+ declare function accessGlyphInk(fill: string): string;
102
+ /** Is a solid hex fill light enough to read as a light surface? Feedback helpers
103
+ * use this to pick the neutral section shell a label sits on. */
104
+ declare function isLightFill(fill: string): boolean;
105
+ /**
106
+ * Neutral section shell fills the renderer paints when a section has no custom
107
+ * colour, mirrored here so contrast feedback can evaluate uncoloured sections.
108
+ */
109
+ declare const LIGHT_NEUTRAL_SECTION_FILL = "#e5e7eb";
110
+ declare const DARK_NEUTRAL_SECTION_FILL = "#273142";
111
+ declare function neutralSectionFill(canvasBackground: string): string;
112
+ interface LabelInkContrastSummary {
113
+ /** Backgrounds that could be evaluated (valid preferred + valid fill hex). */
114
+ total: number;
115
+ /** Backgrounds where the preferred ink is auto-swapped for legibility. */
116
+ overridden: number;
117
+ /** `applies` = preferred survives everywhere; `overridden` = swapped
118
+ * everywhere; `mixed` = some of each; `none` = nothing evaluable. */
119
+ status: 'applies' | 'overridden' | 'mixed' | 'none';
120
+ /** Fallback ink chosen where the preferred was overridden (black/white), or
121
+ * null when nothing was overridden — names the swap in user-facing copy. */
122
+ overrideInk: string | null;
123
+ }
124
+ /**
125
+ * Feedback-only companion to {@link stateAwareBookableLabelInk}: does a preferred
126
+ * label ink survive across a set of backgrounds, or does the shared auto-contrast
127
+ * rule silently swap it for black/white on some of them? Reuses the exact same
128
+ * per-fill decision so a report can never disagree with what the renderer paints.
129
+ */
130
+ declare function summarizeLabelInkContrast(preferred: string, backgrounds: Iterable<string>, minContrast?: number): LabelInkContrastSummary;
131
+
132
+ export { ACCESS_GLYPH_PATH, ACCESS_GLYPH_VIEWBOX, ACCESS_TYPE_GLYPH_PATHS, AUTHORED_LABEL_MIN_CONTRAST, BOOTH_LABEL_FONT_SIZE, DARK_BOOKABLE_LABEL_INK, DARK_NEUTRAL_SECTION_FILL, GA_CAPACITY_LABEL_FONT_SIZE, GA_FILL_OPACITY, GA_LABEL_FONT_SIZE, LARGE_TEXT_CONTRAST, LIGHT_BOOKABLE_LABEL_INK, LIGHT_NEUTRAL_SECTION_FILL, type LabelInkContrastSummary, MIN_VISIBLE_BOOKABLE_LABEL_PX, SEAT_LABEL_FONT_SIZE, SMALL_TEXT_CONTRAST, accessGlyphInk, accessGlyphPath, bookableMarkerLabel, compositeHexOver, isBookableLabelLegibleAtScale, isLightFill, minimumTextContrast, neutralSectionFill, renderedTextContrast, stateAwareBookableLabelInk, summarizeLabelInkContrast };