@clivly/chat-widget 0.3.0-next.7 → 0.3.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/README.md +10 -0
- package/dist/browser-mount.cjs +49 -12
- package/dist/browser-mount.mjs +49 -12
- package/dist/browser.cjs +49 -12
- package/dist/browser.mjs +49 -12
- package/dist/index.cjs +49 -12
- package/dist/index.mjs +49 -12
- package/dist/widget.global.js +93 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Embeddable chat widget package for Clivly conversations.
|
|
4
4
|
|
|
5
|
+
> **Preview-quality surface.** Install with `npm install @clivly/chat-widget`.
|
|
6
|
+
> The end-to-end path (SDK session handler, `/widget.js` serving, operator
|
|
7
|
+
> inbox) is implemented and covered by integration tests; treat it as
|
|
8
|
+
> design-partner quality while the conversations UX settles.
|
|
9
|
+
|
|
10
|
+
> **New here?** Start with
|
|
11
|
+
> [Adding the chat widget](../../docs/guides/adding-the-chat-widget.md) — the
|
|
12
|
+
> end-to-end integration guide (widget record, session route, embed, operator
|
|
13
|
+
> inbox). This README is the API-level reference for the package's entrypoints.
|
|
14
|
+
|
|
5
15
|
This package currently exposes four stable entrypoints with different intended
|
|
6
16
|
use cases.
|
|
7
17
|
|
package/dist/browser-mount.cjs
CHANGED
|
@@ -106,6 +106,23 @@ const WIDGET_STYLES = `
|
|
|
106
106
|
font-size: 0.75rem;
|
|
107
107
|
margin-top: 0.35rem;
|
|
108
108
|
}
|
|
109
|
+
.clivly-chat-widget__powered {
|
|
110
|
+
border-top: 1px solid var(--clivly-chat-border);
|
|
111
|
+
color: var(--clivly-chat-muted);
|
|
112
|
+
font-size: 0.6875rem;
|
|
113
|
+
font-style: italic;
|
|
114
|
+
margin: 0;
|
|
115
|
+
padding: 0.5rem 1rem;
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
118
|
+
.clivly-chat-widget__powered a {
|
|
119
|
+
color: inherit;
|
|
120
|
+
text-decoration: none;
|
|
121
|
+
}
|
|
122
|
+
.clivly-chat-widget__powered a:hover,
|
|
123
|
+
.clivly-chat-widget__powered a:focus-visible {
|
|
124
|
+
text-decoration: underline;
|
|
125
|
+
}
|
|
109
126
|
.clivly-chat-widget__composer,
|
|
110
127
|
.clivly-chat-widget__form {
|
|
111
128
|
border-top: 1px solid var(--clivly-chat-border);
|
|
@@ -164,7 +181,7 @@ const WIDGET_STYLES = `
|
|
|
164
181
|
text-align: center;
|
|
165
182
|
}
|
|
166
183
|
`;
|
|
167
|
-
const DEFAULT_STORAGE = typeof window
|
|
184
|
+
const DEFAULT_STORAGE = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
168
185
|
function createLocalMessage(message, delivery = "sent") {
|
|
169
186
|
return {
|
|
170
187
|
...message,
|
|
@@ -184,6 +201,14 @@ function formatTimestamp(value) {
|
|
|
184
201
|
day: "numeric"
|
|
185
202
|
}).format(date);
|
|
186
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Enter sends the draft. Shift+Enter still inserts a newline, and an
|
|
206
|
+
* in-progress IME composition (CJK) must never be sent — committing a
|
|
207
|
+
* composition also fires Enter, which would truncate the word being typed.
|
|
208
|
+
*/
|
|
209
|
+
function isSendKey(event) {
|
|
210
|
+
return event.key === "Enter" && !(event.shiftKey || event.nativeEvent.isComposing);
|
|
211
|
+
}
|
|
187
212
|
function generateId() {
|
|
188
213
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
189
214
|
return `msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -229,11 +254,9 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
229
254
|
const transportRef = (0, react.useRef)(null);
|
|
230
255
|
const listRef = (0, react.useRef)(null);
|
|
231
256
|
const themeStyle = useThemeStyle(session);
|
|
232
|
-
(0, react.useEffect)(() => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
transportRef.current = null;
|
|
236
|
-
};
|
|
257
|
+
(0, react.useEffect)(() => () => {
|
|
258
|
+
transportRef.current?.close();
|
|
259
|
+
transportRef.current = null;
|
|
237
260
|
}, []);
|
|
238
261
|
(0, react.useEffect)(() => {
|
|
239
262
|
if (!listRef.current) return;
|
|
@@ -243,7 +266,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
243
266
|
transportRef.current?.close();
|
|
244
267
|
transportRef.current = null;
|
|
245
268
|
setIsReady(false);
|
|
246
|
-
if (!session
|
|
269
|
+
if (!(session && createTransport)) return;
|
|
247
270
|
const handleEvent = (event) => {
|
|
248
271
|
switch (event.type) {
|
|
249
272
|
case "conversation.snapshot":
|
|
@@ -310,8 +333,8 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
310
333
|
}
|
|
311
334
|
};
|
|
312
335
|
const submitMessage = (event) => {
|
|
313
|
-
event
|
|
314
|
-
if (!session
|
|
336
|
+
event?.preventDefault();
|
|
337
|
+
if (!(session && transportRef.current)) {
|
|
315
338
|
setError("Chat is not connected yet.");
|
|
316
339
|
return;
|
|
317
340
|
}
|
|
@@ -393,10 +416,10 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
393
416
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: message.author ?? "Clivly" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatTimestamp(message.createdAt) })]
|
|
394
417
|
}),
|
|
395
418
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message.body }),
|
|
396
|
-
message.delivery
|
|
419
|
+
message.delivery === "sent" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
397
420
|
className: "clivly-chat-widget__status",
|
|
398
421
|
children: message.delivery === "pending" ? "Sending…" : "Failed to send"
|
|
399
|
-
})
|
|
422
|
+
})
|
|
400
423
|
]
|
|
401
424
|
}, message.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
402
425
|
className: "clivly-chat-widget__empty",
|
|
@@ -419,6 +442,11 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
419
442
|
onChange: (event) => {
|
|
420
443
|
setMessageDraft(event.target.value);
|
|
421
444
|
},
|
|
445
|
+
onKeyDown: (event) => {
|
|
446
|
+
if (!isSendKey(event)) return;
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
if (messageDraft.trim() && isReady) submitMessage();
|
|
449
|
+
},
|
|
422
450
|
placeholder: "Write your message",
|
|
423
451
|
value: messageDraft
|
|
424
452
|
})]
|
|
@@ -429,7 +457,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
429
457
|
children: isReady || !createTransport ? "Conversation ready" : "Connecting…"
|
|
430
458
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
431
459
|
className: "clivly-chat-widget__button",
|
|
432
|
-
disabled: !messageDraft.trim()
|
|
460
|
+
disabled: !(messageDraft.trim() && isReady),
|
|
433
461
|
type: "submit",
|
|
434
462
|
children: "Send"
|
|
435
463
|
})]
|
|
@@ -488,6 +516,15 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
488
516
|
})]
|
|
489
517
|
})
|
|
490
518
|
]
|
|
519
|
+
}),
|
|
520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
521
|
+
className: "clivly-chat-widget__powered",
|
|
522
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
523
|
+
href: "https://clivly.com",
|
|
524
|
+
rel: "noopener",
|
|
525
|
+
target: "_blank",
|
|
526
|
+
children: "Powered by Clivly"
|
|
527
|
+
})
|
|
491
528
|
})
|
|
492
529
|
]
|
|
493
530
|
}) : null
|
package/dist/browser-mount.mjs
CHANGED
|
@@ -105,6 +105,23 @@ const WIDGET_STYLES = `
|
|
|
105
105
|
font-size: 0.75rem;
|
|
106
106
|
margin-top: 0.35rem;
|
|
107
107
|
}
|
|
108
|
+
.clivly-chat-widget__powered {
|
|
109
|
+
border-top: 1px solid var(--clivly-chat-border);
|
|
110
|
+
color: var(--clivly-chat-muted);
|
|
111
|
+
font-size: 0.6875rem;
|
|
112
|
+
font-style: italic;
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 0.5rem 1rem;
|
|
115
|
+
text-align: center;
|
|
116
|
+
}
|
|
117
|
+
.clivly-chat-widget__powered a {
|
|
118
|
+
color: inherit;
|
|
119
|
+
text-decoration: none;
|
|
120
|
+
}
|
|
121
|
+
.clivly-chat-widget__powered a:hover,
|
|
122
|
+
.clivly-chat-widget__powered a:focus-visible {
|
|
123
|
+
text-decoration: underline;
|
|
124
|
+
}
|
|
108
125
|
.clivly-chat-widget__composer,
|
|
109
126
|
.clivly-chat-widget__form {
|
|
110
127
|
border-top: 1px solid var(--clivly-chat-border);
|
|
@@ -163,7 +180,7 @@ const WIDGET_STYLES = `
|
|
|
163
180
|
text-align: center;
|
|
164
181
|
}
|
|
165
182
|
`;
|
|
166
|
-
const DEFAULT_STORAGE = typeof window
|
|
183
|
+
const DEFAULT_STORAGE = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
167
184
|
function createLocalMessage(message, delivery = "sent") {
|
|
168
185
|
return {
|
|
169
186
|
...message,
|
|
@@ -183,6 +200,14 @@ function formatTimestamp(value) {
|
|
|
183
200
|
day: "numeric"
|
|
184
201
|
}).format(date);
|
|
185
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Enter sends the draft. Shift+Enter still inserts a newline, and an
|
|
205
|
+
* in-progress IME composition (CJK) must never be sent — committing a
|
|
206
|
+
* composition also fires Enter, which would truncate the word being typed.
|
|
207
|
+
*/
|
|
208
|
+
function isSendKey(event) {
|
|
209
|
+
return event.key === "Enter" && !(event.shiftKey || event.nativeEvent.isComposing);
|
|
210
|
+
}
|
|
186
211
|
function generateId() {
|
|
187
212
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
188
213
|
return `msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -228,11 +253,9 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
228
253
|
const transportRef = useRef(null);
|
|
229
254
|
const listRef = useRef(null);
|
|
230
255
|
const themeStyle = useThemeStyle(session);
|
|
231
|
-
useEffect(() => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
transportRef.current = null;
|
|
235
|
-
};
|
|
256
|
+
useEffect(() => () => {
|
|
257
|
+
transportRef.current?.close();
|
|
258
|
+
transportRef.current = null;
|
|
236
259
|
}, []);
|
|
237
260
|
useEffect(() => {
|
|
238
261
|
if (!listRef.current) return;
|
|
@@ -242,7 +265,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
242
265
|
transportRef.current?.close();
|
|
243
266
|
transportRef.current = null;
|
|
244
267
|
setIsReady(false);
|
|
245
|
-
if (!session
|
|
268
|
+
if (!(session && createTransport)) return;
|
|
246
269
|
const handleEvent = (event) => {
|
|
247
270
|
switch (event.type) {
|
|
248
271
|
case "conversation.snapshot":
|
|
@@ -309,8 +332,8 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
309
332
|
}
|
|
310
333
|
};
|
|
311
334
|
const submitMessage = (event) => {
|
|
312
|
-
event
|
|
313
|
-
if (!session
|
|
335
|
+
event?.preventDefault();
|
|
336
|
+
if (!(session && transportRef.current)) {
|
|
314
337
|
setError("Chat is not connected yet.");
|
|
315
338
|
return;
|
|
316
339
|
}
|
|
@@ -392,10 +415,10 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
392
415
|
children: [/* @__PURE__ */ jsx("span", { children: message.author ?? "Clivly" }), /* @__PURE__ */ jsx("span", { children: formatTimestamp(message.createdAt) })]
|
|
393
416
|
}),
|
|
394
417
|
/* @__PURE__ */ jsx("div", { children: message.body }),
|
|
395
|
-
message.delivery
|
|
418
|
+
message.delivery === "sent" ? null : /* @__PURE__ */ jsx("div", {
|
|
396
419
|
className: "clivly-chat-widget__status",
|
|
397
420
|
children: message.delivery === "pending" ? "Sending…" : "Failed to send"
|
|
398
|
-
})
|
|
421
|
+
})
|
|
399
422
|
]
|
|
400
423
|
}, message.id)) : /* @__PURE__ */ jsx("p", {
|
|
401
424
|
className: "clivly-chat-widget__empty",
|
|
@@ -418,6 +441,11 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
418
441
|
onChange: (event) => {
|
|
419
442
|
setMessageDraft(event.target.value);
|
|
420
443
|
},
|
|
444
|
+
onKeyDown: (event) => {
|
|
445
|
+
if (!isSendKey(event)) return;
|
|
446
|
+
event.preventDefault();
|
|
447
|
+
if (messageDraft.trim() && isReady) submitMessage();
|
|
448
|
+
},
|
|
421
449
|
placeholder: "Write your message",
|
|
422
450
|
value: messageDraft
|
|
423
451
|
})]
|
|
@@ -428,7 +456,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
428
456
|
children: isReady || !createTransport ? "Conversation ready" : "Connecting…"
|
|
429
457
|
}), /* @__PURE__ */ jsx("button", {
|
|
430
458
|
className: "clivly-chat-widget__button",
|
|
431
|
-
disabled: !messageDraft.trim()
|
|
459
|
+
disabled: !(messageDraft.trim() && isReady),
|
|
432
460
|
type: "submit",
|
|
433
461
|
children: "Send"
|
|
434
462
|
})]
|
|
@@ -487,6 +515,15 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
487
515
|
})]
|
|
488
516
|
})
|
|
489
517
|
]
|
|
518
|
+
}),
|
|
519
|
+
/* @__PURE__ */ jsx("p", {
|
|
520
|
+
className: "clivly-chat-widget__powered",
|
|
521
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
522
|
+
href: "https://clivly.com",
|
|
523
|
+
rel: "noopener",
|
|
524
|
+
target: "_blank",
|
|
525
|
+
children: "Powered by Clivly"
|
|
526
|
+
})
|
|
490
527
|
})
|
|
491
528
|
]
|
|
492
529
|
}) : null
|
package/dist/browser.cjs
CHANGED
|
@@ -106,6 +106,23 @@ const WIDGET_STYLES = `
|
|
|
106
106
|
font-size: 0.75rem;
|
|
107
107
|
margin-top: 0.35rem;
|
|
108
108
|
}
|
|
109
|
+
.clivly-chat-widget__powered {
|
|
110
|
+
border-top: 1px solid var(--clivly-chat-border);
|
|
111
|
+
color: var(--clivly-chat-muted);
|
|
112
|
+
font-size: 0.6875rem;
|
|
113
|
+
font-style: italic;
|
|
114
|
+
margin: 0;
|
|
115
|
+
padding: 0.5rem 1rem;
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
118
|
+
.clivly-chat-widget__powered a {
|
|
119
|
+
color: inherit;
|
|
120
|
+
text-decoration: none;
|
|
121
|
+
}
|
|
122
|
+
.clivly-chat-widget__powered a:hover,
|
|
123
|
+
.clivly-chat-widget__powered a:focus-visible {
|
|
124
|
+
text-decoration: underline;
|
|
125
|
+
}
|
|
109
126
|
.clivly-chat-widget__composer,
|
|
110
127
|
.clivly-chat-widget__form {
|
|
111
128
|
border-top: 1px solid var(--clivly-chat-border);
|
|
@@ -164,7 +181,7 @@ const WIDGET_STYLES = `
|
|
|
164
181
|
text-align: center;
|
|
165
182
|
}
|
|
166
183
|
`;
|
|
167
|
-
const DEFAULT_STORAGE = typeof window
|
|
184
|
+
const DEFAULT_STORAGE = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
168
185
|
function createLocalMessage(message, delivery = "sent") {
|
|
169
186
|
return {
|
|
170
187
|
...message,
|
|
@@ -184,6 +201,14 @@ function formatTimestamp(value) {
|
|
|
184
201
|
day: "numeric"
|
|
185
202
|
}).format(date);
|
|
186
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Enter sends the draft. Shift+Enter still inserts a newline, and an
|
|
206
|
+
* in-progress IME composition (CJK) must never be sent — committing a
|
|
207
|
+
* composition also fires Enter, which would truncate the word being typed.
|
|
208
|
+
*/
|
|
209
|
+
function isSendKey(event) {
|
|
210
|
+
return event.key === "Enter" && !(event.shiftKey || event.nativeEvent.isComposing);
|
|
211
|
+
}
|
|
187
212
|
function generateId() {
|
|
188
213
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
189
214
|
return `msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -229,11 +254,9 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
229
254
|
const transportRef = (0, react.useRef)(null);
|
|
230
255
|
const listRef = (0, react.useRef)(null);
|
|
231
256
|
const themeStyle = useThemeStyle(session);
|
|
232
|
-
(0, react.useEffect)(() => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
transportRef.current = null;
|
|
236
|
-
};
|
|
257
|
+
(0, react.useEffect)(() => () => {
|
|
258
|
+
transportRef.current?.close();
|
|
259
|
+
transportRef.current = null;
|
|
237
260
|
}, []);
|
|
238
261
|
(0, react.useEffect)(() => {
|
|
239
262
|
if (!listRef.current) return;
|
|
@@ -243,7 +266,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
243
266
|
transportRef.current?.close();
|
|
244
267
|
transportRef.current = null;
|
|
245
268
|
setIsReady(false);
|
|
246
|
-
if (!session
|
|
269
|
+
if (!(session && createTransport)) return;
|
|
247
270
|
const handleEvent = (event) => {
|
|
248
271
|
switch (event.type) {
|
|
249
272
|
case "conversation.snapshot":
|
|
@@ -310,8 +333,8 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
310
333
|
}
|
|
311
334
|
};
|
|
312
335
|
const submitMessage = (event) => {
|
|
313
|
-
event
|
|
314
|
-
if (!session
|
|
336
|
+
event?.preventDefault();
|
|
337
|
+
if (!(session && transportRef.current)) {
|
|
315
338
|
setError("Chat is not connected yet.");
|
|
316
339
|
return;
|
|
317
340
|
}
|
|
@@ -393,10 +416,10 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
393
416
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: message.author ?? "Clivly" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatTimestamp(message.createdAt) })]
|
|
394
417
|
}),
|
|
395
418
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message.body }),
|
|
396
|
-
message.delivery
|
|
419
|
+
message.delivery === "sent" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
397
420
|
className: "clivly-chat-widget__status",
|
|
398
421
|
children: message.delivery === "pending" ? "Sending…" : "Failed to send"
|
|
399
|
-
})
|
|
422
|
+
})
|
|
400
423
|
]
|
|
401
424
|
}, message.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
402
425
|
className: "clivly-chat-widget__empty",
|
|
@@ -419,6 +442,11 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
419
442
|
onChange: (event) => {
|
|
420
443
|
setMessageDraft(event.target.value);
|
|
421
444
|
},
|
|
445
|
+
onKeyDown: (event) => {
|
|
446
|
+
if (!isSendKey(event)) return;
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
if (messageDraft.trim() && isReady) submitMessage();
|
|
449
|
+
},
|
|
422
450
|
placeholder: "Write your message",
|
|
423
451
|
value: messageDraft
|
|
424
452
|
})]
|
|
@@ -429,7 +457,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
429
457
|
children: isReady || !createTransport ? "Conversation ready" : "Connecting…"
|
|
430
458
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
431
459
|
className: "clivly-chat-widget__button",
|
|
432
|
-
disabled: !messageDraft.trim()
|
|
460
|
+
disabled: !(messageDraft.trim() && isReady),
|
|
433
461
|
type: "submit",
|
|
434
462
|
children: "Send"
|
|
435
463
|
})]
|
|
@@ -488,6 +516,15 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
488
516
|
})]
|
|
489
517
|
})
|
|
490
518
|
]
|
|
519
|
+
}),
|
|
520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
521
|
+
className: "clivly-chat-widget__powered",
|
|
522
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
523
|
+
href: "https://clivly.com",
|
|
524
|
+
rel: "noopener",
|
|
525
|
+
target: "_blank",
|
|
526
|
+
children: "Powered by Clivly"
|
|
527
|
+
})
|
|
491
528
|
})
|
|
492
529
|
]
|
|
493
530
|
}) : null
|
package/dist/browser.mjs
CHANGED
|
@@ -105,6 +105,23 @@ const WIDGET_STYLES = `
|
|
|
105
105
|
font-size: 0.75rem;
|
|
106
106
|
margin-top: 0.35rem;
|
|
107
107
|
}
|
|
108
|
+
.clivly-chat-widget__powered {
|
|
109
|
+
border-top: 1px solid var(--clivly-chat-border);
|
|
110
|
+
color: var(--clivly-chat-muted);
|
|
111
|
+
font-size: 0.6875rem;
|
|
112
|
+
font-style: italic;
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 0.5rem 1rem;
|
|
115
|
+
text-align: center;
|
|
116
|
+
}
|
|
117
|
+
.clivly-chat-widget__powered a {
|
|
118
|
+
color: inherit;
|
|
119
|
+
text-decoration: none;
|
|
120
|
+
}
|
|
121
|
+
.clivly-chat-widget__powered a:hover,
|
|
122
|
+
.clivly-chat-widget__powered a:focus-visible {
|
|
123
|
+
text-decoration: underline;
|
|
124
|
+
}
|
|
108
125
|
.clivly-chat-widget__composer,
|
|
109
126
|
.clivly-chat-widget__form {
|
|
110
127
|
border-top: 1px solid var(--clivly-chat-border);
|
|
@@ -163,7 +180,7 @@ const WIDGET_STYLES = `
|
|
|
163
180
|
text-align: center;
|
|
164
181
|
}
|
|
165
182
|
`;
|
|
166
|
-
const DEFAULT_STORAGE = typeof window
|
|
183
|
+
const DEFAULT_STORAGE = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
167
184
|
function createLocalMessage(message, delivery = "sent") {
|
|
168
185
|
return {
|
|
169
186
|
...message,
|
|
@@ -183,6 +200,14 @@ function formatTimestamp(value) {
|
|
|
183
200
|
day: "numeric"
|
|
184
201
|
}).format(date);
|
|
185
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Enter sends the draft. Shift+Enter still inserts a newline, and an
|
|
205
|
+
* in-progress IME composition (CJK) must never be sent — committing a
|
|
206
|
+
* composition also fires Enter, which would truncate the word being typed.
|
|
207
|
+
*/
|
|
208
|
+
function isSendKey(event) {
|
|
209
|
+
return event.key === "Enter" && !(event.shiftKey || event.nativeEvent.isComposing);
|
|
210
|
+
}
|
|
186
211
|
function generateId() {
|
|
187
212
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
188
213
|
return `msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -228,11 +253,9 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
228
253
|
const transportRef = useRef(null);
|
|
229
254
|
const listRef = useRef(null);
|
|
230
255
|
const themeStyle = useThemeStyle(session);
|
|
231
|
-
useEffect(() => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
transportRef.current = null;
|
|
235
|
-
};
|
|
256
|
+
useEffect(() => () => {
|
|
257
|
+
transportRef.current?.close();
|
|
258
|
+
transportRef.current = null;
|
|
236
259
|
}, []);
|
|
237
260
|
useEffect(() => {
|
|
238
261
|
if (!listRef.current) return;
|
|
@@ -242,7 +265,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
242
265
|
transportRef.current?.close();
|
|
243
266
|
transportRef.current = null;
|
|
244
267
|
setIsReady(false);
|
|
245
|
-
if (!session
|
|
268
|
+
if (!(session && createTransport)) return;
|
|
246
269
|
const handleEvent = (event) => {
|
|
247
270
|
switch (event.type) {
|
|
248
271
|
case "conversation.snapshot":
|
|
@@ -309,8 +332,8 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
309
332
|
}
|
|
310
333
|
};
|
|
311
334
|
const submitMessage = (event) => {
|
|
312
|
-
event
|
|
313
|
-
if (!session
|
|
335
|
+
event?.preventDefault();
|
|
336
|
+
if (!(session && transportRef.current)) {
|
|
314
337
|
setError("Chat is not connected yet.");
|
|
315
338
|
return;
|
|
316
339
|
}
|
|
@@ -392,10 +415,10 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
392
415
|
children: [/* @__PURE__ */ jsx("span", { children: message.author ?? "Clivly" }), /* @__PURE__ */ jsx("span", { children: formatTimestamp(message.createdAt) })]
|
|
393
416
|
}),
|
|
394
417
|
/* @__PURE__ */ jsx("div", { children: message.body }),
|
|
395
|
-
message.delivery
|
|
418
|
+
message.delivery === "sent" ? null : /* @__PURE__ */ jsx("div", {
|
|
396
419
|
className: "clivly-chat-widget__status",
|
|
397
420
|
children: message.delivery === "pending" ? "Sending…" : "Failed to send"
|
|
398
|
-
})
|
|
421
|
+
})
|
|
399
422
|
]
|
|
400
423
|
}, message.id)) : /* @__PURE__ */ jsx("p", {
|
|
401
424
|
className: "clivly-chat-widget__empty",
|
|
@@ -418,6 +441,11 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
418
441
|
onChange: (event) => {
|
|
419
442
|
setMessageDraft(event.target.value);
|
|
420
443
|
},
|
|
444
|
+
onKeyDown: (event) => {
|
|
445
|
+
if (!isSendKey(event)) return;
|
|
446
|
+
event.preventDefault();
|
|
447
|
+
if (messageDraft.trim() && isReady) submitMessage();
|
|
448
|
+
},
|
|
421
449
|
placeholder: "Write your message",
|
|
422
450
|
value: messageDraft
|
|
423
451
|
})]
|
|
@@ -428,7 +456,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
428
456
|
children: isReady || !createTransport ? "Conversation ready" : "Connecting…"
|
|
429
457
|
}), /* @__PURE__ */ jsx("button", {
|
|
430
458
|
className: "clivly-chat-widget__button",
|
|
431
|
-
disabled: !messageDraft.trim()
|
|
459
|
+
disabled: !(messageDraft.trim() && isReady),
|
|
432
460
|
type: "submit",
|
|
433
461
|
children: "Send"
|
|
434
462
|
})]
|
|
@@ -487,6 +515,15 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
487
515
|
})]
|
|
488
516
|
})
|
|
489
517
|
]
|
|
518
|
+
}),
|
|
519
|
+
/* @__PURE__ */ jsx("p", {
|
|
520
|
+
className: "clivly-chat-widget__powered",
|
|
521
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
522
|
+
href: "https://clivly.com",
|
|
523
|
+
rel: "noopener",
|
|
524
|
+
target: "_blank",
|
|
525
|
+
children: "Powered by Clivly"
|
|
526
|
+
})
|
|
490
527
|
})
|
|
491
528
|
]
|
|
492
529
|
}) : null
|
package/dist/index.cjs
CHANGED
|
@@ -106,6 +106,23 @@ const WIDGET_STYLES = `
|
|
|
106
106
|
font-size: 0.75rem;
|
|
107
107
|
margin-top: 0.35rem;
|
|
108
108
|
}
|
|
109
|
+
.clivly-chat-widget__powered {
|
|
110
|
+
border-top: 1px solid var(--clivly-chat-border);
|
|
111
|
+
color: var(--clivly-chat-muted);
|
|
112
|
+
font-size: 0.6875rem;
|
|
113
|
+
font-style: italic;
|
|
114
|
+
margin: 0;
|
|
115
|
+
padding: 0.5rem 1rem;
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
118
|
+
.clivly-chat-widget__powered a {
|
|
119
|
+
color: inherit;
|
|
120
|
+
text-decoration: none;
|
|
121
|
+
}
|
|
122
|
+
.clivly-chat-widget__powered a:hover,
|
|
123
|
+
.clivly-chat-widget__powered a:focus-visible {
|
|
124
|
+
text-decoration: underline;
|
|
125
|
+
}
|
|
109
126
|
.clivly-chat-widget__composer,
|
|
110
127
|
.clivly-chat-widget__form {
|
|
111
128
|
border-top: 1px solid var(--clivly-chat-border);
|
|
@@ -164,7 +181,7 @@ const WIDGET_STYLES = `
|
|
|
164
181
|
text-align: center;
|
|
165
182
|
}
|
|
166
183
|
`;
|
|
167
|
-
const DEFAULT_STORAGE = typeof window
|
|
184
|
+
const DEFAULT_STORAGE = typeof window === "undefined" ? void 0 : window.localStorage;
|
|
168
185
|
function createLocalMessage(message, delivery = "sent") {
|
|
169
186
|
return {
|
|
170
187
|
...message,
|
|
@@ -184,6 +201,14 @@ function formatTimestamp(value) {
|
|
|
184
201
|
day: "numeric"
|
|
185
202
|
}).format(date);
|
|
186
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Enter sends the draft. Shift+Enter still inserts a newline, and an
|
|
206
|
+
* in-progress IME composition (CJK) must never be sent — committing a
|
|
207
|
+
* composition also fires Enter, which would truncate the word being typed.
|
|
208
|
+
*/
|
|
209
|
+
function isSendKey(event) {
|
|
210
|
+
return event.key === "Enter" && !(event.shiftKey || event.nativeEvent.isComposing);
|
|
211
|
+
}
|
|
187
212
|
function generateId() {
|
|
188
213
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
189
214
|
return `msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -229,11 +254,9 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
229
254
|
const transportRef = (0, react.useRef)(null);
|
|
230
255
|
const listRef = (0, react.useRef)(null);
|
|
231
256
|
const themeStyle = useThemeStyle(session);
|
|
232
|
-
(0, react.useEffect)(() => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
transportRef.current = null;
|
|
236
|
-
};
|
|
257
|
+
(0, react.useEffect)(() => () => {
|
|
258
|
+
transportRef.current?.close();
|
|
259
|
+
transportRef.current = null;
|
|
237
260
|
}, []);
|
|
238
261
|
(0, react.useEffect)(() => {
|
|
239
262
|
if (!listRef.current) return;
|
|
@@ -243,7 +266,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
243
266
|
transportRef.current?.close();
|
|
244
267
|
transportRef.current = null;
|
|
245
268
|
setIsReady(false);
|
|
246
|
-
if (!session
|
|
269
|
+
if (!(session && createTransport)) return;
|
|
247
270
|
const handleEvent = (event) => {
|
|
248
271
|
switch (event.type) {
|
|
249
272
|
case "conversation.snapshot":
|
|
@@ -310,8 +333,8 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
310
333
|
}
|
|
311
334
|
};
|
|
312
335
|
const submitMessage = (event) => {
|
|
313
|
-
event
|
|
314
|
-
if (!session
|
|
336
|
+
event?.preventDefault();
|
|
337
|
+
if (!(session && transportRef.current)) {
|
|
315
338
|
setError("Chat is not connected yet.");
|
|
316
339
|
return;
|
|
317
340
|
}
|
|
@@ -393,10 +416,10 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
393
416
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: message.author ?? "Clivly" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatTimestamp(message.createdAt) })]
|
|
394
417
|
}),
|
|
395
418
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message.body }),
|
|
396
|
-
message.delivery
|
|
419
|
+
message.delivery === "sent" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
397
420
|
className: "clivly-chat-widget__status",
|
|
398
421
|
children: message.delivery === "pending" ? "Sending…" : "Failed to send"
|
|
399
|
-
})
|
|
422
|
+
})
|
|
400
423
|
]
|
|
401
424
|
}, message.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
402
425
|
className: "clivly-chat-widget__empty",
|
|
@@ -419,6 +442,11 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
419
442
|
onChange: (event) => {
|
|
420
443
|
setMessageDraft(event.target.value);
|
|
421
444
|
},
|
|
445
|
+
onKeyDown: (event) => {
|
|
446
|
+
if (!isSendKey(event)) return;
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
if (messageDraft.trim() && isReady) submitMessage();
|
|
449
|
+
},
|
|
422
450
|
placeholder: "Write your message",
|
|
423
451
|
value: messageDraft
|
|
424
452
|
})]
|
|
@@ -429,7 +457,7 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
429
457
|
children: isReady || !createTransport ? "Conversation ready" : "Connecting…"
|
|
430
458
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
431
459
|
className: "clivly-chat-widget__button",
|
|
432
|
-
disabled: !messageDraft.trim()
|
|
460
|
+
disabled: !(messageDraft.trim() && isReady),
|
|
433
461
|
type: "submit",
|
|
434
462
|
children: "Send"
|
|
435
463
|
})]
|
|
@@ -488,6 +516,15 @@ function ChatWidget({ className, createTransport, getSession, initiallyOpen = fa
|
|
|
488
516
|
})]
|
|
489
517
|
})
|
|
490
518
|
]
|
|
519
|
+
}),
|
|
520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
521
|
+
className: "clivly-chat-widget__powered",
|
|
522
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
523
|
+
href: "https://clivly.com",
|
|
524
|
+
rel: "noopener",
|
|
525
|
+
target: "_blank",
|
|
526
|
+
children: "Powered by Clivly"
|
|
527
|
+
})
|
|
491
528
|
})
|
|
492
529
|
]
|
|
493
530
|
}) : null
|