@usereq/widget 0.2.24 → 0.2.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usereq/widget",
3
- "version": "0.2.24",
3
+ "version": "0.2.25",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,12 +1,81 @@
1
1
  import type { CSSProperties } from "react";
2
2
 
3
+ export type ChatWidgetGradient = {
4
+ from: string;
5
+ to: string;
6
+ angle?: number | null;
7
+ };
8
+
9
+ /** Solid hex or gradient. Mirrors `ChatColor` in `@usereq/ui`. */
10
+ export type ChatWidgetColor = string | ChatWidgetGradient;
11
+
12
+ export type ChatWidgetIconPack = "lucide" | "heroicons" | "phosphor";
13
+
14
+ export type ChatWidgetIconSlot =
15
+ | "launcher"
16
+ | "send"
17
+ | "close"
18
+ | "minimize"
19
+ | "attach"
20
+ | "mic";
21
+
22
+ export type ChatWidgetIcons = {
23
+ pack?: ChatWidgetIconPack | null;
24
+ overrides?: Partial<Record<ChatWidgetIconSlot, string>> | null;
25
+ };
26
+
27
+ /**
28
+ * Every appearance token the agents row can store.
29
+ *
30
+ * This deliberately mirrors `ChatAppearance` in `@usereq/ui` field-for-field,
31
+ * but is declared standalone rather than imported: `shared/widget-config` is
32
+ * consumed by `apps/api`, which has no `@usereq/ui` dependency. Structural
33
+ * typing means the two still line up where they meet.
34
+ *
35
+ * Keep in sync with `widgetAppearanceSchema` in the API's `widget.model.ts`
36
+ * and `AgentWidgetAppearance` in the dashboard.
37
+ */
3
38
  export type ChatWidgetAppearance = {
39
+ // — Legacy gradient (still the fallback when `primary` is unset) —
4
40
  avatarOrbColor1?: string | null;
5
41
  avatarOrbColor2?: string | null;
42
+
43
+ // — Brand —
44
+ primary?: ChatWidgetColor | null;
45
+ primaryForeground?: string | null;
46
+ secondary?: string | null;
47
+ secondaryForeground?: string | null;
6
48
  actionText?: string | null;
49
+
50
+ // — Surface tokens (panel) —
51
+ background?: string | null;
52
+ foreground?: string | null;
53
+ muted?: string | null;
54
+ mutedForeground?: string | null;
55
+ border?: string | null;
56
+ ring?: string | null;
57
+
58
+ // — Granular text overrides —
59
+ textPrimary?: string | null;
60
+ textForeground?: string | null;
61
+ textMutedForeground?: string | null;
62
+
63
+ // — Icon pack swap (persist-only for now) —
64
+ icons?: ChatWidgetIcons | null;
7
65
  };
8
66
 
9
- export type ResolvedChatWidgetAppearance = Required<ChatWidgetAppearance>;
67
+ /**
68
+ * Only the three legacy fields are guaranteed present — every other token
69
+ * stays optional on purpose, because `undefined` means "not customized, use
70
+ * the design-system default". Filling them with defaults here would emit
71
+ * `--chat-*` variables for tokens the user never set and lock the panel out
72
+ * of the host theme.
73
+ */
74
+ export type ResolvedChatWidgetAppearance = ChatWidgetAppearance & {
75
+ avatarOrbColor1: string;
76
+ avatarOrbColor2: string;
77
+ actionText: string;
78
+ };
10
79
 
11
80
  export const DEFAULT_CHAT_WIDGET_APPEARANCE: ResolvedChatWidgetAppearance = {
12
81
  avatarOrbColor1: "#2563eb",
@@ -14,10 +83,20 @@ export const DEFAULT_CHAT_WIDGET_APPEARANCE: ResolvedChatWidgetAppearance = {
14
83
  actionText: "Need help?",
15
84
  };
16
85
 
86
+ /**
87
+ * Fills defaults for the three legacy fields and passes every other token
88
+ * through untouched.
89
+ *
90
+ * The spread is load-bearing: this function used to construct a fresh
91
+ * three-key object, which silently discarded the entire saved theme
92
+ * (background, primary, text colors…) on its way to the embed. Any new token
93
+ * added to `ChatWidgetAppearance` now survives without editing this function.
94
+ */
17
95
  export function resolveChatWidgetAppearance(
18
96
  appearance?: Partial<ChatWidgetAppearance> | null,
19
97
  ): ResolvedChatWidgetAppearance {
20
98
  return {
99
+ ...(appearance ?? {}),
21
100
  avatarOrbColor1:
22
101
  normalizeString(appearance?.avatarOrbColor1) ??
23
102
  DEFAULT_CHAT_WIDGET_APPEARANCE.avatarOrbColor1,
@@ -168,7 +168,13 @@ export class AgentWidgetElement extends HTMLElement {
168
168
  const base =
169
169
  this.sessionConfig?.widgetConfig.widgetAppearance ?? DEFAULT_WIDGET_APPEARANCE;
170
170
 
171
+ // Spread first, then apply the three attribute-overridable fields on top.
172
+ // Only those three are exposed as HTML attributes; every other token
173
+ // (surface colors, brand, text) comes from the agent config and must
174
+ // pass through untouched — rebuilding a fixed-key object here is what
175
+ // used to drop a customer's saved theme on the floor.
171
176
  return {
177
+ ...base,
172
178
  avatarOrbColor1:
173
179
  this.appearanceAttributes.avatarOrbColor1?.trim() ||
174
180
  base.avatarOrbColor1,
@@ -255,12 +255,22 @@ function mapPreset(preset: WidgetPreset): ChatWidgetConfig["preset"] {
255
255
  return preset;
256
256
  }
257
257
 
258
+ /**
259
+ * `WidgetAppearance` mirrors `ChatAppearance` field-for-field (see
260
+ * `chat-widget/chat-widget-appearance.ts` for why it's declared separately),
261
+ * so this is a structural pass-through rather than a field-by-field copy.
262
+ *
263
+ * It used to rebuild a three-key object, which meant that even once the API
264
+ * started sending the full theme, the surface and brand tokens were dropped
265
+ * again right here — one strip point behind the other.
266
+ */
258
267
  function toChatAppearance(appearance: WidgetAppearance): ChatAppearance {
259
- return {
260
- avatarOrbColor1: appearance.avatarOrbColor1 ?? null,
261
- avatarOrbColor2: appearance.avatarOrbColor2 ?? null,
262
- actionText: appearance.actionText ?? null,
263
- };
268
+ // Deliberately NOT a cast. Returning the value unasserted makes the
269
+ // compiler verify that `WidgetAppearance` is still structurally
270
+ // assignable to `ChatAppearance` — so if the two ever drift (a token
271
+ // added to one and not the other), this fails typecheck instead of
272
+ // silently dropping the field the way the old field-by-field copy did.
273
+ return appearance;
264
274
  }
265
275
 
266
276
  function isHtmlElement(