Closed ivanRemotely closed 1 year ago
This is available for Vue2 and is implemented here: https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/sourceDecorator.ts
Unfortunately the VNodes are not exposed in the same way in Vue3, so we need to figure out a different approach. @phated can elaborate on this, or perhaps this is enough to get you going @ivanRemotely ?
That's correct, vnode's no longer contain most (any?) of the information that the sourceDecorator uses. From my deep-dive, it seems that this feature would need to be implemented with a "custom renderer", akin to Vue 3's SSR support. This would be done with https://v3.vuejs.org/api/global-api.html#createrenderer but it looks really complex (and I couldn't figure out how to do it).
Thanks for the info, I'll dig into it. I'm very new at Storybook so I can't guarantee results but I'll give it a spin nonetheless and see what sticks. I was able to at least get the template source by grabbing context().render().type.template
, looking into renderers now.
My main question is: What I don't fully understand is what is the scope of this work? Would grabbing the name of the component and the used props be enough? I guess if the component has slots and nested components it would make the code incomplete...
@ivanRemotely Trying to use template won't really work because many Vue 3 components don't have a template. A vnode can be made up of a template, a render function returning something constructed from h()
or a setup function that returns a render function. The only way to have true coverage over any type of component is to implement a "string renderer" using createRenderer
API.
As for the scope, it seems very large to me, as you have to implement everything a vnode can contain. The best "simplified" example of a renderer I could find is https://github.com/vuejs/vue-next/tree/master/packages/runtime-test (which is still quite complex).
Ooh was about to raise the same issue.
Workaround 1 roll it yourself.
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { MyButton },
template: '<my-button @click="onClick" v-bind="$props" />',
});
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
Primary.parameters = {
docs: { source: { code: '<my-button @click="onClick" v-bind="$props" />' } },
};
Workaround 2 roll it yourself a bit more thoroughly. Much of which could be extracted to a utility function
const templateSourceCode = (templateSource, args, replacing = 'v-bind="$props"') => {
const propToSource = (key, val) => {
const type = typeof val;
switch (type) {
case 'boolean':
return val ? key : '';
case 'string':
return `${key}="${val}"`;
default:
return `:${key}="${val}"`;
}
};
return templateSource.replace(
replacing,
Object.keys(args)
.map((key) => propToSource(key, args[key]))
.join(' ')
);
};
const template = '<my-button @click="onClick" v-bind="$props" />';
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { MyButton },
template,
});
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
Primary.parameters = {
docs: { source: { code: templateSourceCode(template, Primary.args) } },
};
Thanks for the answers but workaround 2 breaks the Canvas tab.. it's also not Vue 3 which uses setup to bind the args/props
Thanks for the answers but workaround 2 breaks the Canvas tab.. it's also not Vue 3 which uses setup to bind the args/props
Workaround 2 worked for me, make sure your template uses v-bind="$props"
as that's what the function is looking for. or you can add the third argument when you call the templateSourceCode
function to replace a different string in your template.
I do look forward to seeing this fixed officially, sorry I don't have enough knowledge in Storybook to create a PR
Workaround 2 does indeed work, but unfortunately the template output is static and not dynamic to the controls and their current state in storybook.
This is my workaround based on the code from @lee-chase.
For simplicity, I made it work by only looking for templates that are surrounded by backticks. The code goes in .storybook/preview.js
and works globally without any modification required to the stories (easier to remove when this gets eventually fixed).
import dedent from "ts-dedent";
import { paramCase } from "param-case";
const templateSourceCode = (
templateSource,
args,
argTypes,
replacing = ' v-bind="args"',
) => {
const componentArgs = {};
for (const [k, t] of Object.entries(argTypes)) {
const val = args[k];
if (typeof val !== 'undefined' && t.table && t.table.category === 'props' && val !== t.defaultValue) {
componentArgs[k] = val;
}
}
const propToSource = (key, val) => {
const type = typeof val;
switch (type) {
case "boolean":
return val ? key : "";
case "string":
return `${key}="${val}"`;
default:
return `:${key}="${val}"`;
}
};
return templateSource.replace(
replacing,
Object.keys(componentArgs)
.map((key) => " " + propToSource(paramCase(key), args[key]))
.join(""),
);
};
export const parameters = {
docs: {
transformSource(src, ctx) {
const match = /\b("')?template\1:\s*`([^`]+)`/.exec(src);
if (match) {
return templateSourceCode(dedent(match[2]), ctx.args, ctx.argTypes);
}
return src;
},
}
};
For my use-case I decided to add a filter to show only the props, but it can be easily removed.
Hi! We are very interested at Decathlon (https://github.com/decathlon/vitamin-web) in the resolution of this issue in Storybook but unfortunately, I don't know Storybook stack to contribute. Feel free if you have questions, would love to help you from consumer side. Thanks for the great job you do.
Hey @lauthieb!! Big fan of your work! 👋 Last time I checked, it was a technical blocker on the vue3 side of things -- unlike vue2, the output of Storybook's render function is not straightforward to turn into a string. @pocka is this still a blocker?
Hi @shilman, thanks for your quick answer!
For the moment I've implemented the workaround from @mtorromeo based on @lee-chase solution, it works well but I'm ready to migrate to built-in support in the next versions of @storybook/vue3
for sure!
https://github.com/Decathlon/vitamin-web/commit/a47e90d601e6858f24660e28e33aa6b46c662589
some problem
Workaround by @mtorromeo worked great for me until update to Storybook 6.4.5.
After update docs are not rendered at all and in the console it claims that args
is undefined
at const val = args[k];
It seems that instead of args
property there is initialArgs
in the ctx
object. So the last block is now:
export const parameters = {
docs: {
transformSource(src, ctx) {
const match = /\b("')?template\1:\s*`([^`]+)`/.exec(src);
if (match) {
return templateSourceCode(dedent(match[2]), ctx.initialArgs, ctx.argTypes); // HERE is the change
}
return src;
},
}
};
@Radouch replacing args with initialArgs is better than nothing, but it doesn't reflect changes to the props made at runtime.
Sadly, vue support in storybook is merely an afterthought and has too many quirks IMO.
I'm using the following decorator (globally registered) to workaround this issue:
import { addons, makeDecorator } from '@storybook/addons'
import { h, onMounted } from 'vue'
// this value doesn't seem to be exported by addons-docs
export const SNIPPET_RENDERED = `storybook/docs/snippet-rendered`
export const withSource = makeDecorator({
name: 'withSource',
wrapper: (storyFn, context) => {
const story = storyFn(context)
// this returns a new component that computes the source code when mounted
// and emits an events that is handled by addons-docs
// this approach is based on the vue (2) implementation
// see https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/sourceDecorator.ts
return {
components: {
Story: story
},
setup () {
onMounted(() => {
try {
// get the story source from the depths of storybook
const src = context.originalStoryFn.parameters.storySource.source
// this extracts the template from the story source
const match = /\b(["']?)template\1:\s*(["'`])([^\1]+)\2/.exec(src)
if (match) {
// generate the source code based on the current args
const code = templateSourceCode(match[3], context.args, context.argTypes)
const channel = addons.getChannel()
const emitFormattedTemplate = async () => {
const prettier = await import('prettier/standalone')
const prettierHtml = await import('prettier/parser-html')
// emits an event when the transformation is completed
channel.emit(
SNIPPET_RENDERED,
(context || {}).id,
prettier.format(`<template>${code}</template>`, {
parser: 'vue',
plugins: [prettierHtml],
htmlWhitespaceSensitivity: 'ignore'
})
)
}
emitFormattedTemplate()
}
} catch (e) {
console.warn('Failed to render code', e)
}
})
return () => h(story)
}
}
}
})
The templateSourceCode
is the same function as already mentioned above. Ugly but it works for now. Notice that this replaces the transformSource
approach mentioned above.
I tried creating a custom renderer for vue as @phated mentioned above to try to include the necessary information but ended up having to copy a lot of code from @vue/runtime-dom - as it doesn't export the necessary methods - so I dropped that for now. May try again at some point.
I'm working on the Dynamic snippet rendering feature for Vue 3 in #17295. As I've never used Vue 3 for my projects, I want to hear opinions from Vue 3 users. I'll close the PR if the implementation does not cover common use cases or the PR does not get enough attention (again, I've never used Vue 3, what is "common" is unknown to me).
You can try the feature by editing examples/vue-3-cli/src/stories/addons/docs/DynamicSnippet.stories.js
at pocka/feature/vue3-dynamic-code-rendering-experiments
branch.
Thanks @pocka !!! I'll try to kick the tires this weekend
I think the docs from https://storybook.js.org/docs/vue/writing-docs/doc-blocks should be updated with an warning that the vue3 example does not work as intended, with an explanation on how to disable the 'show/hide code'.
For now what i'm doing to make use of source in a way that makes sense is disabling source for all Canvas and implementing a static Source.
<Canvas withSource='none'>
<Story
name="link button"
args={{
variant: 'link',
default: 'link'
}}
>
{Template.bind({})}
</Story>
</Canvas>
<Source
language="jsx"
code={'<Button variant="link">link</Button>'}
/>
+1 this feature. Vue 3 is vue default version now, so it's really helpful if you can bring this feature to SB. Thank you.
+1
I was working on a workaround where I would create and mount a new vue instance in memory in the transformSource
function so that I could extract source code and the generated outer HTML - only to realize that only the initial arguments are available as pointed out by @Radouch, meaning the end result would be static and not influenced by user input. Why was args
removed from the storyContext
argument? Or is it simply a limitation inherited from Vue3?
This feature is vital for the usage of Vue3 in SB. Manually writing docs/source code defeats the whole purpose IMO.
@pocka can you give us an update on your PR? Is it alive and if so, what's the plan?
@kasperskov909 the "solution" I found for that is to make it a decorator instead of using the transformSource
function. So the args
are available and updated accordingly.
@tiagoskaneta alright sounds good. How do I implement your solution though?
@kasperskov909 see https://github.com/storybookjs/storybook/issues/13917#issuecomment-1013019386 It's a bit of hit or miss sometimes so a more permanent approach is definitely required to really use SB with vue3.
@tiagoskaneta I mean how do you implement it globally on all stories? I've wrapped stories in markup with decorators before but I can't figure out how to use yours?
@kasperskov909
@pocka can you give us an update on your PR? Is it alive and if so, what's the plan?
The PR is inactive. As I stated above, the implementation needs feedback from Vue3 users so that we get an idea of whether it matches users' expectations. Also, we need to find a better way to lookup a story component (https://github.com/storybookjs/storybook/pull/17295#issuecomment-1024363417).
@kasperskov909. I removed the transformSource
from the docs parameters in the main.js
. In the preview.js
I have
import { withSource } from './withSource'
export const decorators = [
withSource
]
So this will apply the decorator globally. I am seeing an issue with my solution though, where the transformed code is not being applied when changing pages, only when refreshing the page. So your millage may vary.
@tiagoskaneta right, thanks. My issue was with the SNIPPET_RENDERED
. Works now.
Btw I can't recognize context.originalStoryFn.parameters.storySource.source
. Are we not looking for context.originalStoryFn().template
? That way the regex is redundant.
@kasperskov909 https://github.com/tiagoskaneta/vue3-sb-code if you still need some reference.
And yes, looks like context.originalStoryFn().template
does work. so better then the regex :)
For the record, this is the full decorator I'm using
import { addons, makeDecorator } from "@storybook/addons";
import kebabCase from "lodash.kebabcase"
import { h, onMounted } from "vue";
// this value doesn't seem to be exported by addons-docs
export const SNIPPET_RENDERED = `storybook/docs/snippet-rendered`;
function templateSourceCode (
templateSource,
args,
argTypes,
replacing = 'v-bind="args"',
) {
const componentArgs = {}
for (const [k, t] of Object.entries(argTypes)) {
const val = args[k]
if (typeof val !== 'undefined' && t.table && t.table.category === 'props' && val !== t.defaultValue) {
componentArgs[k] = val
}
}
const propToSource = (key, val) => {
const type = typeof val
switch (type) {
case "boolean":
return val ? key : ""
case "string":
return `${key}="${val}"`
default:
return `:${key}="${val}"`
}
}
return templateSource.replace(
replacing,
Object.keys(componentArgs)
.map((key) => " " + propToSource(kebabCase(key), args[key]))
.join(""),
)
}
export const withSource = makeDecorator({
name: "withSource",
wrapper: (storyFn, context) => {
const story = storyFn(context);
// this returns a new component that computes the source code when mounted
// and emits an events that is handled by addons-docs
// this approach is based on the vue (2) implementation
// see https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/sourceDecorator.ts
return {
components: {
Story: story,
},
setup() {
onMounted(() => {
try {
// get the story source
const src = context.originalStoryFn().template;
// generate the source code based on the current args
const code = templateSourceCode(
src,
context.args,
context.argTypes
);
const channel = addons.getChannel();
const emitFormattedTemplate = async () => {
const prettier = await import("prettier/standalone");
const prettierHtml = await import("prettier/parser-html");
// emits an event when the transformation is completed
channel.emit(
SNIPPET_RENDERED,
(context || {}).id,
prettier.format(`<template>${code}</template>`, {
parser: "vue",
plugins: [prettierHtml],
htmlWhitespaceSensitivity: "ignore",
})
);
};
setTimeout(emitFormattedTemplate, 0);
} catch (e) {
console.warn("Failed to render code", e);
}
});
return () => h(story);
},
};
},
});
@tiagoskaneta cheers, much appreciated. I'll work on a solution for the page reload/route change problem and post here. After that I will look to the PR for a permanent solution.
@kasperskov909 using setTimeout(emitFormattedTemplate, 0);
did work for me to some extend.
For anyone reading this. Note that my suggetion for @tiagoskaneta solution with using the originalStoryFn()
doesn't work with default slots from props if you define your template in your story something like this:
const Template = (args) => ({
components: { XButton, XIcon },
setup() {
return { args };
},
template: `
<x-button v-bind="args">
<template v-slot:icon-left><x-icon :name="args['icon-left']" />
</template>
${args.default}
</x-button>
`
});
${args}
will be empty. You'll need to send the args
from the context
as parameters to the originalStoryFn()
:
const src = context.originalStoryFn(context.args).template;
args['icon-left']
still won't be parsed. I'm working on a solution. Should be manageable in templateSourceCode()
.
Hey all, I know this is being worked on, but I just wanted to note that the exact same issue is also present in the MDX versions of storybook when working with vue 3
Hi! I have use snippet from @tiagoskaneta it works!
But i faced with issue that code snipet not highlited properly
is there any idea how its can be fixed?
btw, i have added extra type switch entry to parse object values
const propToSource = (key, val) => {
const type = typeof val;
switch (type) {
case 'boolean':
return val ? key : '';
case 'string':
return `${key}="${val}"`;
case 'object':
return `${key}="${JSON.stringify(val).replace(/"(\w+)"\s*:/g, '$1:').replaceAll('"', '\'')}"`; // here
default:
return `:${key}="${val}"`;
}
};
Does somebody know if the problem with source code of Vue 3 stories is planned to be solved in Storybook 7?
Unfortunatelly, Storybook 7.0.0-beta.8 seems not to show Vue 3 source code correctly...
I may not be completely in the loop here, but, as far as I know, there is planning for porting Vue source generation but only after the the major is released, because for the major 7 release there has been a major refactor of various parts of the code, that being the case as far as I understand, they've paused all progress on other features outside of major 7. That being said I think there's a high chance of this feature becoming available in the near future after the major 7 release
Jiminy cricket!! I just released https://github.com/storybookjs/storybook/releases/tag/v7.0.0-beta.29 containing PR #20498 that references this issue. Upgrade today to the @next
NPM tag to try it out!
npx sb@next upgrade --prerelease
Closing this issue. Please re-open if you think there's still more to do.
Update SB version 7: Beware that the channel.emit() "hack" does not work in SB version 7. Not really a problem as Vue snippet rendering has been fixed. If you need to render other forms of snippets like HTML and Web Components you need to update your channel.emit call params to: channel.emit(SNIPPET_RENDERED, { id, args, source });
Notice they have been wrapped in an object.
@kasperskov909
Maybe you know how to get story template initial value now (in SB 7)? originalStoryFn(context.args, context).template
doesn't work now
@sttrbl origianlStoryFn is now an extension method on the context.
Use context.originalStoryFn(context.args).template
where context is an argument from the wrapper
arrow function.
@shilman
Sorry, I don't understand how PR #20498 fix this issue. I have storybook 7.4.0
. How can I enable pretty source code for Vue 3 component?
Now, I always see
I try solution by @tiagoskaneta. It works, but template not re-render after apply/change props.
Hi, for those who struggling with source code rerender absence on args change, here is the solution:
PS: prettier2
is prettier@2
installed as alias (because prettier@3
doesn't sync).
PS: PS: I also renamed decorator into vue3SourceDecorator
.
import { addons, makeDecorator } from "@storybook/addons";
import { h, onMounted, watch } from "vue";
export const vue3SourceDecorator = makeDecorator({
name: "vue3SourceDecorator",
wrapper: (storyFn, context) => {
const story = storyFn(context);
// this returns a new component that computes the source code when mounted
// and emits an events that is handled by addons-docs
// watch args and re-emit on change
return {
components: {
story,
},
setup() {
onMounted(() => {
setSourceCode();
});
watch(context.args, () => {
setSourceCode();
});
function setSourceCode() {
try {
const src = context.originalStoryFn(context.args).template;
const code = templateSourceCode(src, context.args, context.argTypes);
const channel = addons.getChannel();
const emitFormattedTemplate = async () => {
const prettier = await import("prettier2");
const prettierHtml = await import("prettier2/parser-html");
const formattedCode = prettier.format(code, {
parser: "html",
plugins: [prettierHtml],
htmlWhitespaceSensitivity: "ignore",
});
// emits an event when the transformation is completed
channel.emit("storybook/docs/snippet-rendered", (context || {}).id, formattedCode);
};
setTimeout(emitFormattedTemplate, 0);
} catch (e) {
// eslint-disable-next-line no-console
console.warn("Failed to render code", e);
}
}
return () =>
h("div", { style: `padding: 2rem 1.5rem 3rem 1.5rem;` }, [h(story)]);
},
};
},
});
function templateSourceCode(templateSource, args, argTypes) {
const componentArgs = {};
for (const [key, val] of Object.entries(argTypes)) {
const value = args[key];
if (
typeof val !== "undefined" &&
val.table &&
val.table.category === "props" &&
value !== val.defaultValue
) {
componentArgs[key] = val;
}
}
return templateSource
.replace(
'v-bind="args"',
Object.keys(componentArgs)
.map((key) => " " + propToSource(kebabCase(key), args[key]))
.join(""),
);
}
function propToSource(key, val) {
const type = typeof val;
switch (type) {
case "boolean":
return val ? key : "";
case "string":
return `${key}="${val}"`;
default:
return `:${key}="${val}"`;
}
}
function kebabCase(str) {
return str
.split("")
.map((letter, idx) => {
return letter.toUpperCase() === letter
? `${idx !== 0 ? "-" : ""}${letter.toLowerCase()}`
: letter;
})
.join("");
}
Hi, for those who struggling with source code rerender absence on args change, here is the solution:
PS:
prettier2
isprettier@2
installed as alias (becauseprettier@3
doesn't sync). PS: PS: I also renamed decorator intovue3SourceDecorator
.import { addons, makeDecorator } from "@storybook/addons"; import { h, onMounted, watch } from "vue"; export const vue3SourceDecorator = makeDecorator({ name: "vue3SourceDecorator", wrapper: (storyFn, context) => { const story = storyFn(context); // this returns a new component that computes the source code when mounted // and emits an events that is handled by addons-docs // watch args and re-emit on change return { components: { story, }, setup() { onMounted(() => { setSourceCode(); }); watch(context.args, () => { setSourceCode(); }); function setSourceCode() { try { const src = context.originalStoryFn(context.args).template; const code = templateSourceCode(src, context.args, context.argTypes); const channel = addons.getChannel(); const emitFormattedTemplate = async () => { const prettier = await import("prettier2"); const prettierHtml = await import("prettier2/parser-html"); const formattedCode = prettier.format(code, { parser: "html", plugins: [prettierHtml], htmlWhitespaceSensitivity: "ignore", }); // emits an event when the transformation is completed channel.emit("storybook/docs/snippet-rendered", (context || {}).id, formattedCode); }; setTimeout(emitFormattedTemplate, 0); } catch (e) { // eslint-disable-next-line no-console console.warn("Failed to render code", e); } } return () => h("div", { style: `padding: 2rem 1.5rem 3rem 1.5rem;` }, [h(story)]); }, }; }, }); function templateSourceCode(templateSource, args, argTypes) { const componentArgs = {}; for (const [key, val] of Object.entries(argTypes)) { const value = args[key]; if ( typeof val !== "undefined" && val.table && val.table.category === "props" && value !== val.defaultValue ) { componentArgs[key] = val; } } return templateSource .replace( 'v-bind="args"', Object.keys(componentArgs) .map((key) => " " + propToSource(kebabCase(key), args[key])) .join(""), ); } function propToSource(key, val) { const type = typeof val; switch (type) { case "boolean": return val ? key : ""; case "string": return `${key}="${val}"`; default: return `:${key}="${val}"`; } } function kebabCase(str) { return str .split("") .map((letter, idx) => { return letter.toUpperCase() === letter ? `${idx !== 0 ? "-" : ""}${letter.toLowerCase()}` : letter; }) .join(""); }
Any idea on how to implement this decorator? currently using Storybook 7.6.8 with vue3-vite and it's only showing story's configuration code:
{
name: 'Default',
args: {
...defaultArgs
}
}
@isorna did you find a way how to use it? Facing the same issue.
I had to modify it from the story's render:
StoryName = {
args: ...
render: (args) => {
StoryName.parameters.docs.source.code = getCodeTemplate(args);
components: { MyComponent },
setup() {
return { args };
},
template: getStoryTemplate(args),
}
}
Am I missing something here?
Using a story definition of:
export const Primary = {
args: {
primary: true,
label: 'Button',
},
render: (args) => {
return {
components: { MyButton },
setup() {
return { args };
},
template: '<my-button v-bind="args" />',
}
},
};
results in:
I expected to see some nicely rendered vue3 code like this:
@mikemonteith your example is working as expected for me. do you have a reproduction you can share?
Thanks @shilman . I have reproduced it here: https://github.com/mikemonteith/storybook-dynamic-render-testcase
Hi @mikemonteith ! I upgraded your project to Storybook 8 using npx storybook@next upgrade
and it's working fine there. SB8 is scheduled for full release next week and contains TONS more quality of life improvements for Vue users (and everybody!) so my recommendation would be to just upgrade. (It should work in SB7 too, but 🤷 !!!)
I have the exact same issue as @mikemonteith using storybook 7.6.17.
The problem is that we need this specific syntax when dealing with slots. If you have to render some text as slot value for example, because you don't want to use a props label (for many reasons), you have no choice to use a template. In React no problem children is a props so you can acces it with the short args object syntax.
I would not be surprised that the same problem occurs with Svelte as well.
I will wait for the v8 release and let you know if the problem is solved on my side.
Describe the bug When clicking the "View Source" option in the docs tab, the presented code is the template for the story as opposed to the generated Vue code. (See screenshot below)
To Reproduce Steps to reproduce the behavior:
The issue is present in the vue3 cli example, so you can:
Expected behavior The presented panel should show usable Vue code.
Screenshots
System
Environment Info:
System: OS: macOS 10.15.6 CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz Binaries: Node: 12.13.0 - ~/.nvm/versions/node/v12.13.0/bin/node npm: 6.12.0 - ~/.nvm/versions/node/v12.13.0/bin/npm Browsers: Chrome: 88.0.4324.150 Firefox: 85.0.2 Safari: 13.1.2 npmPackages: @storybook/addon-actions: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/addon-essentials: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/addon-links: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/addon-storyshots: 6.2.0-alpha.28 => 6.2.0-alpha.28 @storybook/vue3: 6.2.0-alpha.28 => 6.2.0-alpha.28 npmGlobalPackages: @storybook/cli: 6.2.0-alpha.24
Additional context I understand Vue 3 support is still a WIP. I'd be more than happy to help fix the issue if you can point me in the general direction of the problem. Thank you for you time and help!