Skip to main content

vue-widget Example

examples/vue-widget is the official example for the vue-ts template: Vue 3 SFC + @vitejs/plugin-vue. Its structure is the same as the vanilla example, with only an added vite.config.ts and one .vue file per surface.

Differences from the vanilla Example

Itemvanilla-widgetvue-widget
FrameworkNone (plain TS manipulating the DOM)Vue 3.5 SFC
Vite configNone (zero-config)vite.config.ts registering the Vue plugin
Surface entrywidget.ts renders directlywidget.ts mounts + widget.vue component
Theme responseSubscribes to the themeChanged eventRelies on host token injection (CSS follows automatically)
Dependencies@patab/widget-sdkAdds vue, @vitejs/plugin-vue

The Manifest, tsconfig, and test files are character-for-character identical to vanilla (only the widget ID/name differ).

vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({ plugins: [vue()] })

The CLI only validates the final self-contained HTML and does not restrict Vite plugins — frameworks like React/Svelte work the same way, as long as they can build to self-contained HTML.

Entry TS: Mounting

import { createApp } from 'vue'
import '@patab/widget-sdk/theme.css'
import App from './widget.vue'

createApp(App).mount('#app')

Each surface has one mount script + one SFC (widget/detail/settings share the same structure).

SFC Walkthrough

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { connectPatabWidgetClient, createWidgetApi, type WidgetContext } from '@patab/widget-sdk'

const context = ref<WidgetContext>()
const api = ref<ReturnType<typeof createWidgetApi>>()

onMounted(async () => {
// 1. 挂载后连接宿主并读取上下文
const client = await connectPatabWidgetClient()
api.value = createWidgetApi(client)
context.value = await api.value.context.get()

// 2. 可选权限降级(同 vanilla 示例)
if (context.value.permissions.granted.includes('todos.read')) {
void api.value.todos.list().catch(() => undefined)
}
})

function save(): void {
// 3. 实例存储(替代 localStorage)
if (api.value && context.value) {
void api.value.storage.set('lastSurface', context.value.surface)
}
}
</script>

<template>
<main>
<h1>{{ context?.surface ?? 'widget' }}</h1>
<p>{{ context?.variantId ?? 'default' }}</p>
<button class="pt-button" @click="save">保存示例</button>
</main>
</template>

Key points:

  1. Async connection goes in onMounted: connectPatabWidgetClient() is asynchronous; the template uses optional chaining as a fallback for first-frame rendering (context?.surface ?? 'widget').
  2. Reactive context: storing the WidgetContext in a ref naturally wires SDK data into Vue rendering. To react to theme/locale changes, additionally subscribe to themeChanged/localeChanged and update the ref.
  3. No manual theme handling needed: this example does not subscribe to themeChanged — when the theme switches, the host writes the new --pt-* tokens directly onto the sandbox root node, and any CSS referencing the tokens (including the theme.css semantic classes) follows automatically.

Testing and Running

Unit tests are exactly the same as vanilla (Mock Host + vitest). To run locally:

cd examples/vue-widget
pnpm install
pnpm dev # Open the real PaTab development host and mount the widget
pnpm check && pnpm run pack

The repo-root scripts/verify-widget-examples.mjs runs check/build/deterministic pack comparison/inspect on both examples in sequence, plus keygen-signed packing and signature verification — a useful reference for CI integration.