{"_id":"@csirt-cms/widget-vue","name":"@csirt-cms/widget-vue","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.1":{"name":"@csirt-cms/widget-vue","version":"0.1.1","description":"Vue 3 and Nuxt runtime renderers for the CSIRT CMS widget system.","license":"MIT","type":"module","sideEffects":["*.css"],"types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./styles.css":"./styles.css","./package.json":"./package.json"},"scripts":{"build":"vite build && node ../../scripts/copy-styles.mjs vue","typecheck":"vue-tsc --noEmit"},"dependencies":{"@csirt-cms/widget-core":"0.1.0"},"peerDependencies":{"vue":"^3.4.0","zod":"^4.0.0"},"devDependencies":{"@vitejs/plugin-vue":"^6.0.8","typescript":"^5","vite":"^8.2.0","vite-plugin-dts":"^4.5.4","vue":"^3.5.40","vue-tsc":"^3.3.9","zod":"^4.4.3"},"keywords":["cms","widget","vue","nuxt","page-builder"],"publishConfig":{"access":"public"},"gitHead":"d94d997bc5b3bbaf4876c2a6b5bc314e297a7833","_id":"@csirt-cms/widget-vue@0.1.1","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-aCougPEG99+2mqvVjz8SMkevJVvKSYNTxMuk12EYm9YJUl8T0ijmFNMOAWIPF0SjuwIwDSROKva5ee3AcPL5iA==","shasum":"19432a765d9a57aa2a67d7c53cdd495f9de39e87","tarball":"https://registry.npmjs.org/@csirt-cms/widget-vue/-/widget-vue-0.1.1.tgz","fileCount":48,"unpackedSize":245618,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDsHwmaLuQmhQpWuuHvQkWaKh0WzaKjs3d9jR2+qDOkOwIgM1dQkdhyA9uL+vBjPaucFunl0wsd/2SfObIj/sQsITE="}]},"_npmUser":{"name":"fxbayuanggara","email":"hi@fxbayuanggara.com"},"directories":{},"maintainers":[{"name":"fxbayuanggara","email":"hi@fxbayuanggara.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/widget-vue_0.1.1_1785768542993_0.7108498194184358"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T14:49:02.804Z","0.1.1":"2026-08-03T14:49:03.142Z","modified":"2026-08-03T14:49:03.353Z"},"maintainers":[{"name":"fxbayuanggara","email":"hi@fxbayuanggara.com"}],"description":"Vue 3 and Nuxt runtime renderers for the CSIRT CMS widget system.","keywords":["cms","widget","vue","nuxt","page-builder"],"license":"MIT","readme":"# @csirt-cms/widget-vue\n\nVue 3 and Nuxt runtime renderers for the CSIRT CMS widget system. Takes the\n`{ component, props }` sections the Public API returns and renders them through a\nregistry your Page Builder never has to know about.\n\nEvery widget produces the same markup and class names as\n[`@csirt-cms/widget-react`](https://www.npmjs.com/package/@csirt-cms/widget-react),\nso the two share one stylesheet and one set of schemas. A parity check in the\nrepo server-renders all ten widgets in both frameworks and fails the build if\ntheir class vocabularies diverge.\n\n```bash\nnpm install @csirt-cms/widget-vue\n```\n\nPeers: `vue ^3.4`, `zod ^4`.\n\n---\n\n## Quick start\n\n```ts\n// main.ts\nimport { createApp } from \"vue\";\nimport { CsirtCmsWidgets } from \"@csirt-cms/widget-vue\";\nimport \"@csirt-cms/widget-vue/styles.css\";\nimport App from \"./App.vue\";\n\ncreateApp(App).use(CsirtCmsWidgets).mount(\"#app\");\n```\n\nThe plugin registers every built-in widget and two global components,\n`CmsPageRenderer` and `CmsWidgetPreview`:\n\n```vue\n<script setup lang=\"ts\">\nimport { createCmsClient, useCmsPage } from \"@csirt-cms/widget-vue\";\n\nconst client = createCmsClient({ baseUrl: import.meta.env.VITE_CMS_URL, site: \"csirt\" });\nconst { data: page, loading } = useCmsPage(client, \"beranda\");\n</script>\n\n<template>\n  <p v-if=\"loading\">Loading…</p>\n  <CmsPageRenderer v-else-if=\"page\" :sections=\"page.sections\" />\n</template>\n```\n\nPlugin options:\n\n```ts\napp.use(CsirtCmsWidgets, {\n  widgets: [MyWidget],       // additional widgets\n  includeBuiltIns: true,     // register the ten built-ins too (default)\n  registerComponents: true,  // register the global components (default)\n});\n```\n\nOr import the component directly and skip the plugin:\n\n```vue\n<script setup lang=\"ts\">\nimport { PageRenderer, builtInWidgets, registerWidgets } from \"@csirt-cms/widget-vue\";\nregisterWidgets(builtInWidgets);\n</script>\n\n<template>\n  <PageRenderer :sections=\"sections\" />\n</template>\n```\n\n---\n\n## `PageRenderer`\n\n```vue\n<PageRenderer\n  :sections=\"page.sections\"\n  device=\"mobile\"\n  :preview=\"false\"\n  :now=\"new Date()\"\n>\n  <template #fallback=\"{ widgetKey }\">\n    <p>Unknown widget: {{ widgetKey }}</p>\n  </template>\n</PageRenderer>\n```\n\nIt accepts the Public API's `{ component, props }` pairs directly, or the fuller\n`SectionState` the Page Builder holds.\n\nFor each section it migrates stored props to the widget's current version, fills\ngaps from defaults, and validates. A section that still fails validation renders\nits empty state rather than taking the page down. An unregistered key warns in\ndevelopment and renders the `#fallback` slot if you supply one.\n\n---\n\n## Nuxt\n\nRegister in a plugin so the server and client registries agree:\n\n```ts\n// plugins/cms-widgets.ts\nimport { CsirtCmsWidgets } from \"@csirt-cms/widget-vue\";\nimport \"@csirt-cms/widget-vue/styles.css\";\n\nexport default defineNuxtPlugin((nuxtApp) => {\n  nuxtApp.vueApp.use(CsirtCmsWidgets);\n});\n```\n\nFetch with `useAsyncData` so the payload is server-rendered and hydrated:\n\n```vue\n<script setup lang=\"ts\">\nimport { createCmsClient } from \"@csirt-cms/widget-vue\";\n\nconst client = createCmsClient({ baseUrl: useRuntimeConfig().cmsUrl, site: \"csirt\" });\nconst { data: page } = await useAsyncData(\"page\", () => client.getPage(\"beranda\"));\n</script>\n\n<template>\n  <CmsPageRenderer v-if=\"page\" :sections=\"page.sections\" />\n</template>\n```\n\nMarkdown and HTML are rendered and sanitized during the server render, so no\nMarkdown parser or sanitizer reaches the browser.\n\n---\n\n## Adding a widget\n\nNothing in the Page Builder changes — it renders whatever is registered.\n\n```bash\nnpm run generate:widget -- testimonial --name \"Testimonial\" --category Marketing\n```\n\nOr by hand:\n\n```vue\n<!-- Renderer.vue -->\n<script setup lang=\"ts\">\nimport type { TestimonialProps } from \"./types\";\ndefineProps<TestimonialProps>();\n</script>\n\n<template>\n  <blockquote class=\"cms-w cms-w-testimonial\">{{ quote }} — {{ author }}</blockquote>\n</template>\n```\n\n```ts\nimport { defineWidget, registerWidget } from \"@csirt-cms/widget-vue\";\nimport { testimonialDefinition } from \"./definition\";\nimport Renderer from \"./Renderer.vue\";\n\nregisterWidget(defineWidget(testimonialDefinition, { renderer: Renderer }));\n```\n\n> Props types must be **plain interfaces**, not `z.infer` aliases. Vue's SFC\n> compiler resolves types itself, separately from TypeScript, and cannot follow\n> Zod's inferred conditional types — `defineProps<SomeZodInfer>()` fails with\n> \"Unresolvable type reference\". Every definition in `@csirt-cms/widget-core`\n> exports a hand-written interface for exactly this reason.\n\n---\n\n## Registry and preview\n\n```ts\nimport {\n  getWidgets, getWidgetsByCategory, getWidgetCategories, searchWidgets,\n} from \"@csirt-cms/widget-vue\";\n```\n\n```vue\n<CmsWidgetPreview widget-key=\"hero-carousel\" :props=\"draftProps\" device=\"mobile\" />\n```\n\n---\n\n## Composables\n\n```ts\nconst { data, loading, error, reload } = useCmsPage(client, \"beranda\");\n```\n\nAlso `useCmsNavigation`, `useCmsAnnouncements`, `useCmsTheme`. Under Nuxt prefer\n`useAsyncData`.\n\n---\n\n## Styling\n\nOne stylesheet, shared with the React package. Every value is a CSS custom\nproperty with a fallback, so a site's theme overrides any of it:\n\n```ts\nimport { themeToCssText } from \"@csirt-cms/widget-vue\";\n\nuseHead({ style: [{ children: themeToCssText(theme) }] });\n```\n\nDark mode follows the OS, or is forced with `data-cms-theme=\"dark\"` on `<html>`.\n\n---\n\n## Security\n\nThe `html` and `markdown` widgets sanitize before rendering. The built-in\nsanitizer suits trusted CMS editors — for untrusted input, pass your own via the\n`sanitizer` prop. See the `@csirt-cms/widget-core` README for the full note.\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-b156268e083418a30b47c667eb86a8fd"}