Aller au contenu

Svelte Adapter

The @presage-kit/svelte package provides Svelte stores and context helpers for building adaptive interfaces.

Fenêtre de terminal
pnpm add @presage-kit/core @presage-kit/svelte

Use setAdaptiveClient() in your root layout to make the client available to all child components via Svelte’s context API.

src/routes/+layout.svelte
<script>
import { setAdaptiveClient } from '@presage-kit/svelte'
import { client } from '$lib/adaptive-client'
setAdaptiveClient(client)
</script>
<slot />

The primary function for resolving adaptation points. Returns Svelte readable stores that update reactively when user context changes.

<script lang="ts">
import { useAdaptive } from '@presage-kit/svelte'
const { selectedVariant, resolution, context, track } = useAdaptive('onboarding', {
variants: ['guided-tour', 'standard', 'minimal'] as const,
defaultVariant: 'standard',
})
function handleComplete() {
track('onboarding_completed')
}
</script>
{#if $selectedVariant === 'guided-tour'}
<h2>Welcome! Let us show you around.</h2>
<button on:click={handleComplete}>Complete tour</button>
{:else if $selectedVariant === 'standard'}
<h2>Welcome</h2>
{:else}
<p>Good to see you again.</p>
{/if}

Parameters:

ParamTypeDescription
adaptationIdstringUnique identifier for the adaptation point
config.variantsreadonly string[]Available variant IDs
config.defaultVariantstringFallback variant
config.strategyStrategyOptional. Defaults to { type: 'rules' }

Return type:

All reactive values are Svelte Readable stores — use the $ prefix to subscribe.

FieldTypeDescription
selectedVariantReadable<V>The selected variant string
resolutionReadable<ResolvedAdaptation>Full resolution details
contextReadable<UserContext>Current user context
track(event: string, properties?) => voidScoped track function

Automatic impression tracking: useAdaptive() watches for variant changes and automatically tracks presage:impression events.

A lighter function that returns only the selected variant as a readable store.

<script lang="ts">
import { useVariant } from '@presage-kit/svelte'
const variant = useVariant('cta-style', {
variants: ['primary', 'secondary', 'subtle'] as const,
defaultVariant: 'primary',
})
</script>
<button class="cta cta--{$variant}">Get Started</button>

Parameters: Same as useAdaptive().

Return type: Readable<V> — A store containing the selected variant string.

Returns the AdaptiveClient instance from context. Use for imperative operations.

<script lang="ts">
import { getAdaptiveClient } from '@presage-kit/svelte'
const client = getAdaptiveClient()
function handleExport() {
client.track('feature_used', { featureId: 'export' })
}
</script>
<button on:click={handleExport}>Export Data</button>

Throws an error if setAdaptiveClient() has not been called in a parent component.

A generic ordering function for any list of items. Returns a Readable store of items that are automatically reordered, hidden, or preserved based on adaptation rules. Use this when you need adaptive ordering for non-navigation content such as feature cards, pricing tiers, or tab panels.

<script lang="ts">
import { useAdaptiveOrder } from '@presage-kit/svelte'
const features = [
{ id: 'analytics', title: 'Analytics', description: 'Track user behavior' },
{ id: 'automation', title: 'Automation', description: 'Automate repetitive tasks' },
{ id: 'integrations', title: 'Integrations', description: 'Connect your tools' },
]
const orderedFeatures = useAdaptiveOrder('feature-highlights', features)
</script>
<div class="feature-grid">
{#each $orderedFeatures as feature (feature.id)}
<div class="feature-card">
<h3>{feature.title}</h3>
<p>{feature.description}</p>
</div>
{/each}
</div>

With a reorder rule, items are rearranged based on user context:

{
id: 'power-user-features',
adaptationId: 'feature-highlights',
priority: 10,
conditions: {
all: [{ field: 'maturity', operator: 'eq', value: 'power' }],
},
action: { type: 'reorder', order: ['automation', 'integrations', 'analytics'] },
}

Parameters:

ParamTypeDescription
idstringAdaptation point identifier
itemsT[] (extends Orderable)Array of items, each with at least an id field

Return type: Readable<T[]> — A store containing the ordered items. Returns an empty array when a hide action matches.

A thin wrapper around useAdaptiveOrder(), specialized for navigation lists. Internally delegates all logic to useAdaptiveOrder().

Returns a Readable store of navigation items that are automatically reordered or hidden by adaptation rules.

<script lang="ts">
import { useAdaptiveNav } from '@presage-kit/svelte'
const items = [
{ id: 'home', label: 'Home', href: '/' },
{ id: 'analytics', label: 'Analytics', href: '/analytics' },
{ id: 'settings', label: 'Settings', href: '/settings' },
]
const navItems = useAdaptiveNav('sidebar-nav', items)
</script>
<nav>
{#each $navItems as item (item.id)}
<a href={item.href}>{item.label}</a>
{/each}
</nav>

With a reorder rule, the navigation order changes based on user context:

{
id: 'power-user-nav-order',
adaptationId: 'sidebar-nav',
priority: 10,
conditions: {
all: [{ field: 'maturity', operator: 'eq', value: 'power' }],
},
action: { type: 'reorder', order: ['analytics', 'home', 'settings'] },
}

Parameters:

ParamTypeDescription
idstringAdaptation point identifier
itemsT[] (extends NavItem)Array of items, each with at least an id field

Return type: Readable<T[]> — A store containing the ordered items. Returns an empty array when a hide action matches.

Alternative: Manual navigation with evaluateAction()

Section intitulée « Alternative: Manual navigation with evaluateAction() »

You can also use evaluateAction() with a derived() store for full control over reorder and hide logic.

<script lang="ts">
import { getAdaptiveClient } from '@presage-kit/svelte'
import { useAtom } from '@presage-kit/svelte'
import { derived } from 'svelte/store'
const client = getAdaptiveClient()
const baseItems = [
{ id: 'home', label: 'Home', href: '/' },
{ id: 'analytics', label: 'Analytics', href: '/analytics' },
{ id: 'settings', label: 'Settings', href: '/settings' },
]
const navItems = derived(useAtom(client.state), () => {
const action = client.evaluateAction('sidebar-nav')
if (action?.type === 'hide') return []
if (action?.type === 'reorder') {
const map = new Map(baseItems.map((item) => [item.id, item]))
const ordered = action.order.filter((id) => map.has(id)).map((id) => map.get(id)!)
const rest = baseItems.filter((i) => !action.order.includes(i.id))
return [...ordered, ...rest]
}
return baseItems
})
</script>
<nav>
{#each $navItems as item (item.id)}
<a href={item.href}>{item.label}</a>
{/each}
</nav>
src/lib/adaptive-client.ts
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
export const client = createAdaptiveClient({
rules: [
{
id: 'power-user-sidebar',
adaptationId: 'sidebar-nav',
priority: 10,
conditions: {
all: [{ field: 'maturity', operator: 'eq', value: 'power' }],
},
action: { type: 'reorder', order: ['analytics', 'dashboards', 'settings', 'home'] },
},
{
id: 'new-user-tour',
adaptationId: 'onboarding',
priority: 10,
conditions: {
all: [{ field: 'maturity', operator: 'eq', value: 'new' }],
},
action: { type: 'show', variantId: 'guided-tour' },
},
{
id: 'experienced-skip',
adaptationId: 'onboarding',
priority: 5,
conditions: {
any: [
{ field: 'maturity', operator: 'eq', value: 'active' },
{ field: 'maturity', operator: 'eq', value: 'power' },
],
},
action: { type: 'show', variantId: 'minimal' },
},
],
persistence: {
driver: createLocalStorageDriver('svelte-demo'),
},
})
src/routes/+layout.svelte
<script>
import { setAdaptiveClient } from '@presage-kit/svelte'
import { client } from '$lib/adaptive-client'
setAdaptiveClient(client)
</script>
<div class="layout">
<SidebarNav />
<main>
<slot />
</main>
</div>
src/lib/components/Onboarding.svelte
<script lang="ts">
import { useAdaptive } from '@presage-kit/svelte'
const { selectedVariant, track } = useAdaptive('onboarding', {
variants: ['guided-tour', 'standard', 'minimal'] as const,
defaultVariant: 'standard',
})
function handleComplete() {
track('onboarding_completed')
}
</script>
{#if $selectedVariant === 'guided-tour'}
<div class="onboarding">
<h2>Welcome! Let us show you around.</h2>
<button on:click={handleComplete}>Complete Tour</button>
</div>
{:else if $selectedVariant === 'standard'}
<div class="onboarding">
<h2>What is new</h2>
<p>Check out the latest features.</p>
</div>
{:else}
<div class="onboarding">
<p>Welcome back!</p>
</div>
{/if}