venuekit 0.10.2 → 0.11.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/dist/react.js CHANGED
@@ -38,6 +38,8 @@ import {
38
38
  summarize,
39
39
  tableSize,
40
40
  toggleRemovedSeat,
41
+ toggleReservedSeat,
42
+ toggleReservedTable,
41
43
  toggleUnit,
42
44
  unitStates,
43
45
  zoneBounds,
@@ -45,7 +47,7 @@ import {
45
47
  zoneLocalSize,
46
48
  zoneOutline,
47
49
  zoneWithLocalSize
48
- } from "./chunk-QQQM6AFF.js";
50
+ } from "./chunk-IRNVYNYV.js";
49
51
 
50
52
  // ../canvas/src/camera.ts
51
53
  var MIN_ZOOM = 0.05;
@@ -199,7 +201,12 @@ var defaultPalette = {
199
201
  unitLabel: "#0b0b0d",
200
202
  landmark: "#23232a",
201
203
  landmarkStroke: "#34343c",
202
- landmarkLabel: "#d6d6dc"
204
+ landmarkLabel: "#d6d6dc",
205
+ courtesy: "#7c3aed",
206
+ courtesyLabel: "#ffffff",
207
+ heldFor: "#f59e0b",
208
+ heldForLabel: "#0b0b0d",
209
+ statusMark: "#ffffff"
203
210
  };
204
211
  var PALETTE_VARIABLES = {
205
212
  taken: "--vk-taken",
@@ -219,7 +226,12 @@ var PALETTE_VARIABLES = {
219
226
  unitLabel: "--vk-unit-fg",
220
227
  landmark: "--vk-landmark",
221
228
  landmarkStroke: "--vk-landmark-stroke",
222
- landmarkLabel: "--vk-landmark-fg"
229
+ landmarkLabel: "--vk-landmark-fg",
230
+ courtesy: "--vk-courtesy",
231
+ courtesyLabel: "--vk-courtesy-fg",
232
+ heldFor: "--vk-held-for",
233
+ heldForLabel: "--vk-held-for-fg",
234
+ statusMark: "--vk-status-mark"
223
235
  };
224
236
  function paletteFromVariables(read, base = defaultPalette) {
225
237
  const out = { ...base };
@@ -296,12 +308,16 @@ function computeBounds(element) {
296
308
  }
297
309
  return own;
298
310
  }
299
- function elementAt(layout, screen, camera, viewport) {
311
+ var EDIT_HIT_SLOP_PX = 4;
312
+ function elementAt(layout, screen, camera, viewport, options = {}) {
300
313
  const world = screenToWorld(camera, viewport, screen);
314
+ const slop = (options.slopPx ?? 0) / camera.zoom;
301
315
  for (let i = layout.elements.length - 1; i >= 0; i -= 1) {
302
316
  const element = layout.elements[i];
303
317
  const box = elementBounds(layout, element);
304
- if (box && world.x >= box.minX && world.x <= box.maxX && world.y >= box.minY && world.y <= box.maxY) {
318
+ const ownsRadius = element.type === "seatBlock" || element.type === "table" && element.mode === "perPlace";
319
+ const pad = element.type === "seatBlock" || element.type === "table" ? (ownsRadius ? options.radii?.get(element.id) ?? 0 : 0) + slop : 0;
320
+ if (box && world.x >= box.minX - pad && world.x <= box.maxX + pad && world.y >= box.minY - pad && world.y <= box.maxY + pad) {
305
321
  if (element.type === "zone" && !pointInZone(element, world)) continue;
306
322
  return element;
307
323
  }
@@ -342,6 +358,7 @@ function unitRadii(layout) {
342
358
  }
343
359
  return out;
344
360
  }
361
+ var RESERVED_DASH_PX = 3;
345
362
  var MIN_NUMBERED_RADIUS_PX = 8;
346
363
  var LABEL_INSET_PX = 12;
347
364
  var TWO_LINES_PX = 40;
@@ -525,7 +542,9 @@ function buildScene(input) {
525
542
  const maxWidth = (body.maxX - body.minX) * camera.zoom - 4;
526
543
  if (maxWidth < MIN_NUMBERED_RADIUS_PX * 2) continue;
527
544
  const ref = refOf(el);
528
- const color = el.mode === "whole" ? dim(ref) ? palette.soldLabel : mapUnitPaint(states.get(el.id) ?? "free", "", palette).label : palette.landmarkLabel;
545
+ const state = states.get(el.id) ?? "free";
546
+ const mark = state !== "selected" && state !== "held" ? input.unitMarks?.get(el.id) : void 0;
547
+ const color = el.mode === "whole" ? dim(ref) ? palette.soldLabel : (mark ? markPaint(mark, palette) : mapUnitPaint(state, "", palette)).label : palette.landmarkLabel;
529
548
  labels.push({ x, y, text: el.label, size: 13, weight: 700, color, align: "centre", maxWidth });
530
549
  }
531
550
  const elementsById = new Map(layout.elements.map((el) => [el.id, el]));
@@ -535,8 +554,10 @@ function buildScene(input) {
535
554
  visibleUnitIds.push(u.id);
536
555
  const color = colorOf(u.placeTypeRef) ?? palette.unassigned;
537
556
  const state = mode === "designer" && input.locked?.has(u.id) ? "taken" : states.get(u.id) ?? "free";
538
- const mapPaint = mode === "map" ? mapUnitPaint(state, color, palette) : null;
539
- const paint2 = mapPaint ? { fill: mapPaint.fill, stroke: mapPaint.stroke, strokeWidth: mapPaint.strokeWidth } : unitPaint(state, color, palette);
557
+ const mark = mode === "map" && state !== "selected" && state !== "held" ? input.unitMarks?.get(u.id) : void 0;
558
+ const mapPaint = mode === "map" ? mark ? markPaint(mark, palette) : mapUnitPaint(state, color, palette) : null;
559
+ const base = mapPaint ? { fill: mapPaint.fill, stroke: mapPaint.stroke, strokeWidth: mapPaint.strokeWidth } : unitPaint(state, color, palette);
560
+ const paint2 = u.inviteOnly && state === "free" && !mark ? { ...base, stroke: palette.label, strokeWidth: 2, dash: RESERVED_DASH_PX } : base;
540
561
  const dimmed = dim(u.placeTypeRef);
541
562
  const alpha = dimmed ? { alpha: DIMMED_ALPHA } : {};
542
563
  const el = elementsById.get(u.elementId);
@@ -549,12 +570,30 @@ function buildScene(input) {
549
570
  if (el?.type === "table" && el.mode === "whole" && el.shape === "rect") {
550
571
  const b = tableBodyBox(el);
551
572
  shapes.push({ kind: "rect", x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY, ...paint2, ...alpha });
573
+ if (mark) shapes.push(...markBadge(mark, b.maxX - radius * 0.25, b.minY + radius * 0.25, radius, palette));
552
574
  continue;
553
575
  }
554
576
  shapes.push({ kind: "circle", x: u.x, y: u.y, radius, ...paint2, ...alpha });
577
+ if (mark) shapes.push(...markBadge(mark, u.x + radius * 0.65, u.y - radius * 0.65, radius, palette));
555
578
  }
556
579
  return { background, shapes, labels, visibleUnitIds };
557
580
  }
581
+ function markPaint(mark, palette) {
582
+ switch (mark) {
583
+ case "sold":
584
+ return mapUnitPaint("taken", "", palette);
585
+ case "courtesy":
586
+ return { fill: palette.courtesy, stroke: palette.soldStroke, strokeWidth: 1, label: palette.courtesyLabel };
587
+ case "held":
588
+ return { fill: palette.heldFor, stroke: palette.soldStroke, strokeWidth: 1, label: palette.heldForLabel };
589
+ }
590
+ }
591
+ function markBadge(mark, x, y, radius, palette) {
592
+ if (mark === "sold") return [];
593
+ const r = radius * 0.4;
594
+ if (mark === "courtesy") return [{ kind: "circle", x, y, radius: r, fill: palette.statusMark, stroke: palette.soldStroke, strokeWidth: 1 }];
595
+ return [{ kind: "rect", x: x - r, y: y - r, width: r * 2, height: r * 2, fill: palette.statusMark, stroke: palette.soldStroke, strokeWidth: 1 }];
596
+ }
558
597
 
559
598
  // ../canvas/src/label-fit.ts
560
599
  var MIN_LEGIBLE_PX = 11;
@@ -642,7 +681,10 @@ function drawShape(ctx, shape, zoom) {
642
681
  shape.points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
643
682
  ctx.closePath();
644
683
  } else ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
684
+ const dash = shape.kind !== "polygon" ? shape.dash : void 0;
685
+ if (dash) ctx.setLineDash([dash / zoom, dash / zoom]);
645
686
  paint(ctx, shape.fill, shape.stroke, shape.strokeWidth, zoom);
687
+ if (dash) ctx.setLineDash([]);
646
688
  ctx.globalAlpha = previous;
647
689
  }
648
690
  function renderScene(scene, target) {
@@ -802,12 +844,12 @@ var defaultA11yLabels = {
802
844
  row: "fila",
803
845
  seat: "asiento"
804
846
  };
805
- function buildA11yNodes(units, states, placeTypeNames, labels = defaultA11yLabels) {
847
+ function buildA11yNodes(units, states, placeTypeNames, labels = defaultA11yLabels, stateWords) {
806
848
  return units.map((u) => {
807
849
  const state = states.get(u.id) ?? "free";
808
850
  const where = u.row !== void 0 && u.seat !== void 0 ? `${labels.row} ${u.row}, ${labels.seat} ${u.seat}` : u.label;
809
851
  const typeName = u.placeTypeRef === null ? void 0 : placeTypeNames.get(u.placeTypeRef);
810
- const name = [typeName, where, labels[state]].filter(Boolean).join(", ");
852
+ const name = [typeName, where, stateWords?.get(u.id) ?? labels[state]].filter(Boolean).join(", ");
811
853
  return {
812
854
  id: u.id,
813
855
  name,
@@ -1341,7 +1383,18 @@ var es = {
1341
1383
  backToMap: "Todo el mapa",
1342
1384
  focusHintSeats: "Toc\xE1 las butacas que quer\xE9s.",
1343
1385
  focusHintPlaces: "Toc\xE1 los lugares que quer\xE9s en cada mesa.",
1344
- focusHintTables: "Toc\xE1 las mesas que quer\xE9s."
1386
+ focusHintTables: "Toc\xE1 las mesas que quer\xE9s.",
1387
+ notForSale: "no est\xE1 a la venta",
1388
+ inviteOnlySeat: "Esa butaca no est\xE1 a la venta",
1389
+ inviteOnlyTable: "Esa mesa no est\xE1 a la venta",
1390
+ inviteOnlyPlace: "Ese lugar no est\xE1 a la venta",
1391
+ inviteOnly: "s\xF3lo con invitaci\xF3n",
1392
+ legendInviteOnly: "S\xF3lo con invitaci\xF3n",
1393
+ statusSold: "vendida",
1394
+ statusCourtesy: "regalada",
1395
+ statusHeld: "apartada",
1396
+ legendCourtesy: "Regalada",
1397
+ legendHeld: "Apartada"
1345
1398
  };
1346
1399
  var en = {
1347
1400
  free: "available",
@@ -1386,7 +1439,18 @@ var en = {
1386
1439
  backToMap: "Whole map",
1387
1440
  focusHintSeats: "Tap the seats you want.",
1388
1441
  focusHintPlaces: "Tap the places you want at each table.",
1389
- focusHintTables: "Tap the tables you want."
1442
+ focusHintTables: "Tap the tables you want.",
1443
+ notForSale: "not for sale",
1444
+ inviteOnlySeat: "That seat is not for sale",
1445
+ inviteOnlyTable: "That table is not for sale",
1446
+ inviteOnlyPlace: "That place is not for sale",
1447
+ inviteOnly: "invitation only",
1448
+ legendInviteOnly: "Invitation only",
1449
+ statusSold: "sold",
1450
+ statusCourtesy: "complimentary",
1451
+ statusHeld: "held",
1452
+ legendCourtesy: "Complimentary",
1453
+ legendHeld: "Held"
1390
1454
  };
1391
1455
  function format(template, vars) {
1392
1456
  return template.replace(
@@ -1472,6 +1536,12 @@ var PICKER_CSS = `
1472
1536
  --vk-landmark:#23232a;
1473
1537
  --vk-landmark-stroke:#34343c;
1474
1538
  --vk-landmark-fg:#d6d6dc;
1539
+ /* Panel de la productora: regalada, apartada y la marca que las distingue sin color. */
1540
+ --vk-courtesy:#7c3aed;
1541
+ --vk-courtesy-fg:#ffffff;
1542
+ --vk-held-for:#f59e0b;
1543
+ --vk-held-for-fg:#0b0b0d;
1544
+ --vk-status-mark:#ffffff;
1475
1545
  }
1476
1546
  .vk-root{display:flex;flex-direction:column;gap:12px;font:var(--vk-font);
1477
1547
  color:var(--vk-fg);background:var(--vk-bg);position:relative;box-sizing:border-box;
@@ -1512,6 +1582,9 @@ var PICKER_CSS = `
1512
1582
  .vk-dot{width:14px;height:14px;border-radius:50%;flex:none;box-sizing:border-box}
1513
1583
  .vk-dot[data-state="selected"]{background:var(--vk-mine);border:3px solid var(--vk-accent)}
1514
1584
  .vk-dot[data-state="taken"]{background:var(--vk-sold);border:1px solid var(--vk-sold-stroke)}
1585
+ .vk-dot[data-state="courtesy"]{background:radial-gradient(circle at 70% 30%,var(--vk-status-mark,#fff) 0 2.5px,transparent 3px),var(--vk-courtesy,#7c3aed);border:1px solid var(--vk-sold-stroke)}
1586
+ .vk-dot[data-state="held-for"]{background:linear-gradient(var(--vk-status-mark,#fff),var(--vk-status-mark,#fff)) 80% 20%/5px 5px no-repeat,var(--vk-held-for,#f59e0b);border:1px solid var(--vk-sold-stroke)}
1587
+ .vk-dot[data-state="invite-only"]{border:2px dashed var(--vk-fg)}
1515
1588
  .vk-cta{border:1px solid var(--vk-accent);background:var(--vk-accent);color:var(--vk-accent-fg);
1516
1589
  border-radius:var(--vk-radius);padding:10px 16px;cursor:pointer;font-size:14px;font-weight:600;min-height:44px}
1517
1590
  .vk-cta:disabled{opacity:.5;cursor:default}
@@ -1556,6 +1629,38 @@ var PICKER_CSS = `
1556
1629
  .vk-type-btn[data-selected="true"]{border-color:var(--vk-accent)}
1557
1630
  `;
1558
1631
 
1632
+ // ../react/src/unit-status.ts
1633
+ var KINDS = ["sold", "courtesy", "held"];
1634
+ function unitMarks(status) {
1635
+ const out = /* @__PURE__ */ new Map();
1636
+ if (!status) return out;
1637
+ for (const [id, s] of Object.entries(status)) {
1638
+ if (s && KINDS.includes(s.kind)) out.set(id, s.kind);
1639
+ }
1640
+ return out;
1641
+ }
1642
+ function presentStatusKinds(marks, units) {
1643
+ const seen = /* @__PURE__ */ new Set();
1644
+ for (const u of units) {
1645
+ const k = marks.get(u.id);
1646
+ if (k) seen.add(k);
1647
+ }
1648
+ return KINDS.filter((k) => seen.has(k));
1649
+ }
1650
+ function statusWord(kind, m) {
1651
+ return kind === "sold" ? m.statusSold : kind === "courtesy" ? m.statusCourtesy : m.statusHeld;
1652
+ }
1653
+ function statusA11y(s, m) {
1654
+ const word = statusWord(s.kind, m);
1655
+ return s.label ? `${word}, ${s.label}` : word;
1656
+ }
1657
+ function statusAnnouncement(unit, s, m) {
1658
+ return `${unitName(unit, m)}: ${s.label || statusWord(s.kind, m)}`;
1659
+ }
1660
+ function blockedInviteOnly(units, allow) {
1661
+ return allow ? [] : units.filter((u) => u.inviteOnly).map((u) => u.id);
1662
+ }
1663
+
1559
1664
  // ../react/src/useImage.ts
1560
1665
  import { useEffect, useState } from "react";
1561
1666
  function useImage(src) {
@@ -1664,7 +1769,8 @@ function VenueCanvas(props) {
1664
1769
  unassignedLabel: messages.unassigned,
1665
1770
  ...props.dimmedRefs ? { dimmedRefs: props.dimmedRefs } : {},
1666
1771
  ...props.placeTypePrices ? { placeTypePrices: props.placeTypePrices } : {},
1667
- ...props.highlightRefs ? { highlightRefs: props.highlightRefs } : {}
1772
+ ...props.highlightRefs ? { highlightRefs: props.highlightRefs } : {},
1773
+ ...props.unitMarks ? { unitMarks: props.unitMarks } : {}
1668
1774
  }),
1669
1775
  [
1670
1776
  layout,
@@ -1679,6 +1785,7 @@ function VenueCanvas(props) {
1679
1785
  props.dimmedRefs,
1680
1786
  props.placeTypePrices,
1681
1787
  props.highlightRefs,
1788
+ props.unitMarks,
1682
1789
  messages.unassigned
1683
1790
  ]
1684
1791
  );
@@ -1772,7 +1879,8 @@ function VenueCanvas(props) {
1772
1879
  return;
1773
1880
  }
1774
1881
  const unit = unitById.get(unitId);
1775
- if (unit && props.focusRef !== null && unit.placeTypeRef === props.focusRef) {
1882
+ const choosable = unit && unit.placeTypeRef !== null && (props.focusRef !== null && unit.placeTypeRef === props.focusRef || !!props.selectableRefs?.has(unit.placeTypeRef));
1883
+ if (unit && choosable) {
1776
1884
  const closer = tapZoom(camera, viewport, result.tap, (props.radii.get(unit.elementId) ?? DEFAULT_UNIT_RADIUS) * camera.zoom);
1777
1885
  if (closer) {
1778
1886
  props.onCameraChange(closer);
@@ -1924,21 +2032,40 @@ function SeatMap(props) {
1924
2032
  const first = unitById.get(ids[0]);
1925
2033
  announce(byKind(first, messages.conflictSeat, messages.conflictTable, messages.conflictPlace), true);
1926
2034
  }, [props.conflictUnitIds, unitById, messages, announce]);
2035
+ const marks = useMemo2(() => unitMarks(props.unitStatus), [props.unitStatus]);
2036
+ const allowInviteOnly = props.allowInviteOnly === true;
2037
+ const blocked = useMemo2(() => new Set(blockedInviteOnly(units, allowInviteOnly)), [units, allowInviteOnly]);
1927
2038
  const taken = useMemo2(
1928
- () => /* @__PURE__ */ new Set([...props.takenUnitIds, ...conflicted]),
1929
- [props.takenUnitIds, conflicted]
2039
+ () => /* @__PURE__ */ new Set([...props.takenUnitIds, ...conflicted, ...marks.keys(), ...blocked]),
2040
+ [props.takenUnitIds, conflicted, marks, blocked]
1930
2041
  );
1931
2042
  const held = useMemo2(() => new Set(props.heldUnitIds ?? NONE), [props.heldUnitIds]);
1932
2043
  const states = useMemo2(() => unitStates(units, taken, selection, held), [units, taken, selection, held]);
1933
- const selectable = useMemo2(() => units.filter((u) => u.placeTypeRef === focusRef), [units, focusRef]);
2044
+ const multiType = props.multiType === true;
2045
+ const selectableRefs = useMemo2(() => multiType ? new Set(placeTypeNames.keys()) : void 0, [multiType, placeTypeNames]);
2046
+ const selectable = useMemo2(
2047
+ () => units.filter((u) => selectableRefs ? u.placeTypeRef !== null && selectableRefs.has(u.placeTypeRef) : u.placeTypeRef === focusRef),
2048
+ [units, focusRef, selectableRefs]
2049
+ );
2050
+ const stateWords = useMemo2(() => {
2051
+ const out = /* @__PURE__ */ new Map();
2052
+ for (const u of selectable) {
2053
+ const s = props.unitStatus?.[u.id];
2054
+ if (s && marks.has(u.id)) out.set(u.id, statusA11y(s, messages));
2055
+ else if (blocked.has(u.id)) out.set(u.id, messages.notForSale);
2056
+ else if (u.inviteOnly && states.get(u.id) === "free") out.set(u.id, `${messages.free}, ${messages.inviteOnly}`);
2057
+ }
2058
+ return out;
2059
+ }, [selectable, props.unitStatus, marks, blocked, states, messages]);
1934
2060
  const a11yNodes = useMemo2(
1935
2061
  () => buildA11yNodes(
1936
2062
  selectable.map((u) => u.kind === "seat" ? u : { ...u, label: a11yLabel(u, messages) }),
1937
2063
  states,
1938
2064
  placeTypeNames,
1939
- { free: messages.free, taken: messages.taken, selected: messages.selected, held: messages.held, row: messages.row, seat: messages.seat }
2065
+ { free: messages.free, taken: messages.taken, selected: messages.selected, held: messages.held, row: messages.row, seat: messages.seat },
2066
+ stateWords
1940
2067
  ),
1941
- [selectable, states, placeTypeNames, messages]
2068
+ [selectable, states, placeTypeNames, messages, stateWords]
1942
2069
  );
1943
2070
  const bg = layout.meta.background;
1944
2071
  const background = useImage(bg?.src ?? null);
@@ -1975,10 +2102,20 @@ function SeatMap(props) {
1975
2102
  const activateUnit = useCallback((unitId) => {
1976
2103
  const unit = unitById.get(unitId);
1977
2104
  if (!unit) return;
1978
- if (unit.placeTypeRef !== focusRef) {
2105
+ const ownType = unit.placeTypeRef === focusRef || !!selectableRefs && unit.placeTypeRef !== null && selectableRefs.has(unit.placeTypeRef);
2106
+ if (!ownType) {
1979
2107
  switchTo(unit.placeTypeRef);
1980
2108
  return;
1981
2109
  }
2110
+ const status = props.unitStatus?.[unitId];
2111
+ if (status && marks.has(unitId) && !selection.includes(unitId)) {
2112
+ announce(statusAnnouncement(unit, status, messages), true);
2113
+ return;
2114
+ }
2115
+ if (blocked.has(unitId) && !selection.includes(unitId)) {
2116
+ announce(byKind(unit, messages.inviteOnlySeat, messages.inviteOnlyTable, messages.inviteOnlyPlace), true);
2117
+ return;
2118
+ }
1982
2119
  if (states.get(unitId) === "held" && !selection.includes(unitId)) {
1983
2120
  announce(messages.heldSeat, true);
1984
2121
  return;
@@ -1995,13 +2132,29 @@ function SeatMap(props) {
1995
2132
  const added = result.selection.includes(unitId);
1996
2133
  announce(`${unitName(unit, messages)}: ${added ? messages.selected : messages.free}`);
1997
2134
  onSelectionChange(result.selection);
1998
- }, [unitById, focusRef, states, selection, maxSelection, messages, announce, onSelectionChange, switchTo]);
2135
+ }, [
2136
+ unitById,
2137
+ focusRef,
2138
+ selectableRefs,
2139
+ props.unitStatus,
2140
+ marks,
2141
+ blocked,
2142
+ states,
2143
+ selection,
2144
+ maxSelection,
2145
+ messages,
2146
+ announce,
2147
+ onSelectionChange,
2148
+ switchTo
2149
+ ]);
1999
2150
  const summary = useMemo2(() => selectionSummary(units, selection, messages), [units, selection, messages]);
2000
2151
  const focusColor = placeTypeColors.get(focusRef) ?? "var(--vk-unassigned)";
2001
2152
  const focusType = props.placeTypes.find((p) => p.ref === focusRef);
2002
2153
  const embedded = props.embedded === true;
2003
2154
  const kind = useMemo2(() => focusKind(units, focusRef), [units, focusRef]);
2004
2155
  const hint = kind === "seat" ? messages.focusHintSeats : kind === "place" ? messages.focusHintPlaces : messages.focusHintTables;
2156
+ const statusKinds = useMemo2(() => presentStatusKinds(marks, units), [marks, units]);
2157
+ const showInviteOnly = useMemo2(() => allowInviteOnly && units.some((u) => u.inviteOnly && !marks.has(u.id)), [allowInviteOnly, units, marks]);
2005
2158
  return /* @__PURE__ */ jsxs2(
2006
2159
  "div",
2007
2160
  {
@@ -2043,7 +2196,9 @@ function SeatMap(props) {
2043
2196
  onActivateUnit: activateUnit,
2044
2197
  messages,
2045
2198
  placeTypeColors,
2046
- focusRef,
2199
+ focusRef: multiType ? null : focusRef,
2200
+ ...selectableRefs ? { selectableRefs } : {},
2201
+ ...marks.size > 0 ? { unitMarks: marks } : {},
2047
2202
  onTapEmpty,
2048
2203
  placeTypePrices,
2049
2204
  ...props.height !== void 0 ? { height: props.height } : {},
@@ -2064,7 +2219,19 @@ function SeatMap(props) {
2064
2219
  /* @__PURE__ */ jsxs2("li", { children: [
2065
2220
  /* @__PURE__ */ jsx2("span", { className: "vk-dot", "data-state": "taken" }),
2066
2221
  messages.legendTaken
2067
- ] })
2222
+ ] }),
2223
+ statusKinds.includes("courtesy") ? /* @__PURE__ */ jsxs2("li", { children: [
2224
+ /* @__PURE__ */ jsx2("span", { className: "vk-dot", "data-state": "courtesy" }),
2225
+ messages.legendCourtesy
2226
+ ] }) : null,
2227
+ statusKinds.includes("held") ? /* @__PURE__ */ jsxs2("li", { children: [
2228
+ /* @__PURE__ */ jsx2("span", { className: "vk-dot", "data-state": "held-for" }),
2229
+ messages.legendHeld
2230
+ ] }) : null,
2231
+ showInviteOnly ? /* @__PURE__ */ jsxs2("li", { children: [
2232
+ /* @__PURE__ */ jsx2("span", { className: "vk-dot", "data-state": "invite-only", style: { background: focusColor } }),
2233
+ messages.legendInviteOnly
2234
+ ] }) : null
2068
2235
  ] }),
2069
2236
  announcement.blocking && announcement.text ? /* @__PURE__ */ jsx2("p", { className: "vk-notice", "data-testid": "notice", children: announcement.text }) : null,
2070
2237
  embedded ? null : /* @__PURE__ */ jsxs2("div", { className: "vk-bar", "data-testid": "summary-bar", children: [
@@ -2135,9 +2302,11 @@ function VenueOverview(props) {
2135
2302
  const highlightRefs = useMemo3(() => new Set(selectedRefs), [selectedRefs]);
2136
2303
  const takenIds = props.takenUnitIds ?? NONE2;
2137
2304
  const selectedIds = props.selectedUnitIds ?? NONE2;
2305
+ const marks = useMemo3(() => unitMarks(props.unitStatus), [props.unitStatus]);
2306
+ const allowInviteOnly = props.allowInviteOnly === true;
2138
2307
  const states = useMemo3(
2139
- () => unitStates(units, new Set(takenIds), selectedIds, NO_HELD),
2140
- [units, takenIds, selectedIds]
2308
+ () => unitStates(units, /* @__PURE__ */ new Set([...takenIds, ...blockedInviteOnly(units, allowInviteOnly)]), selectedIds, NO_HELD),
2309
+ [units, takenIds, selectedIds, allowInviteOnly]
2141
2310
  );
2142
2311
  const hideList = props.hideList === true;
2143
2312
  const canvasMessages = useMemo3(
@@ -2214,6 +2383,7 @@ function VenueOverview(props) {
2214
2383
  dimmedRefs,
2215
2384
  placeTypePrices,
2216
2385
  highlightRefs,
2386
+ ...marks.size > 0 ? { unitMarks: marks } : {},
2217
2387
  ...props.height !== void 0 ? { height: props.height } : {},
2218
2388
  ratio: contentRatio,
2219
2389
  cooperative: true,
@@ -3007,7 +3177,7 @@ var DesignerCanvas = forwardRef(function DesignerCanvas2({ designer, placeTypes,
3007
3177
  }
3008
3178
  const d = latest.current.designer;
3009
3179
  const { camera: c, viewport: v } = latest.current;
3010
- const hit = kind === "down" ? backgroundAt(d.layout.meta.background, screenToWorld(c, v, p), d.ui.backgroundEditing) ?? elementAt(d.layout, p, c, v)?.id ?? null : null;
3180
+ const hit = kind === "down" ? backgroundAt(d.layout.meta.background, screenToWorld(c, v, p), d.ui.backgroundEditing) ?? elementAt(d.layout, p, c, v, { radii, slopPx: EDIT_HIT_SLOP_PX })?.id ?? null : null;
3011
3181
  const elementLocked = d.ui.armed !== null || hit !== null && hit === d.ui.seatEditId;
3012
3182
  if (kind !== "down") {
3013
3183
  feed({ kind, p, hitElementId: hit, elementLocked });
@@ -3362,20 +3532,23 @@ function blockRangeText(block, templates) {
3362
3532
  }
3363
3533
  function reshapeBlock(block, patch) {
3364
3534
  const next = { ...block, ...patch };
3365
- if (block.removed.length === 0) return next;
3535
+ if (block.removed.length === 0 && !block.reserved?.length) return next;
3366
3536
  const oldRows = expandLabels(block.rowLabels, block.rows);
3367
3537
  const oldSeats = expandLabels(block.seatLabels, block.cols);
3368
3538
  const newRows = expandLabels(next.rowLabels, next.rows);
3369
3539
  const newSeats = expandLabels(next.seatLabels, next.cols);
3370
- const removed = [];
3371
- for (const id of block.removed) {
3372
- const [, row, seat] = parseUnitId(id);
3373
- const r = row === void 0 ? -1 : oldRows.indexOf(row);
3374
- const c = seat === void 0 ? -1 : oldSeats.indexOf(seat);
3375
- if (r < 0 || c < 0 || r >= next.rows || c >= next.cols) continue;
3376
- removed.push(seatUnitId(block.id, newRows[r], newSeats[c]));
3377
- }
3378
- return { ...next, removed };
3540
+ const remap = (ids) => {
3541
+ const out = [];
3542
+ for (const id of ids) {
3543
+ const [, row, seat] = parseUnitId(id);
3544
+ const r = row === void 0 ? -1 : oldRows.indexOf(row);
3545
+ const c = seat === void 0 ? -1 : oldSeats.indexOf(seat);
3546
+ if (r < 0 || c < 0 || r >= next.rows || c >= next.cols) continue;
3547
+ out.push(seatUnitId(block.id, newRows[r], newSeats[c]));
3548
+ }
3549
+ return out;
3550
+ };
3551
+ return { ...next, removed: remap(block.removed), ...block.reserved ? { reserved: remap(block.reserved) } : {} };
3379
3552
  }
3380
3553
  function labelStart(kind, raw, alphabet) {
3381
3554
  const start = kind === "letters" ? raw.trim().toUpperCase() : raw.trim();
@@ -3457,8 +3630,14 @@ function unitsOnSale(placeTypes, summary) {
3457
3630
  }
3458
3631
  function numberingCounts(block, m) {
3459
3632
  const removed = block.removed.length;
3460
- const onSale = format(m.resultOnSale, { seats: plural(block.rows * block.cols - removed, m.countSeat, m.countSeats) });
3461
- return removed === 0 ? onSale : `${onSale} \xB7 ${plural(removed, m.removedOne, m.removedMany)}`;
3633
+ const out = new Set(block.removed);
3634
+ const reserved = (block.reserved ?? []).filter((id) => !out.has(id)).length;
3635
+ const onSale = format(m.resultOnSale, { seats: plural(block.rows * block.cols - removed - reserved, m.countSeat, m.countSeats) });
3636
+ return [
3637
+ onSale,
3638
+ ...reserved > 0 ? [plural(reserved, m.reservedOne, m.reservedMany)] : [],
3639
+ ...removed > 0 ? [plural(removed, m.removedOne, m.removedMany)] : []
3640
+ ].join(" \xB7 ");
3462
3641
  }
3463
3642
 
3464
3643
  // ../react/src/designer/FloatingPanel.tsx
@@ -3951,7 +4130,7 @@ var designerEs = {
3951
4130
  blockRangeOneRow: "Fila {row} \xB7 butacas {seatFrom} a {seatTo}",
3952
4131
  numbering: "Numeraci\xF3n y pasillos",
3953
4132
  numberingTitle: "Numeraci\xF3n y pasillos \xB7 {name}",
3954
- numberingHint: "As\xED lo va a ver quien compra. Toc\xE1 una butaca para sacarla (pasillo o columna); los n\xFAmeros de las dem\xE1s no cambian.",
4133
+ numberingHint: "As\xED lo va a ver quien compra. Toc\xE1 una butaca para reservarla, o eleg\xED \xABSacar (pasillo)\xBB para sacarla; los n\xFAmeros de las dem\xE1s no cambian.",
3955
4134
  numberingTooBig: "El bloque es muy grande para mostrarlo ac\xE1. La numeraci\xF3n se puede cambiar igual.",
3956
4135
  numberingFailed: "No se pudo aplicar este cambio.",
3957
4136
  rowsNamed: "Las filas se llaman",
@@ -3987,6 +4166,17 @@ var designerEs = {
3987
4166
  seatName: "Fila {row}, butaca {seat}",
3988
4167
  seatNameRemoved: "Fila {row}, butaca {seat} sacada",
3989
4168
  seatNameSold: "Fila {row}, butaca {seat}, ya vendida",
4169
+ seatNameReserved: "Fila {row}, butaca {seat}, reservada",
4170
+ tapMode: "Al tocar",
4171
+ tapReserve: "Reservar",
4172
+ tapRemove: "Sacar (pasillo)",
4173
+ reservedNotice: "Una butaca reservada se ve en el mapa pero no se vende al p\xFAblico: queda para cortes\xEDas y cupos apartados.",
4174
+ legendReserved: "Reservada",
4175
+ reservedOne: "1 reservada",
4176
+ reservedMany: "{n} reservadas",
4177
+ tableInviteOnly: "Mesa s\xF3lo con invitaci\xF3n",
4178
+ placesInviteOnly: "Lugares s\xF3lo con invitaci\xF3n",
4179
+ placeInviteOnly: "Lugar {n} s\xF3lo con invitaci\xF3n",
3990
4180
  assignTitle: "\xBFDe qu\xE9 entrada es?",
3991
4181
  noPlaceTypes: "No hay entradas que le sirvan a esta pieza.",
3992
4182
  capacityCount: "{placed} de {capacity} lugares",
@@ -4171,7 +4361,7 @@ var designerEn = {
4171
4361
  blockRangeOneRow: "Row {row} \xB7 seats {seatFrom} to {seatTo}",
4172
4362
  numbering: "Numbering and aisles",
4173
4363
  numberingTitle: "Numbering and aisles \xB7 {name}",
4174
- numberingHint: "This is how buyers will see it. Tap a seat to remove it (an aisle or a pillar); the other numbers don't change.",
4364
+ numberingHint: "This is how buyers will see it. Tap a seat to reserve it, or pick \xABRemove (aisle)\xBB to remove it; the other numbers don't change.",
4175
4365
  numberingTooBig: "The block is too big to show here. You can still change the numbering.",
4176
4366
  numberingFailed: "This change couldn't be applied.",
4177
4367
  rowsNamed: "Rows are named",
@@ -4207,6 +4397,17 @@ var designerEn = {
4207
4397
  seatName: "Row {row}, seat {seat}",
4208
4398
  seatNameRemoved: "Row {row}, seat {seat} removed",
4209
4399
  seatNameSold: "Row {row}, seat {seat}, already sold",
4400
+ seatNameReserved: "Row {row}, seat {seat}, reserved",
4401
+ tapMode: "On tap",
4402
+ tapReserve: "Reserve",
4403
+ tapRemove: "Remove (aisle)",
4404
+ reservedNotice: "A reserved seat shows on the map but isn't sold to the public: it's kept for complimentary tickets and held allocations.",
4405
+ legendReserved: "Reserved",
4406
+ reservedOne: "1 reserved",
4407
+ reservedMany: "{n} reserved",
4408
+ tableInviteOnly: "Invitation-only table",
4409
+ placesInviteOnly: "Invitation-only places",
4410
+ placeInviteOnly: "Place {n} invitation only",
4210
4411
  assignTitle: "Which ticket is it for?",
4211
4412
  noPlaceTypes: "No tickets fit this piece.",
4212
4413
  capacityCount: "{placed} of {capacity} spots",
@@ -4378,6 +4579,38 @@ function pieceHeading(el, placeTypes, m) {
4378
4579
  const count = el.type === "seatBlock" ? plural2(expandSeatBlock(el).length, m.countSeat, m.countSeats) : el.mode === "whole" ? plural2(1, m.countTable, m.countTables) : plural2(el.places, m.countPlace, m.countPlaces);
4379
4580
  return { color: option?.color ?? null, text: `${name} \xB7 ${count}` };
4380
4581
  }
4582
+ function TableInviteOnly(props) {
4583
+ const { table: t, messages: m } = props;
4584
+ const reserved = new Set(t.reservedPlaces ?? []);
4585
+ return /* @__PURE__ */ jsxs8("div", { className: "vk-d-num-group", "data-testid": "table-invite-only", style: { ["--vk-type"]: props.color ?? "var(--vk-unassigned, #9ca3af)" }, children: [
4586
+ t.mode === "whole" ? /* @__PURE__ */ jsxs8("label", { className: "vk-d-check", children: [
4587
+ /* @__PURE__ */ jsx9(
4588
+ "input",
4589
+ {
4590
+ type: "checkbox",
4591
+ checked: t.reserved === true,
4592
+ onChange: () => props.onRun({ type: "replaceElement", element: toggleReservedTable(t) })
4593
+ }
4594
+ ),
4595
+ /* @__PURE__ */ jsx9("span", { children: m.tableInviteOnly })
4596
+ ] }) : /* @__PURE__ */ jsxs8("div", { role: "group", "aria-label": m.placesInviteOnly, children: [
4597
+ /* @__PURE__ */ jsx9("span", { className: "vk-d-sub", children: m.placesInviteOnly }),
4598
+ /* @__PURE__ */ jsx9("div", { className: "vk-d-places", children: Array.from({ length: t.places }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsx9(
4599
+ "button",
4600
+ {
4601
+ type: "button",
4602
+ className: "vk-d-place",
4603
+ "aria-pressed": reserved.has(n),
4604
+ "aria-label": format(m.placeInviteOnly, { n }),
4605
+ onClick: () => props.onRun({ type: "replaceElement", element: toggleReservedTable(t, n) }),
4606
+ children: n
4607
+ },
4608
+ n
4609
+ )) })
4610
+ ] }),
4611
+ /* @__PURE__ */ jsx9("p", { className: "vk-d-hint", children: m.reservedNotice })
4612
+ ] });
4613
+ }
4381
4614
  function PieceInspector(props) {
4382
4615
  const { element: el, messages: m } = props;
4383
4616
  const [labelError, setLabelError] = useState8(null);
@@ -4519,7 +4752,8 @@ function PieceInspector(props) {
4519
4752
  /* @__PURE__ */ jsx9("span", { "data-testid": "block-range", children: blockRangeText(el, { many: m.blockRange, one: m.blockRangeOneRow }) }),
4520
4753
  /* @__PURE__ */ jsx9("button", { type: "button", className: "vk-d-link", "aria-haspopup": "dialog", onClick: props.onNumbering, children: m.numbering })
4521
4754
  ] }),
4522
- el.type === "table" && assigned && /* @__PURE__ */ jsx9("p", { className: "vk-d-hint", children: el.mode === "whole" ? m.sellWhole : m.sellPerPlace })
4755
+ el.type === "table" && assigned && /* @__PURE__ */ jsx9("p", { className: "vk-d-hint", children: el.mode === "whole" ? m.sellWhole : m.sellPerPlace }),
4756
+ el.type === "table" && assigned && /* @__PURE__ */ jsx9(TableInviteOnly, { table: el, color: heading.color, messages: m, onRun: props.onRun })
4523
4757
  ] });
4524
4758
  }
4525
4759
 
@@ -4584,6 +4818,8 @@ function NumberingDialog(props) {
4584
4818
  const rowLabels = expandLabels(draft.rowLabels, draft.rows);
4585
4819
  const seatLabels = expandLabels(draft.seatLabels, draft.cols);
4586
4820
  const removed = new Set(draft.removed);
4821
+ const reserved = new Set(draft.reserved ?? []);
4822
+ const [tapMode, setTapMode] = useState9("reserve");
4587
4823
  const origRows = expandLabels(props.block.rowLabels, props.block.rows);
4588
4824
  const origSeats = expandLabels(props.block.seatLabels, props.block.cols);
4589
4825
  const soldAt = (r, c) => r < origRows.length && c < origSeats.length && props.lockedUnitIds.has(seatUnitId(props.block.id, origRows[r], origSeats[c]));
@@ -4611,19 +4847,21 @@ function NumberingDialog(props) {
4611
4847
  const id = seatUnitId(draft.id, row, seat);
4612
4848
  const sold = soldAt(r, c) && !removed.has(id);
4613
4849
  const out = removed.has(id);
4850
+ const held = !out && reserved.has(id);
4614
4851
  return /* @__PURE__ */ jsx10(
4615
4852
  "button",
4616
4853
  {
4617
4854
  type: "button",
4618
4855
  className: "vk-d-num-seat",
4619
4856
  "data-removed": out || void 0,
4857
+ "data-reserved": held || void 0,
4620
4858
  "data-sold": sold || void 0,
4621
4859
  disabled: sold,
4622
- "aria-pressed": out,
4623
- "aria-label": format(sold ? m.seatNameSold : out ? m.seatNameRemoved : m.seatName, { row, seat }),
4860
+ "aria-pressed": tapMode === "reserve" ? held : out,
4861
+ "aria-label": format(sold ? m.seatNameSold : out ? m.seatNameRemoved : held ? m.seatNameReserved : m.seatName, { row, seat }),
4624
4862
  onClick: () => {
4625
4863
  setFailed(false);
4626
- setDraft((d) => toggleRemovedSeat(d, id));
4864
+ setDraft((d) => tapMode === "remove" || out ? toggleRemovedSeat(d, id) : toggleReservedSeat(d, id));
4627
4865
  },
4628
4866
  children: seat
4629
4867
  },
@@ -4636,6 +4874,10 @@ function NumberingDialog(props) {
4636
4874
  /* @__PURE__ */ jsx10("span", { className: "vk-d-legend-dot", "aria-hidden": "true" }),
4637
4875
  m.legendOnSale
4638
4876
  ] }),
4877
+ /* @__PURE__ */ jsxs9("span", { children: [
4878
+ /* @__PURE__ */ jsx10("span", { className: "vk-d-legend-dot vk-d-legend-reserved", "aria-hidden": "true" }),
4879
+ m.legendReserved
4880
+ ] }),
4639
4881
  /* @__PURE__ */ jsxs9("span", { children: [
4640
4882
  /* @__PURE__ */ jsx10("span", { className: "vk-d-legend-dot vk-d-legend-out", "aria-hidden": "true" }),
4641
4883
  m.legendRemoved
@@ -4644,6 +4886,18 @@ function NumberingDialog(props) {
4644
4886
  ] })
4645
4887
  ] }),
4646
4888
  /* @__PURE__ */ jsxs9("div", { className: "vk-d-num-side", children: [
4889
+ /* @__PURE__ */ jsxs9("div", { className: "vk-d-num-group", "data-testid": "tap-mode", children: [
4890
+ /* @__PURE__ */ jsx10(
4891
+ Segmented,
4892
+ {
4893
+ label: m.tapMode,
4894
+ value: tapMode,
4895
+ options: [{ value: "reserve", label: m.tapReserve }, { value: "remove", label: m.tapRemove }],
4896
+ onChange: setTapMode
4897
+ }
4898
+ ),
4899
+ /* @__PURE__ */ jsx10("p", { className: "vk-d-hint", "data-testid": "reserved-notice", children: m.reservedNotice })
4900
+ ] }),
4647
4901
  /* @__PURE__ */ jsxs9("div", { className: "vk-d-num-group", children: [
4648
4902
  /* @__PURE__ */ jsx10(
4649
4903
  Segmented,
@@ -5262,10 +5516,15 @@ var DESIGNER_CSS = `
5262
5516
  .vk-d-num-seat{flex:none;width:34px;height:34px;border-radius:999px;border:0;padding:0;background:var(--vk-type);
5263
5517
  color:#fff;font:inherit;font-size:12px;font-weight:600;cursor:pointer}
5264
5518
  .vk-d-num-seat[data-removed]{background:transparent;border:2px dashed var(--vk-unassigned,#5a5a66);color:var(--vk-muted)}
5519
+ .vk-d-num-seat[data-reserved]{background:repeating-linear-gradient(45deg,var(--vk-type) 0 4px,var(--vk-surface,#fff) 4px 7px);border:2px solid var(--vk-fg);color:var(--vk-fg)}
5265
5520
  .vk-d-num-seat[data-sold]{cursor:not-allowed;opacity:.55}
5266
- .vk-d-legend{display:flex;gap:18px;font-size:13px;color:var(--vk-muted)}
5521
+ .vk-d-legend{display:flex;flex-wrap:wrap;justify-content:center;gap:6px 18px;font-size:13px;color:var(--vk-muted)}
5267
5522
  .vk-d-legend > span{display:flex;align-items:center;gap:6px}
5268
5523
  .vk-d-legend-dot{width:12px;height:12px;border-radius:999px;background:var(--vk-type)}
5524
+ .vk-d-places{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
5525
+ .vk-d-place{min-width:44px;min-height:44px;border-radius:999px;border:1px solid var(--vk-line);background:var(--vk-seg,transparent);color:var(--vk-fg);font:inherit;font-weight:600;cursor:pointer}
5526
+ .vk-d-place[aria-pressed="true"]{background:repeating-linear-gradient(45deg,var(--vk-type,#9ca3af) 0 4px,var(--vk-surface,#fff) 4px 7px);border:2px solid var(--vk-fg)}
5527
+ .vk-d-legend-reserved{background:repeating-linear-gradient(45deg,var(--vk-type) 0 3px,var(--vk-surface,#fff) 3px 5px);border:2px solid var(--vk-fg)}
5269
5528
  .vk-d-legend-out{background:transparent;border:2px dashed var(--vk-unassigned,#5a5a66)}
5270
5529
  .vk-d-num-side{width:340px;flex:none;border-left:1px solid var(--vk-line);padding:24px;display:flex;flex-direction:column;gap:20px;overflow:auto;background:var(--vk-surface)}
5271
5530
  .vk-d-num-group{display:flex;flex-direction:column;gap:8px}
@@ -6098,7 +6357,7 @@ function VenueDesigner(props) {
6098
6357
  }
6099
6358
 
6100
6359
  // ../react/src/index.ts
6101
- var VERSION = "0.10.2";
6360
+ var VERSION = "0.11.0";
6102
6361
  export {
6103
6362
  DESIGNER_CSS,
6104
6363
  PICKER_CSS,