Skip to content

Svelte Integration: Complete Example

A Svelte component for the Wedding Band Builder.

Back to Script Tag guide

Component

svelte
<!-- WeddingBandBuilder.svelte -->
<script>
  import { onMount, onDestroy, createEventDispatcher } from 'svelte';

  export let basePath = 'https://your-cdn.com/wbb-assets/';
  export let manifestUrl = 'wedding-band-project.json';
  export let showUI = true;
  export let theme = null;

  const dispatch = createEventDispatcher();

  let container;
  let api = null;
  let price = null;

  const SCRIPT_URL = 'https://releases.ijewel3d.com/libs/mini-viewer/latest/bundle.iife.js';

  function loadScript() {
    if (window.ijewelViewer) return Promise.resolve();
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = SCRIPT_URL;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  function handleReady(e) {
    const viewer = e.detail.viewer;
    api = viewer.getPluginByType('WeddingBandBuilder')?.controller;
    if (!api) return;

    if (theme) api.setTheme(theme);

    api.events.on('price:updated', (data) => {
      if (data.pricing) {
        price = data.pricing;
        dispatch('pricechange', data.pricing);
      }
    });

    dispatch('ready', api);
  }

  onMount(async () => {
    await loadScript();
    window.addEventListener('ijewel-viewer-ready', handleReady);

    new window.ijewelViewer.Viewer(container, {
      name: 'Wedding Band Builder',
      version: 'v5',
      basePath,
      plugins: {
        WeddingBandBuilder: { manifestUrl, showUI },
      },
    }, {
      showCard: false,
      showSwitchNode: false,
      showUiButtons: showUI,
      showConfigurator: false,
      showZoomButtons: showUI,
      enableZoom: true,
    });
  });

  onDestroy(() => {
    window.removeEventListener('ijewel-viewer-ready', handleReady);
  });

  export function getApi() { return api; }
</script>

<div class="wbb-container">
  <div bind:this={container} class="wbb-viewer" />
  {#if price}
    <slot name="price" {price}>
      <div class="wbb-price">${price.totalUsd.toFixed(2)}</div>
    </slot>
  {/if}
</div>

<style>
  .wbb-container { width: 100%; height: 100%; position: relative; }
  .wbb-viewer { width: 100%; height: 100%; }
  .wbb-price {
    position: absolute;
    bottom: 16px;
    right: 16px;
    font-size: 24px;
    font-weight: 600;
    background: rgba(0,0,0,0.7);
    color: #fff;
    padding: 8px 16px;
    border-radius: 8px;
  }
</style>

Usage

svelte
<!-- App.svelte -->
<script>
  import WeddingBandBuilder from './WeddingBandBuilder.svelte';

  let api;

  function onReady(e) {
    api = e.detail;
    console.log('Profiles:', api.getAvailableProfiles());
  }

  function onPrice(e) {
    console.log(`Total: $${e.detail.totalUsd.toFixed(2)}`);
  }
</script>

<div style="width: 100vw; height: 100vh;">
  <WeddingBandBuilder
    theme="luxury-gold"
    on:ready={onReady}
    on:pricechange={onPrice}
  />
</div>

Headless with Custom Controls

svelte
<script>
  import WeddingBandBuilder from './WeddingBandBuilder.svelte';

  let api;
  let price = null;
  let width = 4.0;
  let activeProfile = 0;

  $: profiles = api?.getAvailableProfiles() || [];
  $: metals = api?.getAvailableMetals() || [];

  async function selectProfile(index) {
    await api.setProfile(index);
    activeProfile = index;
  }

  function onWidthChange() {
    api?.setWidth(width);
  }
</script>

<div class="app">
  <div class="viewer">
    <WeddingBandBuilder
      showUI={false}
      on:ready={(e) => { api = e.detail; }}
      on:pricechange={(e) => { price = e.detail; }}
    />
  </div>

  {#if api}
    <div class="controls">
      <div class="row">
        {#each profiles as p}
          <button
            class:active={activeProfile === p.index}
            on:click={() => selectProfile(p.index)}
          >
            {p.name}
          </button>
        {/each}
      </div>

      <div class="row">
        {#each metals as m}
          <button on:click={() => api.setMaterial(1, m.id, 'Polished')}>
            {m.name}
          </button>
        {/each}
      </div>

      <div class="row">
        <input type="range" min="2" max="10" step="0.1" bind:value={width} on:input={onWidthChange} />
        <span>{width.toFixed(1)} mm</span>
      </div>

      {#if price}
        <div class="price">${price.totalUsd.toFixed(2)}</div>
      {/if}
    </div>
  {/if}
</div>

<style>
  .app { display: flex; flex-direction: column; height: 100vh; }
  .viewer { flex: 1; }
  .controls { padding: 16px; background: #fff; border-top: 1px solid #eee; }
  .row { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
  .price { font-size: 24px; font-weight: 600; margin-top: 8px; }
  button { padding: 8px 16px; border: 1px solid #ddd; border-radius: 6px; background: #fff; cursor: pointer; }
  button.active { background: #333; color: #fff; border-color: #333; }
</style>