@courseecho/ai-widget-react 1.0.23 → 1.0.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.
@@ -1,12 +1,8 @@
1
1
  /**
2
- * ShadowWrapper — renders children inside a Shadow DOM so the widget's CSS
3
- * is completely isolated from the host application's styles.
2
+ * ShadowWrapper — renders children inside a Shadow DOM.
4
3
  *
5
- * Key isolation techniques used:
6
- * 1. :host { all: initial } — kills every inherited CSS property at the boundary
7
- * 2. Full element reset inside the shadow — counters any UA-sheet or inherited font changes
8
- * 3. Google Fonts injected into document <head> (fonts are shared across shadow trees)
9
- * 4. React portal into a container that lives inside the shadow root
4
+ * CSS lives in ONE place: packages/core/src/widget-css.ts
5
+ * Resolved via the npm workspace symlink no install needed.
10
6
  */
11
7
  import React from 'react';
12
8
  export declare const ShadowWrapper: React.FC<{
@@ -1,17 +1,14 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * ShadowWrapper — renders children inside a Shadow DOM so the widget's CSS
4
- * is completely isolated from the host application's styles.
3
+ * ShadowWrapper — renders children inside a Shadow DOM.
5
4
  *
6
- * Key isolation techniques used:
7
- * 1. :host { all: initial } — kills every inherited CSS property at the boundary
8
- * 2. Full element reset inside the shadow — counters any UA-sheet or inherited font changes
9
- * 3. Google Fonts injected into document <head> (fonts are shared across shadow trees)
10
- * 4. React portal into a container that lives inside the shadow root
5
+ * CSS lives in ONE place: packages/core/src/widget-css.ts
6
+ * Resolved via the npm workspace symlink no install needed.
11
7
  */
12
8
  import { useRef, useEffect, useState } from 'react';
13
9
  import { createPortal } from 'react-dom';
14
- // Inject Inter font into the document <head> once (fonts ARE shared across shadow DOM boundaries)
10
+ import { WIDGET_CSS } from '@courseecho/ai-core-sdk';
11
+ // Inter font must be in document <head> — fonts are shared across shadow DOM boundaries
15
12
  function ensureFont() {
16
13
  if (typeof document === 'undefined')
17
14
  return;
@@ -23,186 +20,18 @@ function ensureFont() {
23
20
  link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap';
24
21
  document.head.appendChild(link);
25
22
  }
26
- const WIDGET_CSS = `
27
- /* ── Total isolation from host app ───────────────────────────────────────── */
28
- :host {
29
- all: initial !important;
30
- display: block !important;
31
- }
32
-
33
- /* Guarantee every element inside the shadow starts from a known baseline */
34
- *, *::before, *::after {
35
- box-sizing: border-box !important;
36
- margin: 0 !important;
37
- padding: 0 !important;
38
- border: 0 !important;
39
- font-size: 100% !important;
40
- font: inherit !important;
41
- vertical-align: baseline !important;
42
- line-height: inherit !important;
43
- color: inherit !important;
44
- background: transparent !important;
45
- text-decoration: none !important;
46
- list-style: none !important;
47
- }
48
-
49
- /* Restore sensible block/inline defaults that the above stripped */
50
- div, section, article, header, footer, main, nav, aside { display: block !important; }
51
- span, em, strong, small, code, kbd { display: inline !important; }
52
- button { display: inline-flex !important; cursor: pointer !important; }
53
- textarea, input { display: block !important; }
54
- ul, ol { display: flex !important; flex-direction: column !important; }
55
- a { cursor: pointer !important; }
56
-
57
- /* ── Widget component styles ─────────────────────────────────────────────── */
58
- .aiwg-root *, .aiwg-root *::before, .aiwg-root *::after { box-sizing: border-box; margin: 0; padding: 0; }
59
-
60
- .aiwg-root {
61
- --aiwg-primary: #6366f1;
62
- --aiwg-primary-dark: #8b5cf6;
63
- --aiwg-fg-color: #ffffff;
64
- --aiwg-bg-color: #ffffff;
65
- --aiwg-chat-bg: #f1f5f9;
66
-
67
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
68
- font-size: 14px; line-height: 1.5; color: #1a1a2e;
69
- display: flex; flex-direction: column; overflow: hidden;
70
- border-radius: 16px; background: var(--aiwg-bg-color);
71
- position: fixed; bottom: 24px; right: 24px;
72
- width: 380px !important; max-width: calc(100vw - 48px) !important;
73
- z-index: 99999;
74
- transition: height 0.25s ease;
75
- box-shadow: 0 24px 64px rgba(0,0,0,0.18);
76
- }
77
-
78
- .aiwg-header { background: linear-gradient(135deg,#6366f1 0%,#8b5cf6 100%); padding: 16px 20px; display: flex; align-items: center; gap: 12px; flex-shrink: 0; position: relative; overflow: hidden; }
79
- .aiwg-header::before { content:''; position:absolute; width:140px; height:140px; background:rgba(255,255,255,.08); border-radius:50%; top:-50px; right:-30px; }
80
- .aiwg-avatar { width:40px; height:40px; background:rgba(255,255,255,.25); border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:18px; flex-shrink:0; border:2px solid rgba(255,255,255,.4); }
81
- .aiwg-header-info { flex:1; }
82
- .aiwg-title { color:#fff; font-weight:600; font-size:15px; }
83
- .aiwg-subtitle { color:rgba(255,255,255,.75); font-size:12px; display:flex; align-items:center; gap:5px; }
84
- .aiwg-online-dot { width:7px; height:7px; background:#4ade80; border-radius:50%; display:inline-block; animation:aiwg-pulse 2s ease-in-out infinite; }
85
- @keyframes aiwg-pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.6;transform:scale(.85)} }
86
- .aiwg-minimize-btn { width:36px; height:36px; background:rgba(255,255,255,.18); border:1.5px solid rgba(255,255,255,.3); border-radius:50%; color:#fff; cursor:pointer !important; display:flex; align-items:center; justify-content:center; font-size:18px; line-height:1; transition:background .15s,transform .15s; flex-shrink:0; position:relative; z-index:2; }
87
- .aiwg-minimize-btn * { pointer-events:none; }
88
- .aiwg-minimize-btn:hover { background:rgba(255,255,255,.32); transform:scale(1.08); }
89
-
90
- .aiwg-messages { flex:1; overflow-y:auto; padding:20px 16px 8px; scroll-behavior:smooth; display:flex; flex-direction:column; gap:12px; }
91
- .aiwg-messages::-webkit-scrollbar { width:4px; }
92
- .aiwg-messages::-webkit-scrollbar-thumb { background:#e2e8f0; border-radius:99px; }
93
-
94
- .aiwg-welcome { text-align:center; padding:24px 16px; display:flex; flex-direction:column; align-items:center; gap:8px; }
95
- .aiwg-welcome-icon { font-size:36px; margin-bottom:4px; }
96
- .aiwg-welcome h4 { font-size:15px; font-weight:600; color:#1a1a2e; }
97
- .aiwg-welcome p { font-size:13px; color:#64748b; }
98
-
99
- .aiwg-msg { display:flex; gap:8px; align-items:flex-end; animation:aiwg-fade-up .2s ease both; }
100
- @keyframes aiwg-fade-up { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
101
- .aiwg-msg--user { flex-direction:row-reverse; }
102
- .aiwg-msg-avatar { width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:13px; flex-shrink:0; margin-bottom:2px; }
103
- .aiwg-msg--bot .aiwg-msg-avatar { background:linear-gradient(135deg,#6366f1,#8b5cf6); color:#fff; }
104
- .aiwg-msg--user .aiwg-msg-avatar { background:#e2e8f0; color:#64748b; }
105
- .aiwg-msg-body { max-width:78%; }
106
- .aiwg-msg-bubble { padding:10px 14px; border-radius:18px; font-size:14px; line-height:1.55; word-break:break-word; }
107
- .aiwg-msg--bot .aiwg-msg-bubble { background:#f1f5f9; color:#1a1a2e; border-bottom-left-radius:4px; }
108
- .aiwg-msg--user .aiwg-msg-bubble { background:linear-gradient(135deg,#6366f1,#8b5cf6); color:#fff; border-bottom-right-radius:4px; }
109
- .aiwg-msg-time { font-size:10px; color:#94a3b8; margin-top:4px; text-align:right; padding:0 4px; }
110
- .aiwg-msg--bot .aiwg-msg-time { text-align:left; }
111
-
112
- .aiwg-msg-bubble strong { font-weight:600; }
113
- .aiwg-msg-bubble em { font-style:italic; }
114
- .aiwg-msg-bubble code { background:rgba(0,0,0,.08); padding:1px 5px; border-radius:4px; font-family:'Fira Code','Consolas',monospace; font-size:.9em; }
115
- .aiwg-msg--user .aiwg-msg-bubble code { background:rgba(255,255,255,.2); }
116
- .aiwg-msg-bubble ol, .aiwg-msg-bubble ul { padding-left:18px; margin:6px 0; display:flex; flex-direction:column; gap:3px; }
117
- .aiwg-msg-bubble ol { list-style:decimal; }
118
- .aiwg-msg-bubble ul { list-style:disc; }
119
- .aiwg-msg-bubble li { line-height:1.5; }
120
- .aiwg-msg-bubble h3,.aiwg-msg-bubble h4 { font-weight:600; margin:8px 0 4px; }
121
- .aiwg-msg-bubble p { margin:2px 0; }
122
-
123
- .aiwg-typing { padding:10px 14px; }
124
- .aiwg-typing-dots { display:flex; gap:4px; align-items:center; }
125
- .aiwg-typing-dots span { width:7px; height:7px; background:#94a3b8; border-radius:50%; animation:aiwg-bounce 1.2s ease-in-out infinite; }
126
- .aiwg-typing-dots span:nth-child(2) { animation-delay:.2s; }
127
- .aiwg-typing-dots span:nth-child(3) { animation-delay:.4s; }
128
- @keyframes aiwg-bounce { 0%,80%,100%{transform:translateY(0)} 40%{transform:translateY(-6px)} }
129
-
130
- .aiwg-chip-row { padding:6px 16px 2px; display:flex; flex-wrap:wrap; gap:6px; flex-shrink:0; }
131
- .aiwg-chip { background:#f1f5f9; border:1px solid #e2e8f0; border-radius:99px; padding:4px 12px; font-size:12px; color:#4f46e5; cursor:pointer; transition:all .15s; white-space:nowrap; font-weight:500; }
132
- .aiwg-chip:hover { background:#ede9fe; border-color:#a5b4fc; }
133
-
134
- .aiwg-input-area { padding:12px 16px 16px; flex-shrink:0; background:#fff; border-top:1px solid #f1f5f9; position:relative; }
135
-
136
- .aiwg-suggestions { position:absolute; bottom:calc(100% + 4px); left:16px; right:16px; background:#fff; border:1px solid #e2e8f0; border-radius:12px; box-shadow:0 8px 24px rgba(0,0,0,.12); overflow:hidden; z-index:100; animation:aiwg-fade-up .15s ease both; }
137
- .aiwg-suggestions-header { padding:8px 14px 4px; font-size:11px; font-weight:600; color:#94a3b8; text-transform:uppercase; letter-spacing:.06em; border-bottom:1px solid #f1f5f9; }
138
- .aiwg-suggestion-item { display:flex; align-items:center; gap:10px; padding:10px 14px; cursor:pointer; transition:background .1s; border-bottom:1px solid #f8fafc; }
139
- .aiwg-suggestion-item:last-child { border-bottom:none; }
140
- .aiwg-suggestion-item:hover,.aiwg-suggestion-item.aiwg-active { background:#f5f3ff; }
141
- .aiwg-suggestion-icon { font-size:16px; flex-shrink:0; }
142
- .aiwg-suggestion-main { flex:1; min-width:0; }
143
- .aiwg-suggestion-text { font-size:13px; font-weight:500; color:#1a1a2e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
144
- .aiwg-suggestion-text mark { background:none; color:#6366f1; font-weight:600; }
145
- .aiwg-suggestion-desc { font-size:11px; color:#94a3b8; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
146
- .aiwg-suggestion-badge { font-size:10px; font-weight:600; padding:2px 8px; border-radius:99px; background:#ede9fe; color:#6366f1; flex-shrink:0; }
147
- .aiwg-kbd-hint { padding:6px 14px; font-size:11px; color:#cbd5e1; background:#f8fafc; display:flex; justify-content:flex-end; gap:10px; }
148
- .aiwg-kbd-hint kbd { font-family:inherit; background:#e2e8f0; border-radius:4px; padding:1px 5px; font-size:10px; color:#64748b; }
149
-
150
- .aiwg-input-row { display:flex; gap:8px; align-items:flex-end; }
151
- .aiwg-input { flex:1; border:1.5px solid #e2e8f0; border-radius:12px; padding:10px 14px; font-size:14px; font-family:inherit; outline:none; resize:none; background:#f8fafc; color:#1a1a2e; transition:border-color .15s,box-shadow .15s,background .15s; line-height:1.4; max-height:120px; overflow-y:auto; scrollbar-width:none; -ms-overflow-style:none; }
152
- .aiwg-input::-webkit-scrollbar { display:none; }
153
- .aiwg-input::placeholder { color:#94a3b8; }
154
- .aiwg-input:focus { border-color:#8b5cf6; background:#fff; box-shadow:0 0 0 3px rgba(139,92,246,.1); }
155
- .aiwg-send-btn { width:42px; height:42px; background:linear-gradient(135deg,#6366f1,#8b5cf6); border:none; border-radius:12px; color:#fff; cursor:pointer; display:flex; align-items:center; justify-content:center; flex-shrink:0; transition:transform .15s,box-shadow .15s,opacity .15s; box-shadow:0 4px 12px rgba(99,102,241,.35); }
156
- .aiwg-send-btn svg { width:18px; height:18px; }
157
- .aiwg-send-btn:hover { transform:translateY(-1px); box-shadow:0 6px 16px rgba(99,102,241,.4); }
158
- .aiwg-send-btn:active { transform:translateY(0); }
159
- .aiwg-send-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
160
-
161
- .aiwg-error { margin:0 16px 8px; padding:8px 12px; background:#fef2f2; border:1px solid #fecaca; border-radius:8px; font-size:12px; color:#dc2626; }
162
-
163
- .aiwg-root.aiwg-minimized .aiwg-messages,
164
- .aiwg-root.aiwg-minimized .aiwg-chip-row,
165
- .aiwg-root.aiwg-minimized .aiwg-error,
166
- .aiwg-root.aiwg-minimized .aiwg-input-area,
167
- .aiwg-root.aiwg-minimized .aiwg-powered { display:none !important; }
168
- .aiwg-root.aiwg-minimized .aiwg-minimize-btn { display:none !important; }
169
- .aiwg-root.aiwg-minimized { cursor:pointer; }
170
- .aiwg-root.aiwg-minimized .aiwg-header { cursor:pointer !important; }
171
-
172
- .aiwg-powered { text-align:center; padding:6px 16px 10px; font-size:11px; color:#94a3b8; flex-shrink:0; border-top:1px solid #f1f5f9; background:#fff; }
173
- .aiwg-powered a { color:#6366f1; text-decoration:none; font-weight:500; }
174
- .aiwg-powered a:hover { text-decoration:underline; }
175
-
176
- .aiwg-dark { background:#0f172a; color:#e2e8f0; }
177
- .aiwg-dark .aiwg-msg--bot .aiwg-msg-bubble { background:#1e293b; color:#e2e8f0; }
178
- .aiwg-dark .aiwg-input-area { background:#0f172a; border-top-color:#1e293b; }
179
- .aiwg-dark .aiwg-input { background:#1e293b; border-color:#334155; color:#e2e8f0; }
180
- .aiwg-dark .aiwg-input:focus { border-color:#8b5cf6; background:#1e293b; }
181
- .aiwg-dark .aiwg-suggestions { background:#1e293b; border-color:#334155; }
182
- .aiwg-dark .aiwg-suggestions-header { color:#64748b; border-bottom-color:#334155; }
183
- .aiwg-dark .aiwg-suggestion-item { border-bottom-color:#0f172a; }
184
- .aiwg-dark .aiwg-suggestion-item:hover,.aiwg-dark .aiwg-suggestion-item.aiwg-active { background:#2d1b69; }
185
- .aiwg-dark .aiwg-suggestion-text { color:#e2e8f0; }
186
- .aiwg-dark .aiwg-chip { background:#1e293b; border-color:#334155; }
187
- .aiwg-dark .aiwg-chip:hover { background:#2d1b69; }
188
- .aiwg-dark .aiwg-kbd-hint { background:#1e293b; }
189
- .aiwg-dark .aiwg-welcome h4 { color:#e2e8f0; }
190
- .aiwg-dark .aiwg-powered { background:#0f172a; border-top-color:#1e293b; }
191
- `;
192
23
  export const ShadowWrapper = ({ children }) => {
193
24
  const hostRef = useRef(null);
194
25
  const [portalTarget, setPortalTarget] = useState(null);
195
26
  useEffect(() => {
196
- ensureFont(); // inject Inter into <head> — fonts are shared across shadow boundaries
27
+ ensureFont();
197
28
  const host = hostRef.current;
198
29
  if (!host || host.shadowRoot)
199
30
  return;
200
31
  const shadow = host.attachShadow({ mode: 'open' });
201
- // Inject all widget CSS into the shadow root
202
32
  const style = document.createElement('style');
203
33
  style.textContent = WIDGET_CSS;
204
34
  shadow.appendChild(style);
205
- // The container where React will portal the widget tree
206
35
  const container = document.createElement('div');
207
36
  shadow.appendChild(container);
208
37
  setPortalTarget(container);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@courseecho/ai-widget-react",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "React AI chat widget component for CourseEcho.",
5
5
  "license": "MIT",
6
6
  "author": "CourseEcho",