@vio-live/web-sdk 0.21.0 → 0.23.1
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 +177 -0
- package/dist/chunk-45YKS5UG.js +3 -0
- package/dist/{chunk-PZUOMOLF.js.map → chunk-45YKS5UG.js.map} +1 -1
- package/dist/{chunk-WTVGVDZX.js → chunk-BRJMT7P4.js} +1053 -80
- package/dist/chunk-BRJMT7P4.js.map +1 -0
- package/dist/{chunk-MRJR6DG5.js → chunk-UVDOKFGD.js} +403 -34
- package/dist/chunk-UVDOKFGD.js.map +1 -0
- package/dist/core/index.d.ts +431 -16
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/react/index.js.map +1 -1
- package/dist/{tokens-Dkbfcd1L.d.ts → tokens-DDfZuHF6.d.ts} +108 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-MRJR6DG5.js.map +0 -1
- package/dist/chunk-PZUOMOLF.js +0 -3
- package/dist/chunk-WTVGVDZX.js.map +0 -1
package/README.md
CHANGED
|
@@ -106,6 +106,183 @@ function Shop() {
|
|
|
106
106
|
|
|
107
107
|
Wrappers: `VioProduct`, `VioProductCarousel`, `VioProductDetail`, `VioCart`, `VioCheckout`.
|
|
108
108
|
|
|
109
|
+
Hosts with their own quantity stepper: change a line with
|
|
110
|
+
`Vio.cart.adjustQuantity(lineItemId, sponsorId, ±1)`, not
|
|
111
|
+
`updateQuantity(…, item.quantity ± 1)` — the delta is applied to the manager's
|
|
112
|
+
live quantity, so quick taps before a re-render never ask for the same target
|
|
113
|
+
twice, and the backend sees one request in flight per line.
|
|
114
|
+
|
|
115
|
+
## Slots — where things happen on a page
|
|
116
|
+
|
|
117
|
+
A **slot** is a named place on a page (ADR-0027). Each page declares its slots to
|
|
118
|
+
Vio, the dashboard assigns a campaign to each one (optionally with a date window),
|
|
119
|
+
and whatever no slot names shows the surface's **default campaign** — so several
|
|
120
|
+
campaigns can run on one page at the same time. **Nobody has to name anything:
|
|
121
|
+
every Vio element registers itself.** This is the standard the web SDK, the Vev
|
|
122
|
+
blocks and the Replit site all follow.
|
|
123
|
+
|
|
124
|
+
Tell the SDK which platform the page is (it travels with every declaration and with
|
|
125
|
+
the bootstrap, so only this platform's assignments come back):
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
Vio.init({ apiKey: '…', environment: 'production', platform: 'web' }) // 'web' | 'custom-web' | 'vev' — default 'web'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Identity of an element
|
|
132
|
+
|
|
133
|
+
In order:
|
|
134
|
+
|
|
135
|
+
1. **`vio-slot`** — your own id, when you want to name the place or make several
|
|
136
|
+
elements share one slot (the cards a carousel renders already carry the
|
|
137
|
+
carousel's id). `vio-slot-name` is its optional human label for the dashboard.
|
|
138
|
+
2. Otherwise an **auto id**: `${scope}-${kind}-${ordinal + 1}` — `carousel-1`,
|
|
139
|
+
`card-3`, `detail-1`, `article-123-carousel-1` — counted among the same kind of
|
|
140
|
+
element **with the same scope**: on a fresh load elements connect in document
|
|
141
|
+
order, so the number is the document position; in a dynamic DOM a later element
|
|
142
|
+
takes the first free number and a removed element's number is reused — two live
|
|
143
|
+
elements never share an id. Its label is `Carousel 1` (` · Title` when the
|
|
144
|
+
scope has a label). Every id is capped at 100 characters (a long scope is cut,
|
|
145
|
+
the kind and number are kept), and the capped id is what gets declared,
|
|
146
|
+
exposed and sent.
|
|
147
|
+
|
|
148
|
+
The attribute is **`vio-slot`** (property `vioSlot`, React prop `vioSlot`) — **not
|
|
149
|
+
`slot`**, which is HTML's own attribute for shadow DOM slotting and stays untouched.
|
|
150
|
+
The id in effect is readable as `element.effectiveSlot`.
|
|
151
|
+
|
|
152
|
+
```html
|
|
153
|
+
<vio-product-carousel product-ids="408909,408910"></vio-product-carousel> <!-- carousel-1 -->
|
|
154
|
+
<vio-product-carousel vio-slot="hero" vio-slot-name="Hero banner" product-ids="…"></vio-product-carousel>
|
|
155
|
+
<vio-product vio-slot="hero" product-id="408911" sponsor-id="1"></vio-product> <!-- shares `hero` -->
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<VioProductCarousel vioSlot="hero" vioSlotName="Hero banner" productIds="408909,408910" />
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Scope — per-page slots with one line
|
|
163
|
+
|
|
164
|
+
There is **no page URL in an auto id** on purpose: one Vev design or one article
|
|
165
|
+
template serves thousands of articles and they are all the same *place*. When a
|
|
166
|
+
page wants slots of its own, it sets a **scope** once and every element inherits
|
|
167
|
+
it:
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<body data-vio-scope="article-123" data-vio-scope-name="Why the lamp matters">
|
|
171
|
+
<vio-product-carousel product-ids="…"></vio-product-carousel> <!-- article-123-carousel-1 · "Carousel 1 · Why the lamp matters" -->
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
An element's scope is its own `vio-scope` attribute, else the nearest ancestor's
|
|
175
|
+
`data-vio-scope` (through shadow roots), else none; the label the same way with
|
|
176
|
+
`vio-scope-name` / `data-vio-scope-name`. Ordinals restart per scope. A Vev page
|
|
177
|
+
sets the page as scope; a Replit article page sets `data-vio-scope` on `<body>`; a
|
|
178
|
+
template that wants slots shared across articles simply sets no scope.
|
|
179
|
+
|
|
180
|
+
### Declaration
|
|
181
|
+
|
|
182
|
+
Every mounted element registers its id (and label); after the first successful
|
|
183
|
+
`Vio.bootstrap()` the SDK sends **one** manifest for the whole page
|
|
184
|
+
(`POST /v2/mobile/components/manifest`, `mode: 'merge'`, so one page never
|
|
185
|
+
deprecates another page's slots) and again whenever a new id or label appears.
|
|
186
|
+
The browser remembers **each id (under its label) for 24 h** in `localStorage`
|
|
187
|
+
(under the platform and a hash of the key — never the key itself), so a
|
|
188
|
+
multi-page site posts only the ids a page adds, a revisit posts nothing, a
|
|
189
|
+
renamed block posts again; without storage it simply posts each load. A failed
|
|
190
|
+
attempt (or a server that ignored `merge`) is not retried for 1 h; a declaration
|
|
191
|
+
sends at most 100 locations and a page load at most 3 manifests (each cap warns
|
|
192
|
+
once). Best-effort, never throws. By hand, e.g. from an editor (`force` re-sends
|
|
193
|
+
everything and ignores the per-load cap):
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar' }], { force: true })
|
|
197
|
+
// → ManifestResult | null (null = skipped as already declared, or failed — see the console)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Which campaign a slot shows
|
|
201
|
+
|
|
202
|
+
Among the active campaigns of the bootstrap, the one whose assignment names the
|
|
203
|
+
slot (for this platform, or for all) and whose window is open right now; if
|
|
204
|
+
several, the default campaign, else the first listed. No assignment → the default
|
|
205
|
+
campaign. `Vio.campaignForSlot('hero')` answers from the last bootstrap (`null`
|
|
206
|
+
before it); the pure `resolveSlotCampaign(boot, slot, now?, platform?)` is exported
|
|
207
|
+
from `/core`. A carousel or card **without a `sponsor-id`** uses the primary sponsor
|
|
208
|
+
of its slot's campaign (the default campaign's when the slot has none), and the
|
|
209
|
+
detail opened by such a card does the same — before 0.23 a sponsorless card did
|
|
210
|
+
nothing. An explicit `sponsor-id` still wins.
|
|
211
|
+
|
|
212
|
+
## Attribution — the slot, the campaign, the article
|
|
213
|
+
|
|
214
|
+
`Vio.attribution(slot?)` is **the** attribution helper — the SDK elements use it,
|
|
215
|
+
and so do the Vev blocks and the Replit site:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
Vio.attribution('hero')
|
|
219
|
+
// → { slot: 'hero', campaignId: 12, sponsorId: 7, contentId: 'article-42', contentTitle: 'Why the lamp matters' }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`campaignId`/`sponsorId` are the slot's campaign and its primary sponsor (`{}` before
|
|
223
|
+
the bootstrap); `contentId`/`contentTitle` are the **content** (the article) the page
|
|
224
|
+
is. The place is the slot, the article is the content — both automatic.
|
|
225
|
+
|
|
226
|
+
**Article (content) attribution.** `Vio.content()` resolves it **live** (a few
|
|
227
|
+
`querySelector`s, so an SPA route change or a new title is seen on the next event)
|
|
228
|
+
and every analytics event carries it as `content_id` / `content_title`: id from
|
|
229
|
+
`Vio.init({ contentId })` → `?vio_content=` (honored by design — a page may be
|
|
230
|
+
told which article it is) → `<meta name="vio:content-id">` →
|
|
231
|
+
`<link rel="canonical">` / `og:url` (without query and hash) → `location.pathname`;
|
|
232
|
+
title from `Vio.init({ contentTitle })` → `?vio_content_title=` →
|
|
233
|
+
`<meta name="vio:content-title">` → `og:title` → `document.title`. The collector
|
|
234
|
+
rejects a whole event over an id of 128 chars or a title of 255, so a candidate id
|
|
235
|
+
that does not fit is skipped for the next one (and omitted when none fits) and
|
|
236
|
+
titles are cut — in one place, before anything is sent. Per element,
|
|
237
|
+
`vio-content-id` / `vio-content-title` override it (a teaser for another article).
|
|
238
|
+
|
|
239
|
+
**What analytics gets.** Every event an element emits (`vio:product-click`,
|
|
240
|
+
`vio:product-add`, `vio:added-to-cart`, `vio:checkout-open`, …) and every event it
|
|
241
|
+
tracks carries `slot` (the effective id), `location_id` (= the slot), `content_id`
|
|
242
|
+
and `content_title`. `campaign_id` resolves as explicit `campaignId` (the element's
|
|
243
|
+
own attribution travels in its event detail and is honored as such) → the slot's
|
|
244
|
+
assigned campaign → the sponsor's campaign → the default. Two things changed from
|
|
245
|
+
0.22 for carousels: a carousel's impressions and `view_item_list` report
|
|
246
|
+
`location_id` as its slot id (`carousel-1`, or the `vio-slot`) instead of its
|
|
247
|
+
`label`, and every event now carries `content_id` / `content_title` — including
|
|
248
|
+
the single-campaign `begin_checkout` / `purchase`.
|
|
249
|
+
|
|
250
|
+
### Cart origins and the purchase per campaign
|
|
251
|
+
|
|
252
|
+
The cart remembers **where every unit came from**. The add paths take an optional
|
|
253
|
+
`origin` (`{ campaignId?, slot?, contentId? }` — what `Vio.attribution(slot)`
|
|
254
|
+
answers), and so does `adjustQuantity(lineItemId, sponsorId, delta, { origin })`:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
Vio.cart.addProduct({ product, sponsorId: 1, origin: Vio.attribution('hero') })
|
|
258
|
+
// a host adding from the card's event:
|
|
259
|
+
onProductAdd={(e) => Vio.cart.addProduct({ product, sponsorId, origin: e.detail.origin })}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Each line keeps `origins: Array<{ campaignId?, slot?, contentId?, quantity }>`: an
|
|
263
|
+
add with an origin increments that origin, without one the most recent (the
|
|
264
|
+
`<vio-cart>` +/− buttons pass none), and −1/remove comes off the most recent first.
|
|
265
|
+
They persist with the cart; carts from before origins read as one empty origin.
|
|
266
|
+
|
|
267
|
+
At checkout and purchase the cart is **split per campaign**: `begin_checkout` and
|
|
268
|
+
`purchase` go out **once per campaign in the cart**, each with that campaign's
|
|
269
|
+
items (quantities from the origins; units without one fall into the campaign the
|
|
270
|
+
current rule resolves), its own `value` (two decimals), the same `order_id`, the
|
|
271
|
+
slice's `slot` / `content_id` only when they are unique in it, and
|
|
272
|
+
`props: { slice, slices }` so the collector can count the order once. A cart with a
|
|
273
|
+
single campaign is one event, as before; when its origins name the campaign, the
|
|
274
|
+
event names it too (plus the unique slot / content). A cart without origins
|
|
275
|
+
(0.22) reports exactly as it did.
|
|
276
|
+
|
|
277
|
+
The split is public, so a host with its own analytics sink (Vev) applies the same
|
|
278
|
+
rule instead of its own:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
Vio.cartSlices() // over the live cart; Vio.cartSlices(sponsorId) for one sponsor
|
|
282
|
+
Vio.cartSlices(undefined, { items }) // pure, over a snapshot of lines
|
|
283
|
+
// → CartSlice[]: { campaignId?, sponsorId?, slot?, contentId?, items: VioItem[], value, currency?, slice, slices }
|
|
284
|
+
```
|
|
285
|
+
|
|
109
286
|
## Theming
|
|
110
287
|
|
|
111
288
|
Every component reads its colors and fonts from `--vio-*` CSS custom properties
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-45YKS5UG.js"}
|