@vio-live/web-sdk 0.19.0 → 0.23.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 CHANGED
@@ -106,6 +106,150 @@ 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**, in document order, when the element connects.
141
+ Its label is `Carousel 1` (` · Title` when the scope has a label).
142
+
143
+ The attribute is **`vio-slot`** (property `vioSlot`, React prop `vioSlot`) — **not
144
+ `slot`**, which is HTML's own attribute for shadow DOM slotting and stays untouched.
145
+ The id in effect is readable as `element.effectiveSlot`.
146
+
147
+ ```html
148
+ <vio-product-carousel product-ids="408909,408910"></vio-product-carousel> <!-- carousel-1 -->
149
+ <vio-product-carousel vio-slot="hero" vio-slot-name="Hero banner" product-ids="…"></vio-product-carousel>
150
+ <vio-product vio-slot="hero" product-id="408911" sponsor-id="1"></vio-product> <!-- shares `hero` -->
151
+ ```
152
+
153
+ ```tsx
154
+ <VioProductCarousel vioSlot="hero" vioSlotName="Hero banner" productIds="408909,408910" />
155
+ ```
156
+
157
+ ### Scope — per-page slots with one line
158
+
159
+ There is **no page URL in an auto id** on purpose: one Vev design or one article
160
+ template serves thousands of articles and they are all the same *place*. When a
161
+ page wants slots of its own, it sets a **scope** once and every element inherits
162
+ it:
163
+
164
+ ```html
165
+ <body data-vio-scope="article-123" data-vio-scope-name="Why the lamp matters">
166
+ <vio-product-carousel product-ids="…"></vio-product-carousel> <!-- article-123-carousel-1 · "Carousel 1 · Why the lamp matters" -->
167
+ ```
168
+
169
+ An element's scope is its own `vio-scope` attribute, else the nearest ancestor's
170
+ `data-vio-scope` (through shadow roots), else none; the label the same way with
171
+ `vio-scope-name` / `data-vio-scope-name`. Ordinals restart per scope. A Vev page
172
+ sets the page as scope; a Replit article page sets `data-vio-scope` on `<body>`; a
173
+ template that wants slots shared across articles simply sets no scope.
174
+
175
+ ### Declaration
176
+
177
+ Every mounted element registers its id (and label); after the first successful
178
+ `Vio.bootstrap()` the SDK sends **one** manifest for the whole page
179
+ (`POST /v2/mobile/components/manifest`, `mode: 'merge'`, so one page never
180
+ deprecates another page's slots) and again whenever a new id or label appears. The
181
+ same set is not re-sent from the same browser for **24 h** (remembered in
182
+ `localStorage` under the platform and a hash of the key — never the key itself;
183
+ without storage it simply posts each load). Best-effort, never throws. By hand,
184
+ e.g. from an editor:
185
+
186
+ ```ts
187
+ await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar' }], { force: true })
188
+ // → ManifestResult | null (null = skipped as already declared, or failed — see the console)
189
+ ```
190
+
191
+ ### Which campaign a slot shows
192
+
193
+ Among the active campaigns of the bootstrap, the one whose assignment names the
194
+ slot (for this platform, or for all) and whose window is open right now; if
195
+ several, the default campaign, else the first listed. No assignment → the default
196
+ campaign. `Vio.campaignForSlot('hero')` answers from the last bootstrap (`null`
197
+ before it); the pure `resolveSlotCampaign(boot, slot, now?, platform?)` is exported
198
+ from `/core`. A carousel or card **without a `sponsor-id`** uses the primary sponsor
199
+ of its slot's campaign; an explicit `sponsor-id` still wins.
200
+
201
+ ## Attribution — the slot, the campaign, the article
202
+
203
+ `Vio.attribution(slot?)` is **the** attribution helper — the SDK elements use it,
204
+ and so do the Vev blocks and the Replit site:
205
+
206
+ ```ts
207
+ Vio.attribution('hero')
208
+ // → { slot: 'hero', campaignId: 12, sponsorId: 7, contentId: 'https://host/sport/42', contentTitle: 'Why the lamp matters' }
209
+ ```
210
+
211
+ `campaignId`/`sponsorId` are the slot's campaign and its primary sponsor (`{}` before
212
+ the bootstrap); `contentId`/`contentTitle` are the **content** (the article) the page
213
+ is. The place is the slot, the article is the content — both automatic.
214
+
215
+ **Article (content) attribution.** `Vio.content()` resolves it once per page and
216
+ every analytics event carries it as `content_id` / `content_title`: id from
217
+ `Vio.init({ contentId })` → `?vio_content=` → `<meta name="vio:content-id">` →
218
+ `<link rel="canonical">` / `og:url` (without query and hash) → `location.pathname`;
219
+ title from `Vio.init({ contentTitle })` → `?vio_content_title=` →
220
+ `<meta name="vio:content-title">` → `og:title` → `document.title`. Per element,
221
+ `vio-content-id` / `vio-content-title` override it (a teaser for another article).
222
+
223
+ **What analytics gets.** Every event an element emits (`vio:product-click`,
224
+ `vio:product-add`, `vio:added-to-cart`, `vio:checkout-open`, …) and every event it
225
+ tracks carries `slot` (the effective id), `location_id` (= the slot), `content_id`
226
+ and `content_title`. `campaign_id` resolves as explicit `campaignId` → the slot's
227
+ assigned campaign → the sponsor's campaign → the default.
228
+
229
+ ### Cart origins and the purchase per campaign
230
+
231
+ The cart remembers **where every unit came from**. The add paths take an optional
232
+ `origin` (`{ campaignId?, slot?, contentId? }` — what `Vio.attribution(slot)`
233
+ answers), and so does `adjustQuantity(lineItemId, sponsorId, delta, { origin })`:
234
+
235
+ ```ts
236
+ Vio.cart.addProduct({ product, sponsorId: 1, origin: Vio.attribution('hero') })
237
+ // a host adding from the card's event:
238
+ onProductAdd={(e) => Vio.cart.addProduct({ product, sponsorId, origin: e.detail.origin })}
239
+ ```
240
+
241
+ Each line keeps `origins: Array<{ campaignId?, slot?, contentId?, quantity }>`: an
242
+ add with an origin increments that origin, without one the most recent (the
243
+ `<vio-cart>` +/− buttons pass none), and −1/remove comes off the most recent first.
244
+ They persist with the cart; carts from before origins read as one empty origin.
245
+
246
+ At checkout and purchase the cart is **split per campaign**: `begin_checkout` and
247
+ `purchase` go out **once per campaign in the cart**, each with that campaign's
248
+ items (quantities from the origins; units without one fall into the campaign the
249
+ current rule resolves), its own `value`, the same `order_id`, and the slice's
250
+ `slot` / `content_id` when they are unique in it. A cart with a single campaign
251
+ behaves exactly as before: one event.
252
+
109
253
  ## Theming
110
254
 
111
255
  Every component reads its colors and fonts from `--vio-*` CSS custom properties
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-25QECQDS.js.map
3
+ //# sourceMappingURL=chunk-25QECQDS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-PZUOMOLF.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-25QECQDS.js"}