react-svg-seatmap
Version:
React components that render a highly-customizable seatmap based on a provided SVG
784 lines (783 loc) • 23.8 kB
JavaScript
import ae, { useRef as ce, useEffect as Y, useState as U, useCallback as W, useMemo as I } from "react";
import ie from "svg-pan-zoom";
import le from "lodash";
import ue from "react-selecto";
var H = { exports: {} }, V = {};
/**
* @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 V;
te = 1;
var c = Symbol.for("react.transitional.element"), f = Symbol.for("react.fragment");
function p(l, d, h) {
var k = null;
if (h !== void 0 && (k = "" + h), d.key !== void 0 && (k = "" + d.key), "key" in d) {
h = {};
for (var S in d)
S !== "key" && (h[S] = d[S]);
} else h = d;
return d = h.ref, {
$$typeof: c,
type: l,
key: k,
ref: d !== void 0 ? d : null,
props: h
};
}
return V.Fragment = f, V.jsx = p, V.jsxs = p, V;
}
var $ = {};
/**
* @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 === r ? null : e.displayName || e.name || null;
if (typeof e == "string") return e;
switch (e) {
case O:
return "Fragment";
case R:
return "Profiler";
case F:
return "StrictMode";
case y:
return "Suspense";
case z:
return "SuspenseList";
case t:
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 q:
return "Portal";
case P:
return (e.displayName || "Context") + ".Provider";
case C:
return (e._context.displayName || "Context") + ".Consumer";
case N:
var n = e.render;
return e = e.displayName, e || (e = n.displayName || n.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
case s:
return n = e.displayName || null, n !== null ? n : c(e.type) || "Memo";
case m:
n = e._payload, e = e._init;
try {
return c(e(n));
} catch {
}
}
return null;
}
function f(e) {
return "" + e;
}
function p(e) {
try {
f(e);
var n = !1;
} catch {
n = !0;
}
if (n) {
n = console;
var v = n.error, T = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
return v.call(
n,
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
T
), f(e);
}
}
function l(e) {
if (e === O) return "<>";
if (typeof e == "object" && e !== null && e.$$typeof === m)
return "<...>";
try {
var n = c(e);
return n ? "<" + n + ">" : "<...>";
} catch {
return "<...>";
}
}
function d() {
var e = a.A;
return e === null ? null : e.getOwner();
}
function h() {
return Error("react-stack-top-frame");
}
function k(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 v() {
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
));
}
v.isReactWarning = !0, Object.defineProperty(e, "key", {
get: v,
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 x(e, n, v, T, Z, L, G, B) {
return v = L.ref, e = {
$$typeof: _,
type: e,
key: n,
props: L,
_owner: Z
}, (v !== void 0 ? v : 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 b(e, n, v, T, Z, L, G, B) {
var g = n.children;
if (g !== void 0)
if (T)
if (w(g)) {
for (T = 0; T < g.length; T++)
u(g[T]);
Object.freeze && Object.freeze(g);
} 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(g);
if (o.call(n, "key")) {
g = c(e);
var D = Object.keys(n).filter(function(ne) {
return ne !== "key";
});
T = 0 < D.length ? "{key: someKey, " + D.join(": ..., ") + ": ...}" : "{key: someKey}", ee[g + T] || (D = 0 < D.length ? "{" + D.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,
g,
D,
g
), ee[g + T] = !0);
}
if (g = null, v !== void 0 && (p(v), g = "" + v), k(n) && (p(n.key), g = "" + n.key), "key" in n) {
v = {};
for (var J in n)
J !== "key" && (v[J] = n[J]);
} else v = n;
return g && S(
v,
typeof e == "function" ? e.displayName || e.name || "Unknown" : e
), x(
e,
g,
L,
Z,
d(),
v,
G,
B
);
}
function u(e) {
typeof e == "object" && e !== null && e.$$typeof === _ && e._store && (e._store.validated = 1);
}
var E = ae, _ = Symbol.for("react.transitional.element"), q = Symbol.for("react.portal"), O = Symbol.for("react.fragment"), F = Symbol.for("react.strict_mode"), R = Symbol.for("react.profiler"), C = Symbol.for("react.consumer"), P = Symbol.for("react.context"), N = Symbol.for("react.forward_ref"), y = Symbol.for("react.suspense"), z = Symbol.for("react.suspense_list"), s = Symbol.for("react.memo"), m = Symbol.for("react.lazy"), t = Symbol.for("react.activity"), r = Symbol.for("react.client.reference"), a = E.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, o = Object.prototype.hasOwnProperty, w = Array.isArray, A = console.createTask ? console.createTask : function() {
return null;
};
E = {
"react-stack-bottom-frame": function(e) {
return e();
}
};
var X, M = {}, Q = E["react-stack-bottom-frame"].bind(
E,
h
)(), K = A(l(h)), ee = {};
$.Fragment = O, $.jsx = function(e, n, v, T, Z) {
var L = 1e4 > a.recentlyCreatedOwnerStacks++;
return b(
e,
n,
v,
!1,
T,
Z,
L ? Error("react-stack-top-frame") : Q,
L ? A(l(e)) : K
);
}, $.jsxs = function(e, n, v, T, Z) {
var L = 1e4 > a.recentlyCreatedOwnerStacks++;
return b(
e,
n,
v,
!0,
T,
Z,
L ? Error("react-stack-top-frame") : Q,
L ? A(l(e)) : K
);
};
}()), $;
}
var se;
function de() {
return se || (se = 1, process.env.NODE_ENV === "production" ? H.exports = fe() : H.exports = me()), H.exports;
}
var i = de();
const ve = (c) => {
const f = ce(void 0);
return Y(() => {
f.current = c;
}), f.current;
}, _e = ({
availableSeats: c,
selectedSeatIds: f,
svg: p,
onSeatSelect: l,
onSeatDeselect: d,
onSeatHover: h,
onSeatHoverEnd: k,
allowDragAndPan: S = !0,
showZoomControls: j = !0,
leftControls: x,
rightControls: b
}) => {
const [u, E] = U(""), [_, q] = U(!1), [O, F] = U(!1), R = ve({
svgString: u,
selectedSeatIds: f,
availableSeats: c
}), C = W(
(t) => f && f.includes(t.id),
[f]
), P = W(
(t) => c.find((r) => {
try {
return t.matches(r.cssSelector);
} catch {
return !1;
}
}),
[c]
), N = W(
() => ie(".seatmap__svg svg", {
dblClickZoomEnabled: !1,
mouseWheelZoomEnabled: !0,
zoomScaleSensitivity: 0.5
}),
[]
);
Y(() => {
fetch(p).then((t) => {
if (t.ok)
return t.text();
throw new Error("Unable to fetch SVG");
}).then((t) => E(t)).catch((t) => {
q(!0), console.error("Failed to load SVG:", t);
});
}, [p]);
const y = W(
(t, r) => {
r ? (t.classList.remove("seat--unavailable"), f != null && f.includes(r.id) ? (t.classList.add("seat--selected"), t.classList.remove("seat--available")) : (t.classList.add("seat--available"), t.classList.remove("seat--selected")), r.color && t.setAttribute(
"style",
`stroke: ${r.color} !important;${C(r) ? " fill: " + r.color + " !important;" : ""}`
)) : (t.classList.add("seat--unavailable"), t.classList.remove("seat--available"), t.classList.remove("seat--selected"));
},
[C, f]
);
Y(() => {
if (!u || _) {
console.error("No SVG, exiting");
return;
}
O && (document.querySelectorAll(
".seatmap__svg circle, .seatmap__svg path, .seatmap__svg ellipse"
).forEach((r) => {
const a = P(r);
y(r, a);
}), F(!1));
}, [
O,
P,
y,
u,
_
]), Y(() => {
if (!u || _) return;
const t = N();
t.resize(), t.center(), t.fit();
}, [u, N, _]), Y(() => {
if (!u || _) return;
const t = N();
S ? t.enablePan() : t.disablePan();
}, [u, S, N, _]), Y(() => {
const t = (o) => {
const w = o.target, A = P(w);
A && (C(A) && d ? d(A) : l && l(A));
}, r = (o) => {
const w = o.target, A = P(w);
A && (o.type === "mouseover" && h ? h(A) : k && k(A));
}, a = document.querySelectorAll(
".seatmap__svg circle, .seatmap__svg path, .seatmap__svg ellipse"
);
return a.forEach((o) => {
o.removeEventListener("pointerdown", t), o.removeEventListener("mouseover", r), o.removeEventListener("mouseout", r), o.addEventListener("pointerdown", t), o.addEventListener("mouseover", r), o.addEventListener("mouseout", r);
}), () => {
a.forEach((o) => {
o.removeEventListener("pointerdown", t), o.removeEventListener("mouseover", r), o.removeEventListener("mouseout", r);
});
};
}, [
d,
l,
c,
u,
P,
C
]), Y(() => {
if (!u || _) return;
const t = c.filter((r) => {
const a = R == null ? void 0 : R.availableSeats.find(
(o) => o.id === r.id
);
return !le.isEqual(r, a);
});
for (const r of t) {
const a = document.querySelector(
r.cssSelector
);
a && y(a, r);
}
}, [
c,
R == null ? void 0 : R.availableSeats,
y,
u,
_
]), Y(() => {
let t = f || [];
f && (R != null && R.selectedSeatIds) && (t = f.filter(
(r) => R.selectedSeatIds && !R.selectedSeatIds.includes(r)
).concat(
R.selectedSeatIds.filter(
(r) => !f.includes(r)
)
));
for (const r of t) {
const a = c.find((w) => w.id === r);
if (!a) continue;
const o = document.querySelector(
a.cssSelector
);
o && y(o, a);
}
}, [
c,
R == null ? void 0 : R.selectedSeatIds,
f,
y
]);
const z = (t) => t && typeof t == "object" && "style" in t ? /* @__PURE__ */ i.jsx(
"div",
{
className: "seatmap__action-group" + (t.style === "none" ? " seatmap__action-group--unstyled" : ""),
children: t.control
}
) : /* @__PURE__ */ i.jsx("div", { className: "seatmap__action-group", children: t }), s = I(() => (F(!0), /* @__PURE__ */ i.jsx(
"div",
{
className: "seatmap__svg",
dangerouslySetInnerHTML: { __html: u }
}
)), [u]), m = I(() => {
const t = () => {
N().zoomIn();
}, r = () => {
const o = N();
o.resize(), o.center(), o.fit();
}, a = () => {
N().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: t,
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: r,
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: a,
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" })
]
}
)
}
)
] }),
x == null ? void 0 : x.map((o) => z(o))
] }),
s,
/* @__PURE__ */ i.jsx("div", { className: "seatmap__actions seatmap__actions--right", children: b == null ? void 0 : b.map((o) => z(o)) })
] });
}, [
x,
b,
N,
s,
j
]);
return _ ? /* @__PURE__ */ i.jsx("div", { className: "seatmap", children: /* @__PURE__ */ i.jsxs("div", { className: "seatmap__error", children: [
"ERROR: Unable to fetch SVG from the given URL: ",
p
] }) }) : m;
}, oe = ({
availableSeats: c,
selectedSeatIds: f,
svg: p,
displayGroupMapping: l,
onSeatSelect: d,
onSeatDeselect: h,
rightControls: k,
leftControls: S,
withDragSelection: j = !0,
withGroupSelection: x = !0
}) => {
const [b, u] = U(null), [E, _] = U(null), q = (s, m) => {
var a;
const t = (a = s == null ? void 0 : s.selectionGroups) == null ? void 0 : a[m];
return typeof t == "object" ? c.filter(
(o) => O(o, s, m) && O(o, s, t.parent)
) : c.filter((o) => O(o, s, m));
}, O = (s, m, t) => {
var o, w;
let r = (o = s.selectionGroups) == null ? void 0 : o[t];
typeof r == "object" && (r = r.value);
const a = (w = m.selectionGroups) == null ? void 0 : w[t];
return typeof a == "object" ? r === a.value : r === a;
}, F = (s, m) => {
const t = c.find((a) => a.id === s.id);
if (!t) return;
let r = [t];
if (b === "group" && E) {
const a = q(t, E);
r = [...r, ...a];
}
m && d ? d(r) : !m && h && h(r);
}, R = (s) => {
const m = [];
s.added.forEach((t) => {
const r = c.find(
(a) => t.matches(a.cssSelector)
);
r && (t.classList.add("seat--selected"), t.classList.remove("seat--available"), m.push(r));
}), m.length > 0 && d && d(m);
}, C = (s) => {
s.added.forEach((m) => {
m.classList.add("seat--selected"), m.classList.remove("seat--available");
}), s.removed.forEach((m) => {
m.classList.remove("seat--selected"), m.classList.add("seat--available");
});
}, P = (s, m) => {
const t = c.find((a) => a.id === s.id);
if (!t) return;
const r = document.querySelector(
t.cssSelector
);
if (r && (m ? r.classList.add("seat--hover") : r.classList.remove("seat--hover"), b === "group" && E)) {
const a = q(t, E);
for (const o of a) {
const w = document.querySelector(
o.cssSelector
);
w && (m ? w.classList.add("seat--hover") : w.classList.remove("seat--hover"));
}
}
}, N = I(
() => c.map((s) => ({
id: s.id,
cssSelector: s.cssSelector,
color: s.displayGroup && typeof (l == null ? void 0 : l[s.displayGroup]) == "string" ? l == null ? void 0 : l[s.displayGroup] : void 0,
icon: s.displayGroup && typeof (l == null ? void 0 : l[s.displayGroup]) != "string" ? l == null ? void 0 : l[s.displayGroup] : void 0,
selected: !1
})),
[c, l]
), y = I(() => {
const s = [];
if (x) {
const m = c.map((r) => {
if (r.selectionGroups) return Object.keys(r.selectionGroups);
}).flat(), t = Array.from(new Set(m)).filter(
(r) => r !== void 0
);
s.push(
...t.map((r) => /* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action" + (b === "group" && E === r ? " seatmap__action--selected" : ""),
onClick: () => {
b === "group" && E === r ? (u(null), _(null)) : (u("group"), _(r));
},
children: Array.from(r)[0]
},
r
))
);
}
return j && s.push(
/* @__PURE__ */ i.jsx(
"button",
{
type: "button",
className: "seatmap__action" + (b === "drag" ? " seatmap__action--selected" : ""),
onClick: () => u(b === "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"
)
), s;
}, [
c,
E,
b,
j,
x
]), z = I(() => {
const s = [];
return y && y.length && s.push(y), S && S.length && s.push(...S), s;
}, [S, y]);
return /* @__PURE__ */ i.jsxs(i.Fragment, { children: [
b === "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: C,
onSelectEnd: R
}
),
/* @__PURE__ */ i.jsx(
_e,
{
svg: p,
availableSeats: N,
selectedSeatIds: f,
onSeatSelect: (s) => F(s, !0),
onSeatDeselect: (s) => F(s, !1),
onSeatHover: (s) => P(s, !0),
onSeatHoverEnd: (s) => P(s, !1),
leftControls: z,
rightControls: k,
allowDragAndPan: b !== "drag"
}
)
] });
}, Re = ({
seats: c,
value: f,
onChange: p,
svg: l,
displayGroupMapping: d,
leftControls: h,
rightControls: k,
withDragSelection: S,
withGroupSelection: j
}) => {
const x = (u, E) => {
let _ = [...f || []];
for (const q of u)
E ? _ = [..._, q] : _ = _.filter((O) => O !== q);
p && p(_);
}, b = I(
() => c.map((u) => ({
...u,
cssSelector: u.cssSelector || ""
})),
[c]
);
if (l)
return /* @__PURE__ */ i.jsx(
oe,
{
availableSeats: b,
selectedSeatIds: f,
svg: l,
onSeatSelect: (u) => x(
u.map((E) => E.id),
!0
),
onSeatDeselect: (u) => x(
u.map((E) => E.id),
!1
),
displayGroupMapping: d,
leftControls: h,
rightControls: k,
withDragSelection: S,
withGroupSelection: j
}
);
}, Se = ({
seats: c,
selectedSeatIds: f,
onClick: p,
svg: l,
displayGroupMapping: d,
leftControls: h,
rightControls: k
}) => {
const S = I(
() => c.map((j) => ({
...j,
cssSelector: j.cssSelector || ""
})),
[c]
);
if (l)
return /* @__PURE__ */ i.jsx(
oe,
{
availableSeats: S,
selectedSeatIds: f,
svg: l,
onSeatSelect: (j) => {
p && p(j.map((x) => x.id)[0], !0);
},
onSeatDeselect: (j) => {
p && p(j.map((x) => x.id)[0], !1);
},
displayGroupMapping: d,
leftControls: h,
rightControls: k,
withDragSelection: !1,
withGroupSelection: !1
}
);
};
export {
Se as SeatmapAccordion,
Re as SeatmapInput
};