@dolard.eu/versiq-widget 0.1.0 → 0.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/README.md +252 -106
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @dolard.eu/versiq-widget
|
|
2
2
|
|
|
3
|
-
Versiq Widget SDK
|
|
3
|
+
Versiq Widget SDK — embed a conversational conversion agent that answers
|
|
4
|
+
visitors using **your** site's data (catalogue, inventory, CRM, knowledge base),
|
|
5
|
+
not the public-web average a generic LLM assistant returns.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -14,66 +16,148 @@ pnpm add @dolard.eu/versiq-widget
|
|
|
14
16
|
|
|
15
17
|
### Script Tag (CDN)
|
|
16
18
|
|
|
17
|
-
Hot-link from any public npm CDN, pinned to a version
|
|
19
|
+
Hot-link from any public npm CDN, pinned to a version, with
|
|
20
|
+
[Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
|
|
21
|
+
(SRI) so the browser refuses to run a tampered bundle.
|
|
22
|
+
|
|
23
|
+
> **Before copy-pasting**, replace `<VERSION>` with the latest published version
|
|
24
|
+
> (see [npm](https://www.npmjs.com/package/@dolard.eu/versiq-widget)) and
|
|
25
|
+
> `<SRI_HASH>` with the regenerated SHA-384 (see _Regenerating the SRI hash_
|
|
26
|
+
> below). The placeholder values are intentional — every release ships a new
|
|
27
|
+
> bundle, so a hash hardcoded here would always be stale.
|
|
18
28
|
|
|
19
29
|
```html
|
|
20
30
|
<script
|
|
21
|
-
src="https://unpkg.com/@dolard.eu/versiq-widget
|
|
31
|
+
src="https://unpkg.com/@dolard.eu/versiq-widget@<VERSION>/dist/widget.umd.js"
|
|
32
|
+
integrity="sha384-<SRI_HASH>"
|
|
33
|
+
crossorigin="anonymous"
|
|
22
34
|
data-api-key="pk_your_key"
|
|
23
|
-
data-theme='{"primaryColor":"#3B82F6"}'
|
|
24
35
|
></script>
|
|
25
36
|
```
|
|
26
37
|
|
|
27
|
-
Equivalent via jsDelivr
|
|
38
|
+
Equivalent via jsDelivr (same hash — both CDNs serve the bytes published to npm,
|
|
39
|
+
so the `integrity` value is identical):
|
|
28
40
|
|
|
29
41
|
```html
|
|
30
42
|
<script
|
|
31
|
-
src="https://cdn.jsdelivr.net/npm/@dolard.eu/versiq-widget
|
|
43
|
+
src="https://cdn.jsdelivr.net/npm/@dolard.eu/versiq-widget@<VERSION>/dist/widget.umd.js"
|
|
44
|
+
integrity="sha384-<SRI_HASH>"
|
|
45
|
+
crossorigin="anonymous"
|
|
32
46
|
data-api-key="pk_your_key"
|
|
33
47
|
></script>
|
|
34
48
|
```
|
|
35
49
|
|
|
36
50
|
Pinning a version avoids breaking changes from later releases. To always follow
|
|
37
|
-
the latest, drop
|
|
51
|
+
the latest, drop `@<VERSION>` — but then **also drop `integrity`**: SRI locks
|
|
52
|
+
the bundle to one specific publish, you cannot pin "latest hash".
|
|
53
|
+
|
|
54
|
+
#### Regenerating the SRI hash after a version bump
|
|
55
|
+
|
|
56
|
+
Each `@dolard.eu/versiq-widget` release ships a new bundle, so the SRI hash must
|
|
57
|
+
be regenerated and re-pasted into the snippet. From any shell with `curl` and
|
|
58
|
+
`openssl` available:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
curl -sSL https://unpkg.com/@dolard.eu/versiq-widget@<version>/dist/widget.umd.js \
|
|
62
|
+
| openssl dgst -sha384 -binary | openssl base64 -A
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Prefix the output with `sha384-` and copy it as the `integrity` attribute value.
|
|
66
|
+
Verify the result by reloading the host page in a browser — the DevTools Console
|
|
67
|
+
reports a clear error if the hash mismatches.
|
|
38
68
|
|
|
39
69
|
### Programmatic API
|
|
40
70
|
|
|
71
|
+
The minimal integration is one line — every visual and behavioural aspect
|
|
72
|
+
(theme, position, language, open-state, branding) is configured in the **Versiq
|
|
73
|
+
admin portal** for your Application and resolved server-side from the API key.
|
|
74
|
+
|
|
41
75
|
```typescript
|
|
42
|
-
import { createWidget
|
|
76
|
+
import { createWidget } from "@dolard.eu/versiq-widget";
|
|
43
77
|
|
|
44
|
-
const widget = createWidget({
|
|
45
|
-
apiKey: "pk_your_key",
|
|
46
|
-
position: "bottom-right",
|
|
47
|
-
theme: { primaryColor: "#3B82F6" },
|
|
48
|
-
});
|
|
78
|
+
const widget = createWidget({ apiKey: "pk_live_your_key" });
|
|
49
79
|
|
|
50
|
-
// Control the widget
|
|
80
|
+
// Control the widget at runtime
|
|
51
81
|
widget.open();
|
|
52
82
|
widget.close();
|
|
53
83
|
widget.reset();
|
|
54
84
|
|
|
55
|
-
// Cleanup
|
|
85
|
+
// Cleanup (e.g., on SPA route change)
|
|
56
86
|
widget.destroy();
|
|
57
87
|
```
|
|
58
88
|
|
|
89
|
+
## Why this widget converts
|
|
90
|
+
|
|
91
|
+
Two reasons most platforms fail to convert mobile visitors, and two reasons this
|
|
92
|
+
widget does:
|
|
93
|
+
|
|
94
|
+
1. **It speaks with your data, not the public-web average.** The agent answers
|
|
95
|
+
from your catalogue, your stock, your CRM, your knowledge base — what your
|
|
96
|
+
competitors and generic AI assistants don't have.
|
|
97
|
+
2. **It is driven by your thumb, not your keyboard.** The LLM dynamically picks
|
|
98
|
+
the right component at every turn — quick replies, sliders, product cards —
|
|
99
|
+
instead of forcing a form. Visitors qualify their need in a few taps on
|
|
100
|
+
mobile, where 3+ field forms lose 50%+ of users (HubSpot).
|
|
101
|
+
|
|
102
|
+
The widget rendering layer (`QuickReplies`, `PropertyCard`, `ActionButtons`) is
|
|
103
|
+
the runtime that materialises this. Schemas live in the Versiq backend
|
|
104
|
+
repository and are out of scope for the SDK consumer.
|
|
105
|
+
|
|
59
106
|
## Configuration
|
|
60
107
|
|
|
61
|
-
|
|
108
|
+
Widget configuration is split into three scopes — only the first one is your
|
|
109
|
+
responsibility as an integrator.
|
|
110
|
+
|
|
111
|
+
### 1. Host-side (passed to `createWidget` or as `data-*` attributes)
|
|
112
|
+
|
|
113
|
+
These can only live on the integrator's page because they describe the host
|
|
114
|
+
context (DOM, identity, environment).
|
|
115
|
+
|
|
116
|
+
| Option | Type | Default | Description |
|
|
117
|
+
| ----------- | ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `apiKey` | `string` | **required** | Publishable API key (`pk_live_*`, `pk_test_*`). Binds the widget to one Application. |
|
|
119
|
+
| `container` | `HTMLElement \| string` | - | DOM container for inline mode. Required only when the admin has set `position: "inline"` on the portal. |
|
|
120
|
+
| `baseUrl` | `string` | Production URL | Override the widget iframe origin. Only useful for sandbox / self-hosted setups. |
|
|
121
|
+
| `debug` | `boolean` | `false` | Enable debug logging in the browser console. |
|
|
122
|
+
| `email` | `string` | - | Pre-identified visitor email — must be paired with `userHash`. |
|
|
123
|
+
| `userId` | `string` | - | Host-side stable user identifier — must be paired with `userHash`. |
|
|
124
|
+
| `userHash` | `string` | - | HMAC-SHA256 of `email` (or `userId`), signed with the Application identity secret. See _Identity_. |
|
|
125
|
+
|
|
126
|
+
### 2. Server-resolved (configured in the Versiq admin portal)
|
|
127
|
+
|
|
128
|
+
These are **not** passed by the integration — they are stored in the
|
|
129
|
+
Application's `widget_config` JSONB row and fetched at bootstrap from the API
|
|
130
|
+
key. To change any of them, edit the Application in the portal.
|
|
131
|
+
|
|
132
|
+
| Field | Configured in admin | Notes |
|
|
133
|
+
| ----------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| `theme` | Apparence → palette | Full palette (`primaryColor`, `backgroundColor`, `textColor`, `borderRadius`, `fontFamily`, `colorScheme`). |
|
|
135
|
+
| `position` | Apparence → position | `"bottom-right"`, `"bottom-left"`, or `"inline"`. |
|
|
136
|
+
| `language` | Apparence → langue | ISO 639-1 (e.g. `fr`, `en`). Falls back to browser language when unset. |
|
|
137
|
+
| `showProfile` | Apparence → panneau profil | Toggles the profile panel in the widget header. |
|
|
138
|
+
| `open` | Apparence → état initial | Default open state on page load. Host can still call `widget.open()` programmatically. |
|
|
139
|
+
| `brand.title` | Branding → titre | Custom header title (falls back to a vertical-specific default). |
|
|
140
|
+
| `brand.avatarUrl` | Branding → avatar | Custom avatar URL (must originate from the portal upload — arbitrary URLs are rejected). |
|
|
141
|
+
| `vertical` | Application creation | `real-estate`, `b2b-qualification`, … Bound to the API key. |
|
|
142
|
+
|
|
143
|
+
### 3. Client overrides (advanced — rarely needed in production)
|
|
144
|
+
|
|
145
|
+
For tooling, A/B previews or staging environments, every server-resolved field
|
|
146
|
+
above can also be passed as a `WidgetConfig` argument or `data-*` attribute. A
|
|
147
|
+
host-side value, when present, **takes precedence over the admin value**. This
|
|
148
|
+
is intentional — but in production you should configure things in the portal so
|
|
149
|
+
all your sites stay in sync.
|
|
62
150
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
72
|
-
| `email` | `string` | - | Pre-identified user email (HMAC) |
|
|
73
|
-
| `userId` | `string` | - | Host-side user identifier (HMAC) |
|
|
74
|
-
| `userHash` | `string` | - | HMAC-SHA256 of email/userId |
|
|
151
|
+
```typescript
|
|
152
|
+
// Override only for a staging preview — production should leave this out
|
|
153
|
+
createWidget({
|
|
154
|
+
apiKey: "pk_test_...",
|
|
155
|
+
theme: { primaryColor: "#3B82F6" },
|
|
156
|
+
position: "bottom-left",
|
|
157
|
+
});
|
|
158
|
+
```
|
|
75
159
|
|
|
76
|
-
### ThemeConfig
|
|
160
|
+
### `ThemeConfig` shape (admin-defined, occasionally overridden)
|
|
77
161
|
|
|
78
162
|
| Option | Type | Description |
|
|
79
163
|
| ----------------- | ----------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
@@ -88,14 +172,16 @@ widget.destroy();
|
|
|
88
172
|
|
|
89
173
|
### createWidget(config)
|
|
90
174
|
|
|
91
|
-
Creates a new widget instance.
|
|
175
|
+
Creates a new widget instance. The minimal call is `createWidget({ apiKey })` —
|
|
176
|
+
every behaviour comes from the admin portal. The example below illustrates the
|
|
177
|
+
inline mode where the integrator must additionally supply a `container`
|
|
178
|
+
(host-context only).
|
|
92
179
|
|
|
93
180
|
```typescript
|
|
94
181
|
const widget = createWidget({
|
|
95
|
-
apiKey: "
|
|
96
|
-
position: "inline"
|
|
182
|
+
apiKey: "pk_live_your_key",
|
|
183
|
+
// Required only when the admin set position: "inline" on the portal
|
|
97
184
|
container: document.getElementById("widget-container"),
|
|
98
|
-
open: true,
|
|
99
185
|
});
|
|
100
186
|
```
|
|
101
187
|
|
|
@@ -191,28 +277,27 @@ import type {
|
|
|
191
277
|
- **`B2BProfile`** — Typed B2B qualification profile (sector, companySize,
|
|
192
278
|
etc.). Kept for backward compatibility.
|
|
193
279
|
|
|
194
|
-
##
|
|
195
|
-
|
|
196
|
-
When using `vertical: "real-estate"`, the widget runs in **qualification mode**:
|
|
197
|
-
Versiq qualifies the buyer through conversation and transmits the profile — no
|
|
198
|
-
property data is needed from the integrator.
|
|
280
|
+
## Vertical Resolution
|
|
199
281
|
|
|
200
|
-
|
|
282
|
+
The vertical (`real-estate`, `b2b-qualification`, …) is **resolved server-side
|
|
283
|
+
from the API key** — you do not pass it from the integration. Each `pk_*` key is
|
|
284
|
+
bound to exactly one Application, and each Application is bound to one vertical.
|
|
285
|
+
Switching vertical = creating a new Application + new key.
|
|
201
286
|
|
|
202
|
-
|
|
203
|
-
2. Collects criteria: location, budget, property type, surface...
|
|
204
|
-
3. Emits `profile-update` at each turn with the current profile
|
|
205
|
-
4. When qualification is complete, emits `versiq:qualified` with the full
|
|
206
|
-
profile
|
|
287
|
+
### Real-Estate flow
|
|
207
288
|
|
|
208
|
-
|
|
289
|
+
When the resolved vertical is `real-estate`, the widget runs in qualification
|
|
290
|
+
mode: Versiq qualifies the buyer through conversation and emits the profile — no
|
|
291
|
+
property data is needed from the integrator (data-dependent tools like
|
|
292
|
+
`searchProperties`, `getCityStats`, `estimateProperty` are automatically
|
|
293
|
+
excluded).
|
|
209
294
|
|
|
210
295
|
```typescript
|
|
296
|
+
// Assumes the real-estate Application is configured for `inline` mode in
|
|
297
|
+
// the admin portal. Only the host-context fields are passed here.
|
|
211
298
|
const widget = createWidget({
|
|
212
|
-
apiKey: "
|
|
213
|
-
position: "inline",
|
|
299
|
+
apiKey: "pk_live_your_real_estate_key",
|
|
214
300
|
container: "#chat",
|
|
215
|
-
open: true,
|
|
216
301
|
});
|
|
217
302
|
|
|
218
303
|
// Track profile updates in real-time
|
|
@@ -229,35 +314,29 @@ widget.on("qualified", (data) => {
|
|
|
229
314
|
});
|
|
230
315
|
```
|
|
231
316
|
|
|
232
|
-
### Data Tools
|
|
233
|
-
|
|
234
|
-
In widget mode, data-dependent tools (searchProperties, getCityStats,
|
|
235
|
-
estimateProperty, etc.) are **automatically excluded**. The integrator's
|
|
236
|
-
platform provides the data; Versiq handles qualification only.
|
|
237
|
-
|
|
238
317
|
## Display Modes
|
|
239
318
|
|
|
240
|
-
|
|
319
|
+
Display mode (`bottom-right`, `bottom-left`, `inline`) is set in the admin
|
|
320
|
+
portal. From the integrator's side, the only difference is whether you also need
|
|
321
|
+
to supply a `container`.
|
|
322
|
+
|
|
323
|
+
### Floating (admin chose `bottom-right` or `bottom-left`)
|
|
241
324
|
|
|
242
|
-
Widget appears as a floating button in the corner of the page.
|
|
325
|
+
Widget appears as a floating button in the corner of the page. No `container`
|
|
326
|
+
needed.
|
|
243
327
|
|
|
244
328
|
```typescript
|
|
245
|
-
createWidget({
|
|
246
|
-
apiKey: "pk_your_key",
|
|
247
|
-
position: "bottom-right", // or "bottom-left"
|
|
248
|
-
});
|
|
329
|
+
createWidget({ apiKey: "pk_live_your_key" });
|
|
249
330
|
```
|
|
250
331
|
|
|
251
|
-
### Inline
|
|
332
|
+
### Inline (admin chose `inline`)
|
|
252
333
|
|
|
253
|
-
Widget is
|
|
334
|
+
Widget is mounted directly into a DOM container you provide.
|
|
254
335
|
|
|
255
336
|
```typescript
|
|
256
337
|
createWidget({
|
|
257
|
-
apiKey: "
|
|
258
|
-
position: "inline",
|
|
338
|
+
apiKey: "pk_live_your_key",
|
|
259
339
|
container: document.getElementById("chat-container"),
|
|
260
|
-
open: true,
|
|
261
340
|
});
|
|
262
341
|
```
|
|
263
342
|
|
|
@@ -276,12 +355,12 @@ export function ContactWidget() {
|
|
|
276
355
|
useEffect(() => {
|
|
277
356
|
if (!containerRef.current || widgetRef.current) return;
|
|
278
357
|
|
|
358
|
+
// Assumes the Application is configured for `inline` mode in the admin
|
|
359
|
+
// portal — only the host-context fields (apiKey + container) are passed
|
|
360
|
+
// here. Theme, position, language, etc. come from the server config.
|
|
279
361
|
const widget = createWidget({
|
|
280
|
-
apiKey: "
|
|
281
|
-
position: "inline",
|
|
362
|
+
apiKey: "pk_live_your_key",
|
|
282
363
|
container: containerRef.current,
|
|
283
|
-
open: true,
|
|
284
|
-
theme: { primaryColor: "#3B82F6" },
|
|
285
364
|
});
|
|
286
365
|
|
|
287
366
|
widgetRef.current = widget;
|
|
@@ -298,36 +377,123 @@ export function ContactWidget() {
|
|
|
298
377
|
|
|
299
378
|
## Data Attributes (Script Tag)
|
|
300
379
|
|
|
380
|
+
All `WidgetConfig` options can be passed as kebab-cased `data-*` attributes on
|
|
381
|
+
the `<script>` tag. The same scope split as the JavaScript API applies — in
|
|
382
|
+
practice only `data-api-key` is needed.
|
|
383
|
+
|
|
384
|
+
### Host-side (essential)
|
|
385
|
+
|
|
301
386
|
| Attribute | Maps to | Example |
|
|
302
387
|
| ---------------- | ---------- | --------------------------------------- |
|
|
303
388
|
| `data-api-key` | `apiKey` | `data-api-key="pk_live_abc123"` |
|
|
304
|
-
| `data-position` | `position` | `data-position="bottom-left"` |
|
|
305
|
-
| `data-open` | `open` | `data-open="true"` |
|
|
306
|
-
| `data-theme` | `theme` | `data-theme='{"primaryColor":"#F00"}'` |
|
|
307
389
|
| `data-base-url` | `baseUrl` | `data-base-url="https://app.versiq.io"` |
|
|
308
390
|
| `data-debug` | `debug` | `data-debug="true"` |
|
|
309
391
|
| `data-email` | `email` | `data-email="user@example.com"` |
|
|
310
392
|
| `data-user-id` | `userId` | `data-user-id="usr_123"` |
|
|
311
393
|
| `data-user-hash` | `userHash` | `data-user-hash="<hmac>"` |
|
|
312
394
|
|
|
395
|
+
### Overrides (tooling/preview only — use the admin portal in production)
|
|
396
|
+
|
|
397
|
+
| Attribute | Maps to | Example |
|
|
398
|
+
| --------------- | ---------- | -------------------------------------- |
|
|
399
|
+
| `data-position` | `position` | `data-position="bottom-left"` |
|
|
400
|
+
| `data-open` | `open` | `data-open="true"` |
|
|
401
|
+
| `data-theme` | `theme` | `data-theme='{"primaryColor":"#F00"}'` |
|
|
402
|
+
|
|
403
|
+
## Host Page Permissions Policy
|
|
404
|
+
|
|
405
|
+
Some widget features rely on browser-level permissions that **must be granted by
|
|
406
|
+
the host page** — the iframe itself can't unlock a capability the parent
|
|
407
|
+
document forbids.
|
|
408
|
+
|
|
409
|
+
| Feature | Browser permission | Required `Permissions-Policy` on the host |
|
|
410
|
+
| ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
411
|
+
| `Autour de moi` quick-reply (real-estate) — proposes nearby cities/districts based on the visitor's current coords | `geolocation` (`navigator.geolocation.getCurrentPosition`) | `geolocation=(self)` at minimum (or include the widget origin explicitly) |
|
|
412
|
+
| Copy property listing / share link buttons | `clipboard-write` | `clipboard-write=(self)` (most hosts already inherit the default) |
|
|
413
|
+
|
|
414
|
+
If your site already ships a `Permissions-Policy` header (recommended for
|
|
415
|
+
defense in depth — see
|
|
416
|
+
[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy)),
|
|
417
|
+
add `geolocation=(self)` to the list. The widget iframe is created with
|
|
418
|
+
`allow="clipboard-write; geolocation"` so the parent's permission propagates
|
|
419
|
+
automatically once it's not denied.
|
|
420
|
+
|
|
421
|
+
### Minimal example
|
|
422
|
+
|
|
423
|
+
```http
|
|
424
|
+
Permissions-Policy: camera=(), microphone=(), geolocation=(self)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### What happens if you forget
|
|
428
|
+
|
|
429
|
+
The widget keeps working — except the geolocation-backed quick-replies log
|
|
430
|
+
`Geolocation has been disabled in this document by permissions policy.` to the
|
|
431
|
+
visitor's console and the chip silently does nothing on click. The underlying
|
|
432
|
+
conversation still works; the visitor just types the city manually.
|
|
433
|
+
|
|
313
434
|
## E2E Test Selectors (data-testid)
|
|
314
435
|
|
|
315
436
|
The widget exposes a **stable contract** of `data-testid` attributes for
|
|
316
437
|
end-to-end testing (Playwright, Cypress, etc.). These selectors are guaranteed
|
|
317
438
|
not to change without a major version bump.
|
|
318
439
|
|
|
319
|
-
| Selector
|
|
320
|
-
|
|
|
321
|
-
| `widget-root`
|
|
322
|
-
| `widget-input`
|
|
323
|
-
| `widget-send`
|
|
324
|
-
| `widget-message`
|
|
325
|
-
| `widget-suggestion`
|
|
326
|
-
| `widget-cta-button`
|
|
327
|
-
| `widget-avatar`
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
440
|
+
| Selector | Element | Additional attributes |
|
|
441
|
+
| ----------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
442
|
+
| `widget-root` | Chat container (open) | — |
|
|
443
|
+
| `widget-input` | Message input field | — |
|
|
444
|
+
| `widget-send` | Send button | — |
|
|
445
|
+
| `widget-message` | Message bubble | `data-role="user" \| "assistant"` |
|
|
446
|
+
| `widget-suggestion` | Quick reply chip | `data-index="<N>"` (position) |
|
|
447
|
+
| `widget-cta-button` | Call-to-action button | `data-objective="<objectiveType>"` |
|
|
448
|
+
| `widget-avatar` | Header avatar | — |
|
|
449
|
+
| `widget-map-toggle` | Map open/reopen control (FAB mobile / button desktop) | — _(real-estate vertical, #1164)_ |
|
|
450
|
+
| `widget-map-panel` | Map container — desktop `<aside>` or mobile `<Sheet>` | — _(real-estate vertical, #1164)_ |
|
|
451
|
+
| `widget-map-close` | Map close button (desktop only) | — _(real-estate vertical, #1164)_ |
|
|
452
|
+
| `widget-areas-overview` | Areas-overview fullscreen panel root | — _(real-estate vertical, #1165)_ |
|
|
453
|
+
| `widget-areas-overview-close` | Close button (top-right) on the panel | — _(real-estate vertical, #1165)_ |
|
|
454
|
+
| `widget-areas-overview-submit` | Validate-selection button (footer) | — _(real-estate vertical, #1165)_ |
|
|
455
|
+
| `widget-areas-overview-empty` | Empty-state message when `areas.length === 0` | — _(real-estate vertical, #1165)_ |
|
|
456
|
+
| `widget-area-card-<slug>` | Single area card | `data-area="<area>"`, `data-state="kept\|rejected\|toExplore"` _(#1165)_ |
|
|
457
|
+
| `widget-area-keep-<slug>` | Favori toggle button on a card | `data-area="<area>"` _(#1165)_ |
|
|
458
|
+
| `widget-area-reject-<slug>` | Rejeter toggle button on a card | `data-area="<area>"` _(#1165)_ |
|
|
459
|
+
| `widget-exploration-mode` | Exploration overlay root (single-city deep-dive) | `data-city="<city>"` _(real-estate vertical, #1166)_ |
|
|
460
|
+
| `widget-exploration-mode-close` | Close button (×) on the overlay | — _(#1166)_ |
|
|
461
|
+
| `widget-exploration-mode-modify-criteria` | Footer button opening `EmbedCriteriaPopup` | — _(#1166)_ |
|
|
462
|
+
| `widget-exploration-mode-exit` | Footer "Terminer" button (immediate exit, sends current state) | — _(#1166)_ |
|
|
463
|
+
| `widget-exploration-mode-empty` | Empty-state when `initialProperties === []` | — _(#1166)_ |
|
|
464
|
+
| `widget-exploration-mode-empty-modify-criteria` | Modify-criteria CTA inside the empty state | — _(#1166)_ |
|
|
465
|
+
| `widget-exploration-mode-recap` | Recap screen shown once every property has been classified | — _(#1166)_ |
|
|
466
|
+
| `widget-exploration-mode-recap-finish` | Send-selection CTA on the recap screen | — _(#1166)_ |
|
|
467
|
+
| `widget-exploration-property-<id>` | Currently displayed property card | `data-property-id="<id>"`, `data-state="favorite\|rejected\|neutral"` _(#1166)_ |
|
|
468
|
+
| `widget-exploration-favorite-<id>` | Favori toggle button for the current property | `data-property-id="<id>"` _(#1166)_ |
|
|
469
|
+
| `widget-exploration-reject-<id>` | Écarter toggle button for the current property | `data-property-id="<id>"` _(#1166)_ |
|
|
470
|
+
| `widget-exploration-previous` | Navigate to the previous property | — _(#1166)_ |
|
|
471
|
+
| `widget-exploration-next` | Navigate to the next property | — _(#1166)_ |
|
|
472
|
+
| `widget-criteria-popup` | Criteria edit modal root (rendered inside ExplorationMode) | — _(#1166)_ |
|
|
473
|
+
| `widget-criteria-popup-close` | Close button (×) of the criteria modal | — _(#1166)_ |
|
|
474
|
+
| `widget-criteria-popup-cancel` | Footer "Annuler" button | — _(#1166)_ |
|
|
475
|
+
| `widget-criteria-popup-apply` | Footer "Appliquer" button | — _(#1166)_ |
|
|
476
|
+
| `widget-criteria-popup-warning` | Warning banner shown when at least one field changed | — _(#1166)_ |
|
|
477
|
+
| `widget-criteria-budget-min` | Budget min input | — _(#1166)_ |
|
|
478
|
+
| `widget-criteria-budget-max` | Budget max input | — _(#1166)_ |
|
|
479
|
+
| `widget-criteria-surface-min` | Surface min input | — _(#1166)_ |
|
|
480
|
+
| `widget-criteria-surface-max` | Surface max input | — _(#1166)_ |
|
|
481
|
+
| `widget-criteria-rooms` | Rooms min input | — _(#1166)_ |
|
|
482
|
+
| `widget-criteria-property-type-<type>` | Property-type toggle (`apartment`, `house`, `all`) | `data-selected="true\|false"` _(#1166)_ |
|
|
483
|
+
|
|
484
|
+
> Contract source lives in the Versiq backend repository
|
|
485
|
+
> ([sdolard/Toize](https://github.com/sdolard/Toize), path
|
|
486
|
+
> `apps/app/src/app/widget/embed/components/`). Property card selectors
|
|
487
|
+
> (`widget-property-card`) are not yet exposed (pending #727). Map selectors
|
|
488
|
+
> only appear when `MapContext.isVisible === true` — i.e. once the LLM has
|
|
489
|
+
> profiled a geocodable city. On B2B verticals without a map, they never render.
|
|
490
|
+
> Areas-overview selectors only appear when the LLM has streamed an
|
|
491
|
+
> `<!--AREAS_OVERVIEW:-->` marker; `<slug>` is the city name lowercased, accent-
|
|
492
|
+
> stripped (NFD), with non-alphanumeric runs collapsed to `-` (e.g. `Vénissieux`
|
|
493
|
+
> → `venissieux`). Exploration-mode and criteria-popup selectors
|
|
494
|
+
> (`widget-exploration-*` / `widget-criteria-*`) only appear after the
|
|
495
|
+
> `enterExplorationMode` tool has streamed an `<!--EXPLORATION_MODE:-->` marker
|
|
496
|
+
> (real-estate vertical, #1166).
|
|
331
497
|
|
|
332
498
|
### Example (Playwright)
|
|
333
499
|
|
|
@@ -339,34 +505,14 @@ await expect(
|
|
|
339
505
|
).toBeVisible();
|
|
340
506
|
```
|
|
341
507
|
|
|
342
|
-
## Development
|
|
343
|
-
|
|
344
|
-
```bash
|
|
345
|
-
# Install dependencies
|
|
346
|
-
pnpm install
|
|
347
|
-
|
|
348
|
-
# Development build with watch
|
|
349
|
-
pnpm dev
|
|
350
|
-
|
|
351
|
-
# Production build
|
|
352
|
-
pnpm build
|
|
353
|
-
|
|
354
|
-
# Run tests
|
|
355
|
-
pnpm test
|
|
356
|
-
|
|
357
|
-
# Local testing (serves test.html via HTTP - required for iframe CSP)
|
|
358
|
-
pnpm serve
|
|
359
|
-
# Then open http://localhost:5500/test.html
|
|
360
|
-
```
|
|
361
|
-
|
|
362
508
|
## Performance
|
|
363
509
|
|
|
364
510
|
### Bundle Size
|
|
365
511
|
|
|
366
512
|
| Format | Size (minified) | Size (gzipped) | Limit |
|
|
367
513
|
| ------ | --------------- | -------------- | ----- |
|
|
368
|
-
| UMD | ~
|
|
369
|
-
| ESM | ~
|
|
514
|
+
| UMD | ~84 KB | ~24 KB | 50 KB |
|
|
515
|
+
| ESM | ~112 KB | ~26 KB | - |
|
|
370
516
|
|
|
371
517
|
CI enforces the 50 KB gzipped limit via `size-limit`.
|
|
372
518
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dolard.eu/versiq-widget",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Versiq Widget SDK - Embed conversational qualification into your website.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/widget.umd.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"zod": "^4.2.0",
|
|
59
|
-
"@dolard.eu/versiq-core-types": "0.
|
|
59
|
+
"@dolard.eu/versiq-core-types": "0.2.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@size-limit/file": "^11.0.0",
|