viewa/docs
Widget

JS API

Drive the widget from your product page, and read back what the customer did.

Your product page already knows what the customer has selected — the JS API is how you tell the widget. Everything else is optional: a page that only ever sets one product can do it in markup and never touch JavaScript at all.

Reaching the element

With async on the script tag the element upgrades whenever the script lands. Attributes are safe to set before that; methods are not, so wait for the definition:

await customElements.whenDefined('viewa-visualizer')
const widget = document.querySelector('viewa-visualizer')

Pushing a selection

When the customer changes product or material in your customizer, push it in:

widget.setSelection({ product: 'YOUR-SKU-123' })

product takes either a minted prod_… id or one of your registered SKU aliases. The API resolves both through this one field, so there is no separate sku to set.

Materials are per slot, keyed by slot token:

widget.setSelection({
  product: 'YOUR-SKU-123',
  materials: [{ optionId: 'slot_fabric', material: 'CHARCOAL-LINEN' }],
})

setSelection merges. Only the keys you pass are written, so a finish picker can send materials alone without knowing the product ref. Passing a key explicitly as undefined clears it.

A selection triggers a render straight away. Pass { render: false } to batch several coupled changes, or set auto-render="off" to own the trigger entirely and call visualize() yourself.

Reading state

getStatus() returns everything the widget knows as one snapshot. Reach for it first — the events carry transitions, and a listener attached late has missed them.

{
  phase: 'done',                    // idle | uploading | preparing | editing |
                                    // measuring | ready | rendering | done | error
  step: 'render',                   // upload | areas | render — the coarse step
  busy: false,                      // waiting on the API — for your spinner
  capabilities: ['upload', 'areas', 'render'],
  ui: ['chrome', 'controls', 'zoom', 'measurements', 'compare', 'download', 'change-photo'],
  productName: 'Vibe Roller Blind',
  autoRender: true,
  error: null,                      // { message, code } — customer-safe copy only
  image:   { id: 'img_…',  status: 'ready',     url: '…', width: 1024, height: 768 },
  areaSet: { id: 'aset_…', status: 'ready' },
  areas:   [{ id: 'area_…', name: null, measurements: { width, height, unit } }],
  activeArea: null,                 // which area is being edited, in the editor only
  measurements: true,               // whether the sizes are drawn on the photo
  render:  { id: 'ren_…',  status: 'succeeded', url: '…' },
  selection: { product: 'prod_…', materials: [...], animation: [1] },
  can: { upload, addArea, removeArea, saveAreas, editAreas, visualize },
}

The ids are the part worth keeping. Persist image and areaSet against a cart line and a later page can pick the customer up where they left off — see Embedding.

can is the widget's own answer to "would this do anything right now?". Sync your chrome off it rather than re-deriving the rule from the phase and getting an edge wrong.

Everything it hands back is a copy, all the way down. Mutating the object you get from getStatus() or getSelection() changes nothing inside the widget.

Methods

MethodWhat it does
getStatus()Everything the widget knows, as one snapshot
setSelection(selection, { render })Push the product and material choice. Merges
getSelection()What it currently holds
setAnimation(values)The open/closed position, 0..1 per animation
visualize()Render now. What auto-render="off" hands the trigger to
canVisualizeWhether there is enough to render — areas, product, capability
getAreas()The measured areas, once they are ready
chooseFile()Open the file picker from your own button
addArea() / removeArea()Drive the corner editor from your own chrome
canAddArea / canRemoveAreaWhether those two will do anything
selectArea(index)Make an area the one being edited
renameArea(index, name)Name an area. Empty clears it. Stored on the area set
saveAreas() / editAreas()Commit and measure; reopen the editor
showMeasurements(on)Draw the sizes on the photo, or not
getPhase()Where the customer is, paired with the phase event
getCapabilities()What this instance is allowed to do
reset()Back to the upload panel, abandoning anything in flight

Methods that could be a no-op — chooseFile, addArea, removeArea, selectArea, renameArea, saveAreas, editAreas, visualize, showMeasurements — return a boolean saying whether they did anything. The setters and reset() return nothing.

selectArea matters more than it looks. Only the active area's handles can be grabbed, so with two overlapping outlines on one photo, picking by index is the only reliable way to say which one you mean.

With ui="none" you own every control, including the one that commits the areas. Nothing calls saveAreas() for you, so without it the customer cannot finish.

Events

CustomEvents on the element, prefixed viewa:, bubbling and composed.

Eventdetail
ready{} — the element is upgraded and listening
phase{ phase, step, capabilities }
image-uploaded{ id } — accepted, not yet processed
image-ready{ id } — processed and safe to render against
areas-changed{ count, active, canAdd, canRemove } — fires on every drag
areas-ready{ id, areas } — the API's measurement of them
selection-changed{ selection }
render-started{ areaSet } — the render's own id arrives with render-ready
render-ready{ id, url }
error{ message, code }
widget.addEventListener('viewa:render-ready', (event) => {
  cartLine.renderId = event.detail.id
})

areas-changed and areas-ready are not the same thing. The first fires while the customer is still dragging, before anything is sent; the second is the measurement that came back. Bind enabled states to phase, which is emitted after the widget has redrawn, so a listener reading can sees the new state rather than the old one.

error is the single failure channel. Job failures — image preprocessing, area occlusion, render capture — arrive from the API on a 200 with an error.code, and go out through this same event rather than a parallel set of *-failed ones. message is always customer-safe copy; code is the stable thing to branch on. See Errors.

Next

  • Embedding — the attributes, and splitting the journey across pages
  • Theming — the custom properties, and choosing which controls the widget draws

On this page