@customerhero/react 2.1.0 → 2.2.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/index.cjs +376 -81
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +373 -77
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
|
|
29
29
|
// src/components/chat-widget.tsx
|
|
30
|
-
var
|
|
30
|
+
var import_react11 = require("react");
|
|
31
31
|
|
|
32
32
|
// src/context.tsx
|
|
33
33
|
var import_react = require("react");
|
|
@@ -118,6 +118,10 @@ function useChat() {
|
|
|
118
118
|
consumePendingPrefill: (0, import_react2.useCallback)(
|
|
119
119
|
() => client.consumePendingPrefill(),
|
|
120
120
|
[client]
|
|
121
|
+
),
|
|
122
|
+
dismissIncidentBanner: (0, import_react2.useCallback)(
|
|
123
|
+
() => client.dismissIncidentBanner(),
|
|
124
|
+
[client]
|
|
121
125
|
)
|
|
122
126
|
};
|
|
123
127
|
}
|
|
@@ -207,7 +211,7 @@ function ChatBubble() {
|
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
// src/components/chat-window.tsx
|
|
210
|
-
var
|
|
214
|
+
var import_react10 = require("react");
|
|
211
215
|
|
|
212
216
|
// src/components/chat-header.tsx
|
|
213
217
|
var import_react5 = require("react");
|
|
@@ -732,9 +736,11 @@ function renderMarkdown(source, opts = {}) {
|
|
|
732
736
|
style: {
|
|
733
737
|
margin: "0 0 8px",
|
|
734
738
|
paddingLeft: 20,
|
|
735
|
-
lineHeight: 1.5
|
|
739
|
+
lineHeight: 1.5,
|
|
740
|
+
listStyleType: "disc",
|
|
741
|
+
listStylePosition: "outside"
|
|
736
742
|
},
|
|
737
|
-
children: block.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: renderInline(l) }, i))
|
|
743
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { style: { display: "list-item" }, children: renderInline(l) }, i))
|
|
738
744
|
},
|
|
739
745
|
key
|
|
740
746
|
);
|
|
@@ -745,9 +751,11 @@ function renderMarkdown(source, opts = {}) {
|
|
|
745
751
|
style: {
|
|
746
752
|
margin: "0 0 8px",
|
|
747
753
|
paddingLeft: 22,
|
|
748
|
-
lineHeight: 1.5
|
|
754
|
+
lineHeight: 1.5,
|
|
755
|
+
listStyleType: "decimal",
|
|
756
|
+
listStylePosition: "outside"
|
|
749
757
|
},
|
|
750
|
-
children: block.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: renderInline(l) }, i))
|
|
758
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { style: { display: "list-item" }, children: renderInline(l) }, i))
|
|
751
759
|
},
|
|
752
760
|
key
|
|
753
761
|
);
|
|
@@ -950,11 +958,11 @@ function MessageStatusPill({
|
|
|
950
958
|
const containerStyle = {
|
|
951
959
|
display: "flex",
|
|
952
960
|
alignItems: "center",
|
|
961
|
+
justifyContent: "flex-end",
|
|
953
962
|
gap: 4,
|
|
954
963
|
marginTop: 2,
|
|
955
964
|
fontSize: 11,
|
|
956
|
-
color: failed ? "#b91c1c" : "#888"
|
|
957
|
-
alignSelf: "flex-end"
|
|
965
|
+
color: failed ? "#b91c1c" : "#888"
|
|
958
966
|
};
|
|
959
967
|
const labelKey = status === "sending" ? "status_sending" : status === "sent" ? "status_sent" : "status_failed";
|
|
960
968
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: containerStyle, "aria-live": "polite", children: [
|
|
@@ -1263,13 +1271,20 @@ function Message({
|
|
|
1263
1271
|
};
|
|
1264
1272
|
const linkColor = isUser ? "#ffffff" : config.primaryColor;
|
|
1265
1273
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AnimatedMessage, { isUser, animate, reduced, children: [
|
|
1266
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1274
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1275
|
+
"div",
|
|
1276
|
+
{
|
|
1277
|
+
style: bubbleStyle,
|
|
1278
|
+
"data-streaming-bubble": !isUser && message.streaming ? "true" : void 0,
|
|
1279
|
+
children: isUser ? message.content : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1280
|
+
renderMarkdown(message.content, {
|
|
1281
|
+
sources: message.sources,
|
|
1282
|
+
linkColor
|
|
1283
|
+
}),
|
|
1284
|
+
message.streaming && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StreamingCursor, { reduced })
|
|
1285
|
+
] })
|
|
1286
|
+
}
|
|
1287
|
+
),
|
|
1273
1288
|
isUser && message.status && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageStatusPill, { status: message.status, t }),
|
|
1274
1289
|
!isUser && message.blocks?.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1275
1290
|
BlockRenderer,
|
|
@@ -1354,16 +1369,51 @@ function ChatMessages() {
|
|
|
1354
1369
|
t
|
|
1355
1370
|
} = useChat();
|
|
1356
1371
|
const reduced = useReducedMotion();
|
|
1372
|
+
const containerRef = (0, import_react7.useRef)(null);
|
|
1357
1373
|
const messagesEndRef = (0, import_react7.useRef)(null);
|
|
1358
1374
|
const isFirstRender = (0, import_react7.useRef)(true);
|
|
1359
1375
|
const prevMessageCount = (0, import_react7.useRef)(0);
|
|
1376
|
+
const stickRef = (0, import_react7.useRef)(true);
|
|
1377
|
+
const suppressScrollRef = (0, import_react7.useRef)(false);
|
|
1378
|
+
const autoScrollTo = (top) => {
|
|
1379
|
+
const el = containerRef.current;
|
|
1380
|
+
if (!el) return;
|
|
1381
|
+
suppressScrollRef.current = true;
|
|
1382
|
+
el.scrollTop = top;
|
|
1383
|
+
requestAnimationFrame(() => {
|
|
1384
|
+
suppressScrollRef.current = false;
|
|
1385
|
+
});
|
|
1386
|
+
};
|
|
1387
|
+
const handleScroll = () => {
|
|
1388
|
+
if (suppressScrollRef.current) return;
|
|
1389
|
+
const el = containerRef.current;
|
|
1390
|
+
if (!el) return;
|
|
1391
|
+
const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
1392
|
+
stickRef.current = distFromBottom < 60;
|
|
1393
|
+
};
|
|
1360
1394
|
(0, import_react7.useEffect)(() => {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1395
|
+
const el = containerRef.current;
|
|
1396
|
+
if (!el) return;
|
|
1397
|
+
const lastMsg2 = messages[messages.length - 1];
|
|
1398
|
+
if (messages.length > prevMessageCount.current && lastMsg2?.role === "user") {
|
|
1399
|
+
stickRef.current = true;
|
|
1366
1400
|
}
|
|
1401
|
+
if (!stickRef.current && !isFirstRender.current) {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
const streamingBubble = el.querySelector(
|
|
1405
|
+
"[data-streaming-bubble='true']"
|
|
1406
|
+
);
|
|
1407
|
+
let target = el.scrollHeight - el.clientHeight;
|
|
1408
|
+
if (streamingBubble && streamingBubble.offsetHeight > el.clientHeight - 24) {
|
|
1409
|
+
const containerTop = el.getBoundingClientRect().top;
|
|
1410
|
+
const bubbleTop = streamingBubble.getBoundingClientRect().top;
|
|
1411
|
+
const TOP_GAP = 16;
|
|
1412
|
+
target = el.scrollTop + (bubbleTop - containerTop) - TOP_GAP;
|
|
1413
|
+
stickRef.current = false;
|
|
1414
|
+
}
|
|
1415
|
+
autoScrollTo(target);
|
|
1416
|
+
isFirstRender.current = false;
|
|
1367
1417
|
}, [messages, isLoading, reduced]);
|
|
1368
1418
|
const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
|
|
1369
1419
|
(0, import_react7.useEffect)(() => {
|
|
@@ -1386,7 +1436,7 @@ function ChatMessages() {
|
|
|
1386
1436
|
};
|
|
1387
1437
|
const lastMsg = messages[messages.length - 1];
|
|
1388
1438
|
const waitingForFirstToken = isLoading && (lastMsg?.role !== "bot" || lastMsg.streaming !== true);
|
|
1389
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: containerStyle, children: [
|
|
1439
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: containerRef, style: containerStyle, onScroll: handleScroll, children: [
|
|
1390
1440
|
messages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1391
1441
|
Message,
|
|
1392
1442
|
{
|
|
@@ -1505,12 +1555,23 @@ function ChatInput() {
|
|
|
1505
1555
|
const [dragActive, setDragActive] = (0, import_react8.useState)(false);
|
|
1506
1556
|
const [transientError, setTransientError] = (0, import_react8.useState)(null);
|
|
1507
1557
|
const fileInputRef = (0, import_react8.useRef)(null);
|
|
1558
|
+
const textInputRef = (0, import_react8.useRef)(null);
|
|
1508
1559
|
const menuRef = (0, import_react8.useRef)(null);
|
|
1509
1560
|
const menuButtonRef = (0, import_react8.useRef)(null);
|
|
1510
1561
|
const dragCounterRef = (0, import_react8.useRef)(0);
|
|
1511
1562
|
(0, import_react8.useEffect)(() => {
|
|
1512
1563
|
setCaptureSupported((0, import_js2.canCaptureScreenshot)());
|
|
1513
1564
|
}, []);
|
|
1565
|
+
(0, import_react8.useEffect)(() => {
|
|
1566
|
+
const id = requestAnimationFrame(() => textInputRef.current?.focus());
|
|
1567
|
+
return () => cancelAnimationFrame(id);
|
|
1568
|
+
}, []);
|
|
1569
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
1570
|
+
const el = textInputRef.current;
|
|
1571
|
+
if (!el) return;
|
|
1572
|
+
el.style.height = "auto";
|
|
1573
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
1574
|
+
}, [value]);
|
|
1514
1575
|
(0, import_react8.useEffect)(() => {
|
|
1515
1576
|
if (pendingPrefill === null) return;
|
|
1516
1577
|
const text = consumePendingPrefill();
|
|
@@ -1688,19 +1749,27 @@ function ChatInput() {
|
|
|
1688
1749
|
};
|
|
1689
1750
|
const rowStyle = {
|
|
1690
1751
|
display: "flex",
|
|
1691
|
-
|
|
1752
|
+
// Anchor the icon buttons to the bottom of the row so a multi-line
|
|
1753
|
+
// textarea grows upward without dragging them along.
|
|
1754
|
+
alignItems: "flex-end",
|
|
1692
1755
|
gap: 8
|
|
1693
1756
|
};
|
|
1757
|
+
const TEXTAREA_MAX_HEIGHT = 140;
|
|
1694
1758
|
const inputStyle = {
|
|
1695
1759
|
flex: 1,
|
|
1696
1760
|
border: "1px solid #e0e0e0",
|
|
1697
|
-
borderRadius:
|
|
1761
|
+
borderRadius: 18,
|
|
1698
1762
|
padding: "10px 16px",
|
|
1699
1763
|
fontSize: 14,
|
|
1764
|
+
lineHeight: 1.4,
|
|
1700
1765
|
outline: "none",
|
|
1701
1766
|
background: "#fafafa",
|
|
1702
1767
|
color: config.textColor,
|
|
1703
|
-
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1768
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
1769
|
+
resize: "none",
|
|
1770
|
+
overflowY: "auto",
|
|
1771
|
+
maxHeight: TEXTAREA_MAX_HEIGHT,
|
|
1772
|
+
boxSizing: "border-box"
|
|
1704
1773
|
};
|
|
1705
1774
|
const sendButtonStyle = {
|
|
1706
1775
|
width: 36,
|
|
@@ -1758,6 +1827,7 @@ function ChatInput() {
|
|
|
1758
1827
|
fontSize: 14,
|
|
1759
1828
|
color: "#333",
|
|
1760
1829
|
textAlign: "left",
|
|
1830
|
+
whiteSpace: "nowrap",
|
|
1761
1831
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1762
1832
|
};
|
|
1763
1833
|
const dropOverlayStyle = {
|
|
@@ -1884,9 +1954,10 @@ function ChatInput() {
|
|
|
1884
1954
|
}
|
|
1885
1955
|
),
|
|
1886
1956
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1887
|
-
"
|
|
1957
|
+
"textarea",
|
|
1888
1958
|
{
|
|
1889
|
-
|
|
1959
|
+
ref: textInputRef,
|
|
1960
|
+
rows: 1,
|
|
1890
1961
|
value,
|
|
1891
1962
|
onChange: (e) => setValue(e.target.value),
|
|
1892
1963
|
onKeyDown: handleKeyDown,
|
|
@@ -2159,8 +2230,231 @@ function Spinner2() {
|
|
|
2159
2230
|
] });
|
|
2160
2231
|
}
|
|
2161
2232
|
|
|
2162
|
-
// src/components/
|
|
2233
|
+
// src/components/incident-banner.tsx
|
|
2234
|
+
var import_react9 = require("react");
|
|
2163
2235
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2236
|
+
var PALETTE = {
|
|
2237
|
+
info: {
|
|
2238
|
+
bg: "#EFF6FF",
|
|
2239
|
+
fg: "#1E3A8A",
|
|
2240
|
+
fgMuted: "#3B5BA9",
|
|
2241
|
+
accent: "#2563EB",
|
|
2242
|
+
iconBg: "#DBEAFE",
|
|
2243
|
+
iconFg: "#2563EB"
|
|
2244
|
+
},
|
|
2245
|
+
warning: {
|
|
2246
|
+
bg: "#FFFBEB",
|
|
2247
|
+
fg: "#78350F",
|
|
2248
|
+
fgMuted: "#92541A",
|
|
2249
|
+
accent: "#B45309",
|
|
2250
|
+
iconBg: "#FEF3C7",
|
|
2251
|
+
iconFg: "#B45309"
|
|
2252
|
+
},
|
|
2253
|
+
outage: {
|
|
2254
|
+
bg: "#FEF2F2",
|
|
2255
|
+
fg: "#991B1B",
|
|
2256
|
+
fgMuted: "#B23A3A",
|
|
2257
|
+
accent: "#DC2626",
|
|
2258
|
+
iconBg: "#FEE2E2",
|
|
2259
|
+
iconFg: "#DC2626"
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
function SeverityIcon({
|
|
2263
|
+
severity,
|
|
2264
|
+
color
|
|
2265
|
+
}) {
|
|
2266
|
+
const props = {
|
|
2267
|
+
width: 14,
|
|
2268
|
+
height: 14,
|
|
2269
|
+
viewBox: "0 0 24 24",
|
|
2270
|
+
fill: "none",
|
|
2271
|
+
stroke: color,
|
|
2272
|
+
strokeWidth: 2.25,
|
|
2273
|
+
strokeLinecap: "round",
|
|
2274
|
+
strokeLinejoin: "round",
|
|
2275
|
+
"aria-hidden": true
|
|
2276
|
+
};
|
|
2277
|
+
if (severity === "outage") {
|
|
2278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...props, children: [
|
|
2279
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2280
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
2281
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
2282
|
+
] });
|
|
2283
|
+
}
|
|
2284
|
+
if (severity === "warning") {
|
|
2285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...props, children: [
|
|
2286
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
|
|
2287
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
|
|
2288
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
|
|
2289
|
+
] });
|
|
2290
|
+
}
|
|
2291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...props, children: [
|
|
2292
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2293
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
|
|
2294
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
|
|
2295
|
+
] });
|
|
2296
|
+
}
|
|
2297
|
+
function IncidentBanner() {
|
|
2298
|
+
const { incidentBanner, incidentBannerDismissed, dismissIncidentBanner, t } = useChat();
|
|
2299
|
+
const [linkHover, setLinkHover] = (0, import_react9.useState)(false);
|
|
2300
|
+
const [closeHover, setCloseHover] = (0, import_react9.useState)(false);
|
|
2301
|
+
if (!incidentBanner || incidentBannerDismissed) return null;
|
|
2302
|
+
const palette = PALETTE[incidentBanner.severity];
|
|
2303
|
+
const wrap = {
|
|
2304
|
+
background: palette.bg,
|
|
2305
|
+
color: palette.fg,
|
|
2306
|
+
padding: "12px 14px 12px 12px",
|
|
2307
|
+
display: "flex",
|
|
2308
|
+
gap: 10,
|
|
2309
|
+
alignItems: "flex-start",
|
|
2310
|
+
fontSize: 13,
|
|
2311
|
+
lineHeight: 1.45,
|
|
2312
|
+
boxShadow: "inset 0 -1px 0 rgba(0,0,0,0.04)"
|
|
2313
|
+
};
|
|
2314
|
+
const iconBadge = {
|
|
2315
|
+
width: 22,
|
|
2316
|
+
height: 22,
|
|
2317
|
+
borderRadius: "50%",
|
|
2318
|
+
background: palette.iconBg,
|
|
2319
|
+
display: "flex",
|
|
2320
|
+
alignItems: "center",
|
|
2321
|
+
justifyContent: "center",
|
|
2322
|
+
flexShrink: 0,
|
|
2323
|
+
marginTop: 1
|
|
2324
|
+
};
|
|
2325
|
+
const titleStyle = {
|
|
2326
|
+
margin: 0,
|
|
2327
|
+
fontSize: 13,
|
|
2328
|
+
fontWeight: 600,
|
|
2329
|
+
letterSpacing: "-0.005em"
|
|
2330
|
+
};
|
|
2331
|
+
const bodyStyle = {
|
|
2332
|
+
margin: "3px 0 0",
|
|
2333
|
+
fontSize: 12.5,
|
|
2334
|
+
color: palette.fgMuted
|
|
2335
|
+
};
|
|
2336
|
+
const footerRowStyle = {
|
|
2337
|
+
display: "flex",
|
|
2338
|
+
flexWrap: "wrap",
|
|
2339
|
+
alignItems: "center",
|
|
2340
|
+
gap: 10,
|
|
2341
|
+
marginTop: 8
|
|
2342
|
+
};
|
|
2343
|
+
const etaStyle = {
|
|
2344
|
+
display: "inline-block",
|
|
2345
|
+
padding: "2px 8px",
|
|
2346
|
+
borderRadius: 4,
|
|
2347
|
+
fontSize: 11,
|
|
2348
|
+
fontWeight: 500,
|
|
2349
|
+
color: palette.accent,
|
|
2350
|
+
background: palette.iconBg,
|
|
2351
|
+
lineHeight: 1.4
|
|
2352
|
+
};
|
|
2353
|
+
const linkStyle = {
|
|
2354
|
+
display: "inline-flex",
|
|
2355
|
+
alignItems: "center",
|
|
2356
|
+
gap: 4,
|
|
2357
|
+
color: palette.accent,
|
|
2358
|
+
textDecoration: linkHover ? "underline" : "none",
|
|
2359
|
+
textUnderlineOffset: 3,
|
|
2360
|
+
fontSize: 12.5,
|
|
2361
|
+
fontWeight: 600
|
|
2362
|
+
};
|
|
2363
|
+
const closeButtonStyle = {
|
|
2364
|
+
background: closeHover ? "rgba(0,0,0,0.06)" : "transparent",
|
|
2365
|
+
border: "none",
|
|
2366
|
+
color: palette.fgMuted,
|
|
2367
|
+
cursor: "pointer",
|
|
2368
|
+
padding: 4,
|
|
2369
|
+
borderRadius: 6,
|
|
2370
|
+
flexShrink: 0,
|
|
2371
|
+
lineHeight: 0,
|
|
2372
|
+
marginTop: -2,
|
|
2373
|
+
marginRight: -2,
|
|
2374
|
+
transition: "background-color 0.12s ease"
|
|
2375
|
+
};
|
|
2376
|
+
const role = incidentBanner.severity === "outage" ? "alert" : "status";
|
|
2377
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { role, style: wrap, children: [
|
|
2378
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: iconBadge, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2379
|
+
SeverityIcon,
|
|
2380
|
+
{
|
|
2381
|
+
severity: incidentBanner.severity,
|
|
2382
|
+
color: palette.iconFg
|
|
2383
|
+
}
|
|
2384
|
+
) }),
|
|
2385
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
2386
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: titleStyle, children: incidentBanner.title }),
|
|
2387
|
+
incidentBanner.body ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: bodyStyle, children: incidentBanner.body }) : null,
|
|
2388
|
+
incidentBanner.eta || incidentBanner.link ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: footerRowStyle, children: [
|
|
2389
|
+
incidentBanner.eta ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: etaStyle, children: incidentBanner.eta }) : null,
|
|
2390
|
+
incidentBanner.link ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2391
|
+
"a",
|
|
2392
|
+
{
|
|
2393
|
+
href: incidentBanner.link.url,
|
|
2394
|
+
target: "_blank",
|
|
2395
|
+
rel: "noopener noreferrer",
|
|
2396
|
+
style: linkStyle,
|
|
2397
|
+
onMouseEnter: () => setLinkHover(true),
|
|
2398
|
+
onMouseLeave: () => setLinkHover(false),
|
|
2399
|
+
children: [
|
|
2400
|
+
incidentBanner.link.label ?? t("incident_default_link_label"),
|
|
2401
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2402
|
+
"svg",
|
|
2403
|
+
{
|
|
2404
|
+
width: "12",
|
|
2405
|
+
height: "12",
|
|
2406
|
+
viewBox: "0 0 24 24",
|
|
2407
|
+
fill: "none",
|
|
2408
|
+
stroke: "currentColor",
|
|
2409
|
+
strokeWidth: "2.5",
|
|
2410
|
+
strokeLinecap: "round",
|
|
2411
|
+
strokeLinejoin: "round",
|
|
2412
|
+
"aria-hidden": "true",
|
|
2413
|
+
children: [
|
|
2414
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
|
|
2415
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "12 5 19 12 12 19" })
|
|
2416
|
+
]
|
|
2417
|
+
}
|
|
2418
|
+
)
|
|
2419
|
+
]
|
|
2420
|
+
}
|
|
2421
|
+
) : null
|
|
2422
|
+
] }) : null
|
|
2423
|
+
] }),
|
|
2424
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2425
|
+
"button",
|
|
2426
|
+
{
|
|
2427
|
+
type: "button",
|
|
2428
|
+
onClick: dismissIncidentBanner,
|
|
2429
|
+
"aria-label": t("incident_dismiss"),
|
|
2430
|
+
style: closeButtonStyle,
|
|
2431
|
+
onMouseEnter: () => setCloseHover(true),
|
|
2432
|
+
onMouseLeave: () => setCloseHover(false),
|
|
2433
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2434
|
+
"svg",
|
|
2435
|
+
{
|
|
2436
|
+
width: "14",
|
|
2437
|
+
height: "14",
|
|
2438
|
+
viewBox: "0 0 24 24",
|
|
2439
|
+
fill: "none",
|
|
2440
|
+
stroke: "currentColor",
|
|
2441
|
+
strokeWidth: "2",
|
|
2442
|
+
strokeLinecap: "round",
|
|
2443
|
+
strokeLinejoin: "round",
|
|
2444
|
+
"aria-hidden": "true",
|
|
2445
|
+
children: [
|
|
2446
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
2447
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
2448
|
+
]
|
|
2449
|
+
}
|
|
2450
|
+
)
|
|
2451
|
+
}
|
|
2452
|
+
)
|
|
2453
|
+
] });
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
// src/components/chat-window.tsx
|
|
2457
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2164
2458
|
function ConfigError({ message, title }) {
|
|
2165
2459
|
const errorStyle = {
|
|
2166
2460
|
flex: 1,
|
|
@@ -2172,8 +2466,8 @@ function ConfigError({ message, title }) {
|
|
|
2172
2466
|
textAlign: "center",
|
|
2173
2467
|
gap: 8
|
|
2174
2468
|
};
|
|
2175
|
-
return /* @__PURE__ */ (0,
|
|
2176
|
-
/* @__PURE__ */ (0,
|
|
2469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: errorStyle, children: [
|
|
2470
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2177
2471
|
"svg",
|
|
2178
2472
|
{
|
|
2179
2473
|
width: "32",
|
|
@@ -2185,14 +2479,14 @@ function ConfigError({ message, title }) {
|
|
|
2185
2479
|
strokeLinecap: "round",
|
|
2186
2480
|
strokeLinejoin: "round",
|
|
2187
2481
|
children: [
|
|
2188
|
-
/* @__PURE__ */ (0,
|
|
2189
|
-
/* @__PURE__ */ (0,
|
|
2190
|
-
/* @__PURE__ */ (0,
|
|
2482
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
2483
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
2484
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
2191
2485
|
]
|
|
2192
2486
|
}
|
|
2193
2487
|
),
|
|
2194
|
-
/* @__PURE__ */ (0,
|
|
2195
|
-
/* @__PURE__ */ (0,
|
|
2488
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
|
|
2489
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
|
|
2196
2490
|
] });
|
|
2197
2491
|
}
|
|
2198
2492
|
function fieldKey(field) {
|
|
@@ -2219,9 +2513,9 @@ function validateField(field, value) {
|
|
|
2219
2513
|
}
|
|
2220
2514
|
function PreChatFormView() {
|
|
2221
2515
|
const { preChatForm, submitPreChatForm, cancelPreChatForm, config, t } = useChat();
|
|
2222
|
-
const [values, setValues] = (0,
|
|
2223
|
-
const [errors, setErrors] = (0,
|
|
2224
|
-
const [submitting, setSubmitting] = (0,
|
|
2516
|
+
const [values, setValues] = (0, import_react10.useState)({});
|
|
2517
|
+
const [errors, setErrors] = (0, import_react10.useState)({});
|
|
2518
|
+
const [submitting, setSubmitting] = (0, import_react10.useState)(false);
|
|
2225
2519
|
if (!preChatForm) return null;
|
|
2226
2520
|
function setValue(key, value) {
|
|
2227
2521
|
setValues((prev) => ({ ...prev, [key]: value }));
|
|
@@ -2317,15 +2611,15 @@ function PreChatFormView() {
|
|
|
2317
2611
|
fontSize: 14,
|
|
2318
2612
|
cursor: "pointer"
|
|
2319
2613
|
};
|
|
2320
|
-
return /* @__PURE__ */ (0,
|
|
2614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2321
2615
|
"form",
|
|
2322
2616
|
{
|
|
2323
2617
|
style: containerStyle,
|
|
2324
2618
|
onSubmit: handleSubmit,
|
|
2325
2619
|
"data-customerhero-prechat-form": true,
|
|
2326
2620
|
children: [
|
|
2327
|
-
preChatForm.title && /* @__PURE__ */ (0,
|
|
2328
|
-
preChatForm.description && /* @__PURE__ */ (0,
|
|
2621
|
+
preChatForm.title && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { style: { fontSize: 16, fontWeight: 600, margin: 0 }, children: preChatForm.title }),
|
|
2622
|
+
preChatForm.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontSize: 13, margin: 0, opacity: 0.8 }, children: preChatForm.description }),
|
|
2329
2623
|
preChatForm.fields.map((field) => {
|
|
2330
2624
|
const k = fieldKey(field);
|
|
2331
2625
|
const v = values[k];
|
|
@@ -2333,12 +2627,12 @@ function PreChatFormView() {
|
|
|
2333
2627
|
const required = "required" in field ? !!field.required : false;
|
|
2334
2628
|
const label = fieldLabel(field);
|
|
2335
2629
|
if (field.kind === "textarea") {
|
|
2336
|
-
return /* @__PURE__ */ (0,
|
|
2337
|
-
/* @__PURE__ */ (0,
|
|
2630
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: labelStyle, children: [
|
|
2631
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
|
|
2338
2632
|
label,
|
|
2339
2633
|
required && " *"
|
|
2340
2634
|
] }),
|
|
2341
|
-
/* @__PURE__ */ (0,
|
|
2635
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2342
2636
|
"textarea",
|
|
2343
2637
|
{
|
|
2344
2638
|
style: { ...inputStyle, minHeight: 80, resize: "vertical" },
|
|
@@ -2347,32 +2641,32 @@ function PreChatFormView() {
|
|
|
2347
2641
|
onChange: (e) => setValue(k, e.target.value)
|
|
2348
2642
|
}
|
|
2349
2643
|
),
|
|
2350
|
-
err && /* @__PURE__ */ (0,
|
|
2644
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
|
|
2351
2645
|
] }, k);
|
|
2352
2646
|
}
|
|
2353
2647
|
if (field.kind === "select") {
|
|
2354
|
-
return /* @__PURE__ */ (0,
|
|
2355
|
-
/* @__PURE__ */ (0,
|
|
2648
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: labelStyle, children: [
|
|
2649
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
|
|
2356
2650
|
label,
|
|
2357
2651
|
required && " *"
|
|
2358
2652
|
] }),
|
|
2359
|
-
/* @__PURE__ */ (0,
|
|
2653
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2360
2654
|
"select",
|
|
2361
2655
|
{
|
|
2362
2656
|
style: inputStyle,
|
|
2363
2657
|
value: v ?? "",
|
|
2364
2658
|
onChange: (e) => setValue(k, e.target.value),
|
|
2365
2659
|
children: [
|
|
2366
|
-
/* @__PURE__ */ (0,
|
|
2367
|
-
field.options.map((opt) => /* @__PURE__ */ (0,
|
|
2660
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "", children: "\u2014" }),
|
|
2661
|
+
field.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
|
|
2368
2662
|
]
|
|
2369
2663
|
}
|
|
2370
2664
|
),
|
|
2371
|
-
err && /* @__PURE__ */ (0,
|
|
2665
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
|
|
2372
2666
|
] }, k);
|
|
2373
2667
|
}
|
|
2374
2668
|
if (field.kind === "consent") {
|
|
2375
|
-
return /* @__PURE__ */ (0,
|
|
2669
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2376
2670
|
"label",
|
|
2377
2671
|
{
|
|
2378
2672
|
style: {
|
|
@@ -2382,7 +2676,7 @@ function PreChatFormView() {
|
|
|
2382
2676
|
gap: 8
|
|
2383
2677
|
},
|
|
2384
2678
|
children: [
|
|
2385
|
-
/* @__PURE__ */ (0,
|
|
2679
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2386
2680
|
"input",
|
|
2387
2681
|
{
|
|
2388
2682
|
type: "checkbox",
|
|
@@ -2391,11 +2685,11 @@ function PreChatFormView() {
|
|
|
2391
2685
|
style: { marginTop: 3 }
|
|
2392
2686
|
}
|
|
2393
2687
|
),
|
|
2394
|
-
/* @__PURE__ */ (0,
|
|
2688
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { style: { fontSize: 13, fontWeight: 400 }, children: [
|
|
2395
2689
|
field.label,
|
|
2396
|
-
field.url && /* @__PURE__ */ (0,
|
|
2690
|
+
field.url && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
2397
2691
|
" ",
|
|
2398
|
-
/* @__PURE__ */ (0,
|
|
2692
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2399
2693
|
"a",
|
|
2400
2694
|
{
|
|
2401
2695
|
href: field.url,
|
|
@@ -2407,7 +2701,7 @@ function PreChatFormView() {
|
|
|
2407
2701
|
)
|
|
2408
2702
|
] })
|
|
2409
2703
|
] }),
|
|
2410
|
-
err && /* @__PURE__ */ (0,
|
|
2704
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
|
|
2411
2705
|
]
|
|
2412
2706
|
},
|
|
2413
2707
|
k
|
|
@@ -2415,12 +2709,12 @@ function PreChatFormView() {
|
|
|
2415
2709
|
}
|
|
2416
2710
|
const inputType = field.kind === "email" ? "email" : field.kind === "phone" ? "tel" : "text";
|
|
2417
2711
|
const maxLength = field.kind === "text" ? field.maxLength : void 0;
|
|
2418
|
-
return /* @__PURE__ */ (0,
|
|
2419
|
-
/* @__PURE__ */ (0,
|
|
2712
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: labelStyle, children: [
|
|
2713
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
|
|
2420
2714
|
label,
|
|
2421
2715
|
required && " *"
|
|
2422
2716
|
] }),
|
|
2423
|
-
/* @__PURE__ */ (0,
|
|
2717
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2424
2718
|
"input",
|
|
2425
2719
|
{
|
|
2426
2720
|
type: inputType,
|
|
@@ -2430,11 +2724,11 @@ function PreChatFormView() {
|
|
|
2430
2724
|
onChange: (e) => setValue(k, e.target.value)
|
|
2431
2725
|
}
|
|
2432
2726
|
),
|
|
2433
|
-
err && /* @__PURE__ */ (0,
|
|
2727
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
|
|
2434
2728
|
] }, k);
|
|
2435
2729
|
}),
|
|
2436
|
-
/* @__PURE__ */ (0,
|
|
2437
|
-
/* @__PURE__ */ (0,
|
|
2730
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: buttonRowStyle, children: [
|
|
2731
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2438
2732
|
"button",
|
|
2439
2733
|
{
|
|
2440
2734
|
type: "button",
|
|
@@ -2444,7 +2738,7 @@ function PreChatFormView() {
|
|
|
2444
2738
|
children: t("action_cancel")
|
|
2445
2739
|
}
|
|
2446
2740
|
),
|
|
2447
|
-
/* @__PURE__ */ (0,
|
|
2741
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "submit", style: submitStyle, disabled: submitting, children: preChatForm.submitLabel })
|
|
2448
2742
|
] })
|
|
2449
2743
|
]
|
|
2450
2744
|
}
|
|
@@ -2453,9 +2747,9 @@ function PreChatFormView() {
|
|
|
2453
2747
|
function ChatWindow() {
|
|
2454
2748
|
const { isOpen, config, configError, t, isRtl, preChatFormVisible } = useChat();
|
|
2455
2749
|
const reduced = useReducedMotion();
|
|
2456
|
-
const [visible, setVisible] = (0,
|
|
2457
|
-
const [shouldRender, setShouldRender] = (0,
|
|
2458
|
-
(0,
|
|
2750
|
+
const [visible, setVisible] = (0, import_react10.useState)(false);
|
|
2751
|
+
const [shouldRender, setShouldRender] = (0, import_react10.useState)(false);
|
|
2752
|
+
(0, import_react10.useEffect)(() => {
|
|
2459
2753
|
if (isOpen) {
|
|
2460
2754
|
setShouldRender(true);
|
|
2461
2755
|
requestAnimationFrame(() => {
|
|
@@ -2504,17 +2798,18 @@ function ChatWindow() {
|
|
|
2504
2798
|
textDecoration: "underline",
|
|
2505
2799
|
textUnderlineOffset: 2
|
|
2506
2800
|
};
|
|
2507
|
-
return /* @__PURE__ */ (0,
|
|
2508
|
-
/* @__PURE__ */ (0,
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
/* @__PURE__ */ (0,
|
|
2512
|
-
/* @__PURE__ */ (0,
|
|
2801
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
|
|
2802
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatHeader, {}),
|
|
2803
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IncidentBanner, {}),
|
|
2804
|
+
configError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ConfigError, { title: t("unable_to_load"), message: configError }) : preChatFormVisible ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreChatFormView, {}) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
2805
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatMessages, {}),
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatSuggestions, {}),
|
|
2807
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatInput, {})
|
|
2513
2808
|
] }),
|
|
2514
|
-
/* @__PURE__ */ (0,
|
|
2809
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: poweredStyle, children: [
|
|
2515
2810
|
t("powered_by"),
|
|
2516
2811
|
" ",
|
|
2517
|
-
/* @__PURE__ */ (0,
|
|
2812
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2518
2813
|
"a",
|
|
2519
2814
|
{
|
|
2520
2815
|
href: "https://customerhero.app",
|
|
@@ -2529,12 +2824,12 @@ function ChatWindow() {
|
|
|
2529
2824
|
}
|
|
2530
2825
|
|
|
2531
2826
|
// src/components/chat-widget.tsx
|
|
2532
|
-
var
|
|
2827
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2533
2828
|
function ChatWidgetInner({ identity }) {
|
|
2534
2829
|
const client = useCustomerHeroClient();
|
|
2535
2830
|
const { configLoaded, configError } = useChat();
|
|
2536
|
-
const prevIdentityRef = (0,
|
|
2537
|
-
(0,
|
|
2831
|
+
const prevIdentityRef = (0, import_react11.useRef)(void 0);
|
|
2832
|
+
(0, import_react11.useEffect)(() => {
|
|
2538
2833
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
2539
2834
|
if (key !== prevIdentityRef.current) {
|
|
2540
2835
|
prevIdentityRef.current = key;
|
|
@@ -2544,13 +2839,13 @@ function ChatWidgetInner({ identity }) {
|
|
|
2544
2839
|
}
|
|
2545
2840
|
}, [identity, client]);
|
|
2546
2841
|
if (!configLoaded || configError) return null;
|
|
2547
|
-
return /* @__PURE__ */ (0,
|
|
2548
|
-
/* @__PURE__ */ (0,
|
|
2549
|
-
/* @__PURE__ */ (0,
|
|
2842
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
2843
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubble, {}),
|
|
2844
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWindow, {})
|
|
2550
2845
|
] });
|
|
2551
2846
|
}
|
|
2552
2847
|
function ChatWidget({ identity, ...config }) {
|
|
2553
|
-
return /* @__PURE__ */ (0,
|
|
2848
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWidgetInner, { identity }) });
|
|
2554
2849
|
}
|
|
2555
2850
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2556
2851
|
0 && (module.exports = {
|