@usereq/widget 0.2.21 → 0.2.22
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/widget.js +28 -28
- package/package.json +1 -1
- package/src/custom-element/agent-widget-element.tsx +65 -90
- package/src/renderer/widget-runtime.tsx +9 -0
- package/src/runtime/bootstrap.ts +30 -39
- package/src/shared/widget-config.ts +21 -0
- package/src/types.ts +10 -0
- package/src/chat-widget/chat-widget-defaults.tsx +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
-
import { DEFAULT_START_CONVERSATION_LABEL } from "../chat-widget/chat-widget-defaults";
|
|
4
3
|
import {
|
|
5
4
|
DEFAULT_WIDGET_APPEARANCE,
|
|
6
5
|
DEFAULT_WIDGET_PRESET,
|
|
@@ -15,7 +14,10 @@ import {
|
|
|
15
14
|
type WidgetSessionState,
|
|
16
15
|
type WidgetVariant,
|
|
17
16
|
} from "../types";
|
|
18
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
normalizeWidgetMobileHeight,
|
|
19
|
+
widgetTriggerRuleKey,
|
|
20
|
+
} from "../shared/widget-config";
|
|
19
21
|
import { WidgetRuntime, type WidgetRuntimeStatus } from "../renderer/index";
|
|
20
22
|
import { buildAssistantMessageRevealSteps } from "../chat-widget/message-reveal";
|
|
21
23
|
import { DEFAULT_STOP_CONVERSATION_PROMPT } from "../shared/stop-confirmation";
|
|
@@ -23,7 +25,6 @@ import {
|
|
|
23
25
|
bootstrapWidget,
|
|
24
26
|
resetWidgetSession,
|
|
25
27
|
submitWidgetStopConfirmation,
|
|
26
|
-
startWidgetConversation,
|
|
27
28
|
submitWidgetMessage,
|
|
28
29
|
} from "../runtime/bootstrap";
|
|
29
30
|
import { getWidgetShadowCss } from "../shared/shadow-css";
|
|
@@ -67,7 +68,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
67
68
|
private sendVersion = 0;
|
|
68
69
|
private confirmationVersion = 0;
|
|
69
70
|
private bootstrapPhase: "idle" | "booting" | "ready" | "blocked" = "idle";
|
|
70
|
-
private autoStartTriggered = false;
|
|
71
71
|
// Back-to-back send support. The composer stays enabled while the agent
|
|
72
72
|
// is replying; new submissions land here and are drained sequentially by
|
|
73
73
|
// `sendMessage`. Each entry carries the optimistic bubble id so failures
|
|
@@ -234,32 +234,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
234
234
|
const variant = this.getResolvedVariant();
|
|
235
235
|
const preset = this.getResolvedPreset();
|
|
236
236
|
const behavior = this.sessionConfig?.widgetConfig.widgetBehavior;
|
|
237
|
-
const autoStartEnabled = behavior?.autoStart === true;
|
|
238
237
|
const alwaysOpen = behavior?.alwaysOpen === true;
|
|
239
|
-
const awaitingConversation =
|
|
240
|
-
this.status === "booting" ||
|
|
241
|
-
(this.sessionState != null && !this.sessionState.conversationId);
|
|
242
|
-
// When auto-start is on we never render the manual CTA — except as a
|
|
243
|
-
// fallback if the auto-start attempt errored, so the visitor isn't
|
|
244
|
-
// stranded on a blank panel.
|
|
245
|
-
const showFallbackCta = autoStartEnabled && this.status === "error";
|
|
246
|
-
const emptyAction =
|
|
247
|
-
!autoStartEnabled && awaitingConversation
|
|
248
|
-
? {
|
|
249
|
-
label: DEFAULT_START_CONVERSATION_LABEL,
|
|
250
|
-
onClick: () => void this.handleStartConversation(),
|
|
251
|
-
disabled: this.status !== "ready",
|
|
252
|
-
}
|
|
253
|
-
: showFallbackCta
|
|
254
|
-
? {
|
|
255
|
-
label: DEFAULT_START_CONVERSATION_LABEL,
|
|
256
|
-
onClick: () => {
|
|
257
|
-
this.autoStartTriggered = false;
|
|
258
|
-
void this.handleStartConversation();
|
|
259
|
-
},
|
|
260
|
-
disabled: false,
|
|
261
|
-
}
|
|
262
|
-
: undefined;
|
|
263
238
|
|
|
264
239
|
this.root.render(
|
|
265
240
|
React.createElement(WidgetRuntime, {
|
|
@@ -277,21 +252,23 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
277
252
|
welcomeMessage: this.getWelcomeMessage(),
|
|
278
253
|
spotlights: this.getSpotlights(),
|
|
279
254
|
ctaLink: this.getCtaLink(),
|
|
255
|
+
mobileHeight: normalizeWidgetMobileHeight(
|
|
256
|
+
this.sessionConfig?.widgetConfig.mobileHeight,
|
|
257
|
+
),
|
|
280
258
|
open: this.open,
|
|
281
259
|
alwaysOpen,
|
|
282
260
|
status: this.status,
|
|
283
261
|
messages: this.messages,
|
|
284
262
|
errorMessage: this.errorMessage,
|
|
285
263
|
triggerRule: this.sessionConfig?.widgetConfig.triggerRule ?? null,
|
|
286
|
-
|
|
264
|
+
// Lazy session: no "Start a chat" gate. The composer is live from
|
|
265
|
+
// the moment the panel opens; the conversation is created on the
|
|
266
|
+
// server the first time the user actually sends something.
|
|
267
|
+
emptyAction: undefined,
|
|
287
268
|
onStopConversation:
|
|
288
269
|
this.sessionState?.conversationId != null
|
|
289
270
|
? () => void this.handleStopConversation()
|
|
290
271
|
: undefined,
|
|
291
|
-
// Allow the user to keep typing while the agent is mid-reply —
|
|
292
|
-
// submissions queue inside `sendMessage` and drain sequentially.
|
|
293
|
-
// Only block when there's no conversation to send to yet.
|
|
294
|
-
sendDisabled: !this.sessionState?.conversationId,
|
|
295
272
|
onOpenChange: (nextOpen: boolean) => void this.handleOpenChange(nextOpen),
|
|
296
273
|
onSendMessage: (content: string) => void this.sendMessage(content),
|
|
297
274
|
onConfirmationDecision: (confirmationId: string, accepted: boolean) =>
|
|
@@ -347,6 +324,11 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
347
324
|
this.welcomeMessage = state.session.widgetConfig.welcomeMessage;
|
|
348
325
|
this.status = "ready";
|
|
349
326
|
this.bootstrapPhase = "ready";
|
|
327
|
+
// Lazy session: when the visitor doesn't already have a running
|
|
328
|
+
// conversation, seed a client-side welcome bubble from the boot
|
|
329
|
+
// config so the panel doesn't open on a blank empty state. The
|
|
330
|
+
// real conversation is only created server-side once they type.
|
|
331
|
+
this.seedClientWelcomeIfNeeded();
|
|
350
332
|
// Apply the agent's configured open-state on mount. `alwaysOpen`
|
|
351
333
|
// wins (the panel is pinned regardless), and `defaultOpen` opens it
|
|
352
334
|
// once if the visitor hasn't already interacted with the launcher.
|
|
@@ -355,17 +337,6 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
355
337
|
this.open = true;
|
|
356
338
|
}
|
|
357
339
|
this.renderWidget();
|
|
358
|
-
// Bootstrap has finished and status is now "ready". If `autoStart`
|
|
359
|
-
// is on AND the panel is open (either because `defaultOpen` /
|
|
360
|
-
// `alwaysOpen` just opened it, or because the visitor opened it
|
|
361
|
-
// before bootstrap finished and `handleOpenChange` is awaiting
|
|
362
|
-
// this very `ensureBootstrap` call), trigger the auto-start now.
|
|
363
|
-
// Without this, the `defaultOpen + autoStart` combination renders
|
|
364
|
-
// an empty panel with no CTA (autoStart suppresses the Start CTA)
|
|
365
|
-
// and the visitor sees no welcome message ever.
|
|
366
|
-
// `maybeAutoStartConversation` is idempotent — it guards on
|
|
367
|
-
// `autoStartTriggered` so calling it from multiple paths is safe.
|
|
368
|
-
this.maybeAutoStartConversation();
|
|
369
340
|
} catch (error) {
|
|
370
341
|
if (runVersion !== this.bootstrapVersion) {
|
|
371
342
|
return;
|
|
@@ -397,14 +368,19 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
397
368
|
*/
|
|
398
369
|
private async sendMessage(content: string) {
|
|
399
370
|
const trimmedContent = content.trim();
|
|
400
|
-
|
|
371
|
+
// Lazy session: no `conversationId` check here. If the visitor is
|
|
372
|
+
// sending their very first message, `submitWidgetMessage` will call
|
|
373
|
+
// POST /conversations/public/start under the hood before posting.
|
|
374
|
+
if (!trimmedContent || !this.sessionState) return;
|
|
401
375
|
|
|
402
376
|
const optimisticMessageId = `optimistic:${Date.now()}:${Math.random()
|
|
403
377
|
.toString(36)
|
|
404
378
|
.slice(2, 8)}`;
|
|
405
379
|
const optimisticMessage: WidgetMessage = {
|
|
406
380
|
id: optimisticMessageId,
|
|
407
|
-
|
|
381
|
+
// Empty when no conversation exists yet — the real id lands on the
|
|
382
|
+
// server row we swap in from `submitWidgetMessage`'s response.
|
|
383
|
+
conversationId: this.sessionState.conversationId ?? "",
|
|
408
384
|
role: "user",
|
|
409
385
|
content: trimmedContent,
|
|
410
386
|
createdAt: new Date().toISOString(),
|
|
@@ -480,6 +456,26 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
480
456
|
if (runVersion !== this.sendVersion) return;
|
|
481
457
|
this.sessionState = result.session;
|
|
482
458
|
|
|
459
|
+
// Lazy-start swap: when this was the first send, `submitWidgetMessage`
|
|
460
|
+
// created the conversation server-side and the server inserted its
|
|
461
|
+
// own welcome message row. Replace the placeholder client-welcome
|
|
462
|
+
// bubble with the real server row so ids/timestamps match the DB.
|
|
463
|
+
if (result.welcomeMessage) {
|
|
464
|
+
const clientWelcomeId = this.getClientWelcomeId();
|
|
465
|
+
this.messages = this.messages.map((m) =>
|
|
466
|
+
m.id === clientWelcomeId
|
|
467
|
+
? {
|
|
468
|
+
id: result.welcomeMessage!.id,
|
|
469
|
+
conversationId: result.conversationId,
|
|
470
|
+
role: "assistant",
|
|
471
|
+
content: result.welcomeMessage!.content,
|
|
472
|
+
createdAt: result.welcomeMessage!.createdAt,
|
|
473
|
+
}
|
|
474
|
+
: m,
|
|
475
|
+
);
|
|
476
|
+
this.renderWidget();
|
|
477
|
+
}
|
|
478
|
+
|
|
483
479
|
const revealSteps = buildAssistantMessageRevealSteps(
|
|
484
480
|
result.assistantMessages.map((message) => ({
|
|
485
481
|
id: message.id,
|
|
@@ -533,55 +529,34 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
533
529
|
if (this.open && (this.status === "idle" || !this.sessionState)) {
|
|
534
530
|
await this.ensureBootstrap();
|
|
535
531
|
}
|
|
532
|
+
}
|
|
536
533
|
|
|
537
|
-
|
|
534
|
+
private getClientWelcomeId(): string {
|
|
535
|
+
return `client-welcome:${this.agentId}`;
|
|
538
536
|
}
|
|
539
537
|
|
|
540
538
|
/**
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
539
|
+
* Lazy session seeding. When bootstrap finishes with no live conversation
|
|
540
|
+
* and no rehydrated transcript, drop a synthetic assistant bubble carrying
|
|
541
|
+
* the boot-config welcome copy into `this.messages`. Skipped when there's
|
|
542
|
+
* already a transcript (rehydrated session) or no welcome copy configured.
|
|
543
|
+
* The bubble's id is stable (`client-welcome:{agentId}`) so `processSend`
|
|
544
|
+
* can swap it for the real server row after the first successful send.
|
|
546
545
|
*/
|
|
547
|
-
private
|
|
548
|
-
if (this.
|
|
549
|
-
if (!this.open) return;
|
|
550
|
-
if (this.status !== "ready") return;
|
|
551
|
-
if (!this.sessionConfig) return;
|
|
552
|
-
if (!this.sessionConfig.widgetConfig.widgetBehavior?.autoStart) return;
|
|
546
|
+
private seedClientWelcomeIfNeeded() {
|
|
547
|
+
if (this.messages.length > 0) return;
|
|
553
548
|
if (this.sessionState?.conversationId) return;
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
this.status = "booting";
|
|
568
|
-
this.errorMessage = null;
|
|
569
|
-
this.renderWidget();
|
|
570
|
-
|
|
571
|
-
try {
|
|
572
|
-
const result = await startWidgetConversation({
|
|
573
|
-
agentId: this.agentId,
|
|
574
|
-
});
|
|
575
|
-
this.sessionState = result.session;
|
|
576
|
-
this.messages = result.messages;
|
|
577
|
-
this.status = "ready";
|
|
578
|
-
this.renderWidget();
|
|
579
|
-
} catch (error) {
|
|
580
|
-
this.status = "error";
|
|
581
|
-
this.errorMessage =
|
|
582
|
-
error instanceof Error ? error.message : "Failed to start conversation";
|
|
583
|
-
this.renderWidget();
|
|
584
|
-
}
|
|
549
|
+
const welcome = this.getWelcomeMessage();
|
|
550
|
+
if (!welcome) return;
|
|
551
|
+
this.messages = [
|
|
552
|
+
{
|
|
553
|
+
id: this.getClientWelcomeId(),
|
|
554
|
+
conversationId: "",
|
|
555
|
+
role: "assistant",
|
|
556
|
+
content: welcome,
|
|
557
|
+
createdAt: new Date().toISOString(),
|
|
558
|
+
},
|
|
559
|
+
];
|
|
585
560
|
}
|
|
586
561
|
|
|
587
562
|
private async handleStopConversation() {
|
|
@@ -14,6 +14,7 @@ import type { WidgetMessage } from "../types";
|
|
|
14
14
|
import {
|
|
15
15
|
splitWidgetPlacement,
|
|
16
16
|
type WidgetAppearance,
|
|
17
|
+
type WidgetMobileHeight,
|
|
17
18
|
type WidgetPlacement,
|
|
18
19
|
type WidgetPreset,
|
|
19
20
|
type WidgetVariant,
|
|
@@ -49,6 +50,11 @@ export type WidgetRuntimeProps = {
|
|
|
49
50
|
welcomeMessage: string | null;
|
|
50
51
|
spotlights: string[];
|
|
51
52
|
ctaLink?: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* How much of the screen the open panel covers on mobile. Ignored on
|
|
55
|
+
* desktop, where the panel floats above the launcher regardless.
|
|
56
|
+
*/
|
|
57
|
+
mobileHeight?: WidgetMobileHeight;
|
|
52
58
|
open: boolean;
|
|
53
59
|
/**
|
|
54
60
|
* When true, the embedded panel is pinned open and the launcher is hidden.
|
|
@@ -93,6 +99,7 @@ export function WidgetRuntime({
|
|
|
93
99
|
welcomeMessage,
|
|
94
100
|
spotlights,
|
|
95
101
|
ctaLink,
|
|
102
|
+
mobileHeight = "full",
|
|
96
103
|
open,
|
|
97
104
|
alwaysOpen = false,
|
|
98
105
|
status,
|
|
@@ -142,11 +149,13 @@ export function WidgetRuntime({
|
|
|
142
149
|
ctaLink,
|
|
143
150
|
actionLabel: appearance.actionText || undefined,
|
|
144
151
|
showStopConversation: Boolean(onStopConversation),
|
|
152
|
+
mobileHeight,
|
|
145
153
|
}),
|
|
146
154
|
[
|
|
147
155
|
agentAvatarUrl,
|
|
148
156
|
appearance,
|
|
149
157
|
ctaLink,
|
|
158
|
+
mobileHeight,
|
|
150
159
|
onStopConversation,
|
|
151
160
|
placement,
|
|
152
161
|
preset,
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -227,6 +227,15 @@ export async function bootstrapWidget(input: {
|
|
|
227
227
|
return bootstrapWidgetWithDeps(input, defaultBootstrapDeps);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Send a user message. Lazy-starts the conversation on the first send —
|
|
232
|
+
* `startPublicChat` is called transparently when no `conversationId` is
|
|
233
|
+
* stored yet, so visitors never create empty conversations by opening
|
|
234
|
+
* the panel and walking away. When a start happens as part of this call
|
|
235
|
+
* the server-inserted welcome message rides back on `welcomeMessage`
|
|
236
|
+
* so the caller can reconcile the client-side placeholder bubble with
|
|
237
|
+
* the real DB row.
|
|
238
|
+
*/
|
|
230
239
|
export async function submitWidgetMessage(input: {
|
|
231
240
|
agentId: string;
|
|
232
241
|
content: string;
|
|
@@ -236,37 +245,52 @@ export async function submitWidgetMessage(input: {
|
|
|
236
245
|
messages: WidgetMessage[];
|
|
237
246
|
userMessage: WidgetMessage;
|
|
238
247
|
assistantMessages: WidgetMessage[];
|
|
248
|
+
welcomeMessage?: WidgetMessage;
|
|
239
249
|
}> {
|
|
240
|
-
|
|
250
|
+
let stored = await ensureWidgetSession({
|
|
241
251
|
agentId: input.agentId,
|
|
242
252
|
pageUrl: window.location.href,
|
|
243
253
|
referrer: document.referrer || undefined,
|
|
244
254
|
});
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
|
|
256
|
+
let welcomeMessage: WidgetMessage | undefined;
|
|
257
|
+
let conversationId: string;
|
|
258
|
+
if (stored.conversationId) {
|
|
259
|
+
conversationId = stored.conversationId;
|
|
260
|
+
} else {
|
|
261
|
+
const started = await startPublicChat({
|
|
262
|
+
agentId: input.agentId,
|
|
263
|
+
sessionToken: stored.sessionToken,
|
|
264
|
+
});
|
|
265
|
+
conversationId = started.conversation.id;
|
|
266
|
+
stored = { ...stored, conversationId };
|
|
267
|
+
saveSession(input.agentId, stored);
|
|
268
|
+
welcomeMessage = started.welcomeMessage;
|
|
247
269
|
}
|
|
248
270
|
|
|
249
271
|
const response = await sendPublicChatMessage({
|
|
250
|
-
conversationId
|
|
272
|
+
conversationId,
|
|
251
273
|
sessionToken: stored.sessionToken,
|
|
252
274
|
content: input.content,
|
|
253
275
|
});
|
|
254
276
|
|
|
255
277
|
const nextSession: WidgetSessionState = {
|
|
256
278
|
...stored,
|
|
257
|
-
conversationId
|
|
279
|
+
conversationId,
|
|
258
280
|
};
|
|
259
281
|
saveSession(input.agentId, nextSession);
|
|
260
282
|
|
|
261
283
|
return {
|
|
262
284
|
session: nextSession,
|
|
263
|
-
conversationId
|
|
285
|
+
conversationId,
|
|
264
286
|
messages: sortByCreatedAt([
|
|
287
|
+
...(welcomeMessage ? [welcomeMessage] : []),
|
|
265
288
|
response.userMessage,
|
|
266
289
|
...response.assistantMessages,
|
|
267
290
|
]),
|
|
268
291
|
userMessage: response.userMessage,
|
|
269
292
|
assistantMessages: response.assistantMessages,
|
|
293
|
+
welcomeMessage,
|
|
270
294
|
};
|
|
271
295
|
}
|
|
272
296
|
|
|
@@ -337,39 +361,6 @@ export async function submitWidgetStopConfirmation(input: {
|
|
|
337
361
|
};
|
|
338
362
|
}
|
|
339
363
|
|
|
340
|
-
export async function startWidgetConversation(input: {
|
|
341
|
-
agentId: string;
|
|
342
|
-
}): Promise<{
|
|
343
|
-
session: WidgetSessionState;
|
|
344
|
-
conversationId: string;
|
|
345
|
-
messages: WidgetMessage[];
|
|
346
|
-
welcomeMessage: WidgetMessage;
|
|
347
|
-
}> {
|
|
348
|
-
const stored = await ensureWidgetSession({
|
|
349
|
-
agentId: input.agentId,
|
|
350
|
-
pageUrl: window.location.href,
|
|
351
|
-
referrer: document.referrer || undefined,
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const response = await startPublicChat({
|
|
355
|
-
agentId: input.agentId,
|
|
356
|
-
sessionToken: stored.sessionToken,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const nextSession: WidgetSessionState = {
|
|
360
|
-
...stored,
|
|
361
|
-
conversationId: response.conversation.id,
|
|
362
|
-
};
|
|
363
|
-
saveSession(input.agentId, nextSession);
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
session: nextSession,
|
|
367
|
-
conversationId: response.conversation.id,
|
|
368
|
-
messages: sortByCreatedAt([response.welcomeMessage]),
|
|
369
|
-
welcomeMessage: response.welcomeMessage,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
364
|
export async function resetWidgetSession(agentId: string): Promise<void> {
|
|
374
365
|
clearSession(agentId);
|
|
375
366
|
}
|
|
@@ -18,6 +18,27 @@ export type WidgetPreset = "default" | "facebook_messenger";
|
|
|
18
18
|
|
|
19
19
|
export const DEFAULT_WIDGET_PRESET: WidgetPreset = "default";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* How much of the screen the open panel covers on mobile. `full` is the
|
|
23
|
+
* edge-to-edge sheet; the others render a bottom drawer over the host page.
|
|
24
|
+
*/
|
|
25
|
+
export type WidgetMobileHeight = "half" | "three-quarter" | "full";
|
|
26
|
+
|
|
27
|
+
export const DEFAULT_WIDGET_MOBILE_HEIGHT: WidgetMobileHeight = "full";
|
|
28
|
+
|
|
29
|
+
export function normalizeWidgetMobileHeight(
|
|
30
|
+
value: string | null | undefined,
|
|
31
|
+
): WidgetMobileHeight {
|
|
32
|
+
switch (value) {
|
|
33
|
+
case "half":
|
|
34
|
+
case "three-quarter":
|
|
35
|
+
case "full":
|
|
36
|
+
return value;
|
|
37
|
+
default:
|
|
38
|
+
return DEFAULT_WIDGET_MOBILE_HEIGHT;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
export type WidgetAppearance = ResolvedChatWidgetAppearance;
|
|
22
43
|
|
|
23
44
|
export const DEFAULT_WIDGET_APPEARANCE: WidgetAppearance =
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
WidgetAppearance,
|
|
3
3
|
WidgetBehavior,
|
|
4
|
+
WidgetMobileHeight,
|
|
4
5
|
WidgetPlacement,
|
|
5
6
|
WidgetPreset,
|
|
6
7
|
WidgetVariant,
|
|
@@ -10,14 +11,17 @@ import type {
|
|
|
10
11
|
export {
|
|
11
12
|
DEFAULT_WIDGET_APPEARANCE,
|
|
12
13
|
DEFAULT_WIDGET_BEHAVIOR,
|
|
14
|
+
DEFAULT_WIDGET_MOBILE_HEIGHT,
|
|
13
15
|
DEFAULT_WIDGET_PRESET,
|
|
14
16
|
type WidgetAppearance,
|
|
15
17
|
type WidgetBehavior,
|
|
18
|
+
type WidgetMobileHeight,
|
|
16
19
|
type WidgetPlacement,
|
|
17
20
|
type WidgetPreset,
|
|
18
21
|
type WidgetVariant,
|
|
19
22
|
normalizeWidgetAppearance,
|
|
20
23
|
normalizeWidgetBehavior,
|
|
24
|
+
normalizeWidgetMobileHeight,
|
|
21
25
|
normalizeWidgetPlacement,
|
|
22
26
|
normalizeWidgetPreset,
|
|
23
27
|
normalizeWidgetVariant,
|
|
@@ -41,6 +45,12 @@ export type WidgetSessionConfig = {
|
|
|
41
45
|
widgetVariant: WidgetVariant;
|
|
42
46
|
widgetPreset: WidgetPreset;
|
|
43
47
|
widgetPlacement: WidgetPlacement;
|
|
48
|
+
/**
|
|
49
|
+
* Mobile presentation of the open panel. Optional on the wire so an
|
|
50
|
+
* embed running against an older API (which doesn't send the field)
|
|
51
|
+
* still boots — it falls back to the edge-to-edge default.
|
|
52
|
+
*/
|
|
53
|
+
mobileHeight?: WidgetMobileHeight | null;
|
|
44
54
|
allowedDomains: string[];
|
|
45
55
|
triggerRule: WidgetTriggerRule | null;
|
|
46
56
|
};
|