react-svg-seatmap
Version:
React components that render a highly-customizable seatmap based on a provided SVG
764 lines (763 loc) • 23.5 kB
JavaScript
import ae, { useRef as ce, useEffect as Y, useState as H, useCallback as V, useMemo as z } from "react";
import ie from "svg-pan-zoom";
import le from "lodash";
import ue from "react-selecto";
var W = { exports: {} }, $ = {};
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var te;
function fe() {
if (te) return $;
te = 1;
var c = Symbol.for("react.transitional.element"), m = Symbol.for("react.fragment");
function b(l, v, h) {
var g = null;
if (h !== void 0 && (g = "" + h), v.key !== void 0 && (g = "" + v.key), "key" in v) {
h = {};
for (var S in v)
S !== "key" && (h[S] = v[S]);
} else h = v;
return v = h.ref, {
$$typeof: c,
type: l,
key: g,
ref: v !== void 0 ? v : null,
props: h
};
}
return $.Fragment = m, $.jsx = b, $.jsxs = b, $;
}
var D = {};
/**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var re;
function me() {
return re || (re = 1, process.env.NODE_ENV !== "production" && function() {
function c(e) {
if (e == null) return null;
if (typeof e == "function")
return e.$$typeof === a ? null : e.displayName || e.name || null;
if (typeof e == "string") return e;
switch (e) {
case d:
return "Fragment";
case L:
return "Profiler";
case P:
return "StrictMode";
case q:
return "Suspense";
case r:
return "SuspenseList";
case o:
return "Activity";
}
if (typeof e == "object")
switch (typeof e.tag == "number" && console.error(
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
), e.$$typeof) {
case N:
return "Portal";
case w:
return (e.displayName || "Context") + ".Provider";
case y:
return (e._context.displayName || "Context") + ".Consumer";
case F:
var n = e.render;
return e = e.displayName, e || (e = n.displayName || n.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
case t:
return n = e.displayName || null, n !== null ? n : c(e.type) || "Memo";
case s:
n = e._payload, e = e._init;
try {
return c(e(n));
} catch {
}
}
return null;
}
function m(e) {
return "" + e;
}
function b(e) {
try {
m(e);
var n = !1;
} catch {
n = !0;
}
if (n) {
n = console;
var _ = n.error, T = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
return _.call(
n,
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
T
), m(e);
}
}
function l(e) {
if (e === d) return "<>";
if (typeof e == "object" && e !== null && e.$$typeof === s)
return "<...>";
try {
var n = c(e);
return n ? "<" + n + ">" : "<...>";
} catch {
return "<...>";
}
}
function v() {
var e = f.A;
return e === null ? null : e.getOwner();
}
function h() {
return Error("react-stack-top-frame");
}
function g(e) {
if (O.call(e, "key")) {
var n = Object.getOwnPropertyDescriptor(e, "key").get;
if (n && n.isReactWarning) return !1;
}
return e.key !== void 0;
}
function S(e, n) {
function _() {
X || (X = !0, console.error(
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
n
));
}
_.isReactWarning = !0, Object.defineProperty(e, "key", {
get: _,
configurable: !0
});
}
function j() {
var e = c(this.type);
return M[e] || (M[e] = !0, console.error(
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
)), e = this.props.ref, e !== void 0 ? e : null;
}
function R(e, n, _, T, Z, A, G, B) {
return _ = A.ref, e = {
$$typeof: k,
type: e,
key: n,
props: A,
_owner: Z
}, (_ !== void 0 ? _ : null) !== null ? Object.defineProperty(e, "ref", {
enumerable: !1,
get: j
}) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
configurable: !1,
enumerable: !1,
writable: !0,
value: 0
}), Object.defineProperty(e, "_debugInfo", {
configurable: !1,
enumerable: !1,
writable: !0,
value: null
}), Object.defineProperty(e, "_debugStack", {
configurable: !1,
enumerable: !1,
writable: !0,
value: G
}), Object.defineProperty(e, "_debugTask", {
configurable: !1,
enumerable: !1,
writable: !0,
value: B
}), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
}
function E(e, n, _, T, Z, A, G, B) {
var x = n.children;
if (x !== void 0)
if (T)
if (C(x)) {
for (T = 0; T < x.length; T++)
u(x[T]);
Object.freeze && Object.freeze(x);
} else
console.error(
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
);
else u(x);
if (O.call(n, "key")) {
x = c(e);
var I = Object.keys(n).filter(function(ne) {
return ne !== "key";
});
T = 0 < I.length ? "{key: someKey, " + I.join(": ..., ") + ": ...}" : "{key: someKey}", ee[x + T] || (I = 0 < I.length ? "{" + I.join(": ..., ") + ": ...}" : "{}", console.error(
`A props object containing a "key" prop is being spread into JSX:
let props = %s;
<%s {...props} />
React keys must be passed directly to JSX without using spread:
let props = %s;
<%s key={someKey} {...props} />`,
T,
x,
I,
x
), ee[x + T] = !0);
}
if (x = null, _ !== void 0 && (b(_), x = "" + _), g(n) && (b(n.key), x = "" + n.key), "key" in n) {
_ = {};
for (var J in n)
J !== "key" && (_[J] = n[J]);
} else _ = n;
return x && S(
_,
typeof e == "function" ? e.displayName || e.name || "Unknown" : e
), R(
e,
x,
A,
Z,
v(),
_,
G,
B
);
}
function u(e) {
typeof e == "object" && e !== null && e.$$typeof === k && e._store && (e._store.validated = 1);
}
var p = ae, k = Symbol.for("react.transitional.element"), N = Symbol.for("react.portal"), d = Symbol.for("react.fragment"), P = Symbol.for("react.strict_mode"), L = Symbol.for("react.profiler"), y = Symbol.for("react.consumer"), w = Symbol.for("react.context"), F = Symbol.for("react.forward_ref"), q = Symbol.for("react.suspense"), r = Symbol.for("react.suspense_list"), t = Symbol.for("react.memo"), s = Symbol.for("react.lazy"), o = Symbol.for("react.activity"), a = Symbol.for("react.client.reference"), f = p.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, O = Object.prototype.hasOwnProperty, C = Array.isArray, U = console.createTask ? console.createTask : function() {
return null;
};
p = {
"react-stack-bottom-frame": function(e) {
return e();
}
};
var X, M = {}, Q = p["react-stack-bottom-frame"].bind(
p,
h
)(), K = U(l(h)), ee = {};
D.Fragment = d, D.jsx = function(e, n, _, T, Z) {
var A = 1e4 > f.recentlyCreatedOwnerStacks++;
return E(
e,
n,
_,
!1,
T,
Z,
A ? Error("react-stack-top-frame") : Q,
A ? U(l(e)) : K
);
}, D.jsxs = function(e, n, _, T, Z) {
var A = 1e4 > f.recentlyCreatedOwnerStacks++;
return E(
e,
n,
_,
!0,
T,
Z,
A ? Error("react-stack-top-frame") : Q,
A ? U(l(e)) : K
);
};
}()), D;
}
var oe;
function de() {
return oe || (oe = 1, process.env.NODE_ENV === "production" ? W.exports = fe() : W.exports = me()), W.exports;
}
var i = de();
const ve = (c) => {
const m = ce(void 0);
return Y(() => {
m.current = c;
}), m.current;
}, _e = ({
availableSeats: c,
selectedSeatIds: m,
svg: b,
onSeatSelect: l,
onSeatDeselect: v,
onSeatHover: h,
onSeatHoverEnd: g,
allowDragAndPan: S = !0,
showZoomControls: j = !0,
leftControls: R,
rightControls: E
}) => {
const [u, p] = H(""), [k, N] = H(!1), d = ve({
svgString: u,
selectedSeatIds: m,
availableSeats: c
}), P = V(
(r) => m && m.includes(r.id),
[m]
), L = V(
(r) => c.find((t) => {
try {
return r.matches(t.cssSelector);
} catch {
return !1;
}
}),
[c]
), y = V(
() => ie(".seatmap__svg svg", {
dblClickZoomEnabled: !1,
mouseWheelZoomEnabled: !0,
zoomScaleSensitivity: 0.5
}),
[]
);
Y(() => {
fetch(b).then((r) => r.text()).then((r) => p(r)).catch((r) => console.error("Failed to load SVG:", r));
}, [b]);
const w = V(
(r, t) => {
t ? (r.classList.remove("seat--unavailable"), m != null && m.includes(t.id) ? (r.classList.add("seat--selected"), r.classList.remove("seat--available")) : (r.classList.add("seat--available"), r.classList.remove("seat--selected")), t.color && r.setAttribute(
"style",
`stroke: ${t.color} !important;${P(t) ? " fill: " + t.color + " !important;" : ""}`
)) : (r.classList.add("seat--unavailable"), r.classList.remove("seat--available"), r.classList.remove("seat--selected"));
},
[P, m]
);
Y(() => {
if (!u) {
console.log("No SVG, exiting");
return;
}
k && (console.log("SVG changed, repainting all seats..."), document.querySelectorAll(
".seatmap__svg circle, .seatmap__svg path, .seatmap__svg ellipse"
).forEach((t) => {
const s = L(t);
w(t, s);
}), console.log("Finished initial paint of SVG"), N(!1));
}, [k, L, w, u]), Y(() => {
if (!u) return;
const r = y();
r.resize(), r.center(), r.fit();
}, [u, y]), Y(() => {
if (!u) return;
console.log("Updating pan and zoom...", S);
const r = y();
S ? r.enablePan() : r.disablePan();
}, [u, S, y]), Y(() => {
const r = (o) => {
const a = o.target, f = L(a);
f && (P(f) && v ? v(f) : l && l(f));
}, t = (o) => {
const a = o.target, f = L(a);
f && (o.type === "mouseover" && h ? h(f) : g && g(f));
}, s = document.querySelectorAll(
".seatmap__svg circle, .seatmap__svg path, .seatmap__svg ellipse"
);
return s.forEach((o) => {
o.removeEventListener("pointerdown", r), o.removeEventListener("mouseover", t), o.removeEventListener("mouseout", t), o.addEventListener("pointerdown", r), o.addEventListener("mouseover", t), o.addEventListener("mouseout", t);
}), () => {
s.forEach((o) => {
o.removeEventListener("pointerdown", r), o.removeEventListener("mouseover", t), o.removeEventListener("mouseout", t);
});
};
}, [
v,
l,
c,
u,
L,
P
]), Y(() => {
if (!u) return;
const r = c.filter((t) => {
const s = d == null ? void 0 : d.availableSeats.find(
(o) => o.id === t.id
);
return !le.isEqual(t, s);
});
for (const t of r) {
const s = document.querySelector(
t.cssSelector
);
s && w(s, t);
}
}, [c, d == null ? void 0 : d.availableSeats, w, u]), Y(() => {
let r = m || [];
m && (d != null && d.selectedSeatIds) && (r = m.filter(
(t) => d.selectedSeatIds && !d.selectedSeatIds.includes(t)
).concat(
d.selectedSeatIds.filter(
(t) => !m.includes(t)
)
));
for (const t of r) {
const s = c.find((a) => a.id === t);
if (!s) continue;
const o = document.querySelector(
s.cssSelector
);
o && w(o, s);
}
}, [
c,
d == null ? void 0 : d.selectedSeatIds,
m,
w
]);
const F = (r) => r && typeof r == "object" && "style" in r ? (console.log("Control is an object with style", r.style), /* @__PURE__ */ i.jsx(
"div",
{
className: "seatmap__action-group" + (r.style === "none" ? " seatmap__action-group--unstyled" : ""),
children: r.control
}
)) : /* @__PURE__ */ i.jsx("div", { className: "seatmap__action-group", children: r }), q = z(() => (N(!0), /* @__PURE__ */ i.jsx(
"div",
{
className: "seatmap__svg",
dangerouslySetInnerHTML: { __html: u }
}
)), [u]);
return z(() => {
const r = () => {
y().zoomIn();
}, t = () => {
const o = y();
o.resize(), o.center(), o.fit();
}, s = () => {
y().zoomOut();
};
return /* @__PURE__ */ i.jsxs("div", { className: "seatmap", children: [
/* @__PURE__ */ i.jsxs("div", { className: "seatmap__actions seatmap__actions--left", children: [
j && /* @__PURE__ */ i.jsxs("div", { className: "seatmap__action-group", children: [
/* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action",
onClick: r,
title: "Zoom In",
children: /* @__PURE__ */ i.jsxs(
"svg",
{
className: "seatmap__icon seatmap__icon--zoom-in",
viewBox: "0 -960 960 960",
children: [
/* @__PURE__ */ i.jsx("title", { children: "Zoom In" }),
/* @__PURE__ */ i.jsx("path", { d: "M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z" })
]
}
)
}
),
/* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action",
onClick: t,
title: "Reset Zoom",
children: /* @__PURE__ */ i.jsxs(
"svg",
{
className: "seatmap__icon seatmap__icon--reset",
viewBox: "0 -960 960 960",
children: [
/* @__PURE__ */ i.jsx("title", { children: "Reset zoom" }),
/* @__PURE__ */ i.jsx("path", { d: "M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z" })
]
}
)
}
),
/* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action",
onClick: s,
title: "Zoom Out",
children: /* @__PURE__ */ i.jsxs(
"svg",
{
className: "seatmap__icon seatmap__icon--zoom-out",
viewBox: "0 -960 960 960",
children: [
/* @__PURE__ */ i.jsx("title", { children: "Zoom Out" }),
/* @__PURE__ */ i.jsx("path", { d: "M200-440v-80h560v80H200Z" })
]
}
)
}
)
] }),
R == null ? void 0 : R.map((o) => F(o))
] }),
q,
/* @__PURE__ */ i.jsx("div", { className: "seatmap__actions seatmap__actions--right", children: E == null ? void 0 : E.map((o) => F(o)) })
] });
}, [
R,
E,
y,
q,
j
]);
}, se = ({
availableSeats: c,
selectedSeatIds: m,
svg: b,
displayGroupMapping: l,
onSeatSelect: v,
onSeatDeselect: h,
rightControls: g,
leftControls: S,
withDragSelection: j = !0,
withGroupSelection: R = !0
}) => {
const [E, u] = H(null), [p, k] = H(null), N = (t, s) => {
var f;
const o = (f = t == null ? void 0 : t.selectionGroups) == null ? void 0 : f[s];
return typeof o == "object" ? c.filter(
(O) => d(O, t, s) && d(O, t, o.parent)
) : c.filter((O) => d(O, t, s));
}, d = (t, s, o) => {
var O, C;
let a = (O = t.selectionGroups) == null ? void 0 : O[o];
typeof a == "object" && (a = a.value);
const f = (C = s.selectionGroups) == null ? void 0 : C[o];
return typeof f == "object" ? a === f.value : a === f;
}, P = (t, s) => {
const o = c.find((f) => f.id === t.id);
if (!o) return;
let a = [o];
if (E === "group" && p) {
const f = N(o, p);
a = [...a, ...f];
}
s && v ? v(a) : !s && h && h(a);
}, L = (t) => {
const s = [];
t.added.forEach((o) => {
const a = c.find(
(f) => o.matches(f.cssSelector)
);
a && (o.classList.add("seat--selected"), o.classList.remove("seat--available"), s.push(a));
}), s.length > 0 && v && v(s);
}, y = (t) => {
t.added.forEach((s) => {
s.classList.add("seat--selected"), s.classList.remove("seat--available");
}), t.removed.forEach((s) => {
s.classList.remove("seat--selected"), s.classList.add("seat--available");
});
}, w = (t, s) => {
const o = c.find((f) => f.id === t.id);
if (!o) return;
const a = document.querySelector(
o.cssSelector
);
if (a && (s ? a.classList.add("seat--hover") : a.classList.remove("seat--hover"), E === "group" && p)) {
const f = N(o, p);
for (const O of f) {
const C = document.querySelector(
O.cssSelector
);
C && (s ? C.classList.add("seat--hover") : C.classList.remove("seat--hover"));
}
}
}, F = z(
() => c.map((t) => ({
id: t.id,
cssSelector: t.cssSelector,
color: t.displayGroup && typeof (l == null ? void 0 : l[t.displayGroup]) == "string" ? l == null ? void 0 : l[t.displayGroup] : void 0,
icon: t.displayGroup && typeof (l == null ? void 0 : l[t.displayGroup]) != "string" ? l == null ? void 0 : l[t.displayGroup] : void 0,
selected: !1
})),
[c, l]
), q = z(() => {
const t = [];
if (R) {
const s = c.map((a) => {
if (a.selectionGroups) return Object.keys(a.selectionGroups);
}).flat(), o = Array.from(new Set(s)).filter(
(a) => a !== void 0
);
t.push(
...o.map((a) => /* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action" + (E === "group" && p === a ? " seatmap__action--selected" : ""),
onClick: () => {
E === "group" && p === a ? (u(null), k(null)) : (u("group"), k(a));
},
children: Array.from(a)[0]
},
a
))
);
}
return j && t.push(
/* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action" + (E === "drag" ? " seatmap__action--selected" : ""),
onClick: () => u(E === "drag" ? null : "drag"),
title: "Lasso select",
children: /* @__PURE__ */ i.jsxs(
"svg",
{
className: "seatmap__icon seatmap__icon--zoom-in",
viewBox: "0 -960 960 960",
children: [
/* @__PURE__ */ i.jsx("title", { children: "Lasso select" }),
/* @__PURE__ */ i.jsx("path", { d: "m161-516-80-8q6-46 20.5-89.5T141-696l68 42q-20 31-31.5 66T161-516Zm36 316q-33-32-57-70.5T101-352l76-26q12 35 31 65.5t45 56.5l-56 56Zm110-552-42-68q39-25 82.5-39.5T437-880l8 80q-37 5-72 16.5T307-752ZM479-82q-35 0-69.5-5.5T343-106l26-76q27 9 54 14.5t56 5.5v80Zm226-626q-26-26-56.5-45T583-784l26-76q43 15 81.5 39t70.5 57l-56 56Zm86 594L679-226v104h-80v-240h240v80H735l112 112-56 56Zm8-368q0-29-5.5-56T779-592l76-26q13 32 18.5 66.5T879-482h-80Z" })
]
}
)
},
"multi-select"
)
), t;
}, [
c,
p,
E,
j,
R
]), r = z(() => {
const t = [];
return q && q.length && t.push(q), S && S.length && t.push(...S), t;
}, [S, q]);
return /* @__PURE__ */ i.jsxs(i.Fragment, { children: [
E === "drag" && /* @__PURE__ */ i.jsx(
ue,
{
container: document.body,
dragContainer: window,
selectableTargets: [".seatmap__svg .seat--available"],
selectByClick: !1,
selectFromInside: !0,
continueSelect: !0,
toggleContinueSelect: "shift",
keyContainer: window,
hitRate: 100,
onSelect: y,
onSelectEnd: L
}
),
/* @__PURE__ */ i.jsx(
_e,
{
svg: b,
availableSeats: F,
selectedSeatIds: m,
onSeatSelect: (t) => P(t, !0),
onSeatDeselect: (t) => P(t, !1),
onSeatHover: (t) => w(t, !0),
onSeatHoverEnd: (t) => w(t, !1),
leftControls: r,
rightControls: g,
allowDragAndPan: E !== "drag"
}
)
] });
}, Se = ({
seats: c,
value: m,
onChange: b,
svg: l,
displayGroupMapping: v,
leftControls: h,
rightControls: g,
withDragSelection: S,
withGroupSelection: j
}) => {
const R = (u, p) => {
let k = [...m || []];
for (const N of u)
p ? k = [...k, N] : k = k.filter((d) => d !== N);
b && b(k);
}, E = z(
() => c.map((u) => ({
...u,
cssSelector: u.cssSelector || ""
})),
[c]
);
if (l)
return /* @__PURE__ */ i.jsx(
se,
{
availableSeats: E,
selectedSeatIds: m,
svg: l,
onSeatSelect: (u) => R(
u.map((p) => p.id),
!0
),
onSeatDeselect: (u) => R(
u.map((p) => p.id),
!1
),
displayGroupMapping: v,
leftControls: h,
rightControls: g,
withDragSelection: S,
withGroupSelection: j
}
);
}, je = ({
seats: c,
selectedSeatIds: m,
onClick: b,
svg: l,
displayGroupMapping: v,
leftControls: h,
rightControls: g
}) => {
const S = z(
() => c.map((j) => ({
...j,
cssSelector: j.cssSelector || ""
})),
[c]
);
if (l)
return /* @__PURE__ */ i.jsx(
se,
{
availableSeats: S,
selectedSeatIds: m,
svg: l,
onSeatSelect: (j) => {
b && b(j.map((R) => R.id)[0], !0);
},
onSeatDeselect: (j) => {
b && b(j.map((R) => R.id)[0], !1);
},
displayGroupMapping: v,
leftControls: h,
rightControls: g,
withDragSelection: !1,
withGroupSelection: !1
}
);
};
export {
je as SeatmapAccordion,
Se as SeatmapInput
};