-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Storybook controls dont react to v-model changes #14259
Comments
Looking at the source code, perhaps the control could be updated if the input events were passed to the knob as onChange events |
Essentially, you're asking for the Storybook arg to be automatically updated to reflect any mutation on the value (by the component). Most args are primitive values, so these can't actually be mutated, only replaced. Controls was never designed to be bi-directional or "reactive". If we go down that road, we'd have to implement it to work with any framework, not just Vue. Introducing a wrapper object (a "ref" in React nomenclature) to enable mutability on arg values could be a solution. Alternatively we could also pass a "setter" for each arg, so that one can explicitly hook that up to their component (in the story template). |
I would love the "setter" solution, which would be a lot more generic than using something like a ref (or whatever fits with each framework) and, if desired, there could be wrappers that work with each framework. |
I feel this feature is essential, and should be added to the newer Controls. |
Really hope this will be added |
A setter would be rad. Any idea if this is in the works? |
No it's not. If somebody wants to create an addon to do this, it might be pretty easy to do and I'd be happy to guide. We probably won't build this into Storybook by default, but if somebody makes an addon and it's simple enough, I'd consider adding it as part of |
This blogpost solved the issue for me: https://craigbaldwin.com/blog/updating-args-storybook-vue/ |
It work for me. import type { Preview } from '@storybook/vue3';
import { useArgs } from '@storybook/preview-api';
const preview: Preview = {
decorators: [
/**
* Support `v-model` for vue
* @see {@link https://craigbaldwin.com/blog/updating-args-storybook-vue/}
*/
(story, context) => {
const [args, updateArgs] = useArgs();
if ('modelValue' in args) {
const update = args['onUpdate:model-value'] || args['onUpdate:modelValue'];
args['onUpdate:model-value'] = undefined;
args['onUpdate:modelValue'] = (...vals) => {
update?.(...vals);
/**
* Arg with `undefined` will be deleted by `deleteUndefined()`, then loss of reactive
* @see {@link https://github.com/storybookjs/storybook/blob/next/code/lib/preview-api/src/modules/store/ArgsStore.ts#L63}
*/
const modelValue = vals[0] === undefined ? null : vals[0];
updateArgs({ modelValue });
};
}
return story({ ...context, updateArgs });
}
],
};
export default preview; |
😃 A better solution import { SetupContext, watch } from 'vue';
import { Preview } from '@storybook/vue3';
import { useArgs } from '@storybook/preview-api';
type Decorator = Required<Preview>['decorators'][number];
/**
* Insensibly realize two-way binding of vue3's `v-model` and storybook7's `args`
*/
const patchModel: Decorator = (story, context) => {
const [args, updateArgs] = useArgs();
const { component } = context as any;
if (component.setup.__id === window.location.href) {
return story(context);
}
const proxy = (props: Record<string, any>, ctx: SetupContext) => {
Object.keys(props).forEach((key: string) => {
const [event, model] = key.split(':');
const updateModel = props[key];
if (event !== 'onUpdate' || typeof updateModel !== 'function') {
return;
}
/**
* @fix Arg with `undefined` will be deleted by `deleteUndefined()`, then loss of reactive
* @see {@link https://github.com/storybookjs/storybook/blob/next/code/lib/preview-api/src/modules/store/ArgsStore.ts#L63}
*/
watch(() => props[model], (val = null) => {
updateArgs({ [model]: val });
});
watch(() => args[model], val => {
updateModel(val);
});
});
return proxy.__origin(props, ctx);
};
if (!proxy.__origin) {
proxy.__origin = component.setup;
}
proxy.__id = window.location.href;
component.setup = proxy;
return story(context);
};
export const decorators: Preview['decorators'] = [
patchModel,
]; |
Or this naive solution import { Preview } from '@storybook/vue3'
import { useArgs } from '@storybook/preview-api'
type Decorator = Required<Preview>['decorators'][number]
/**
* Insensibly realize two-way binding of vue3's `v-model` and storybook7's `args`
* Not interested in maintaining this, when it stops working we can just delete it
*/
export const patchVModel: Decorator = (story, context) => {
const [args, updateArgs] = useArgs()
const { component } = context as any
component?.emits?.forEach((emit: string) => {
const [, model] = emit.split(':')
if(!model) return
const update = args[emit]
args[emit.replace('update', 'onUpdate')] = (...vals) => {
update?.(...vals)
/**
* Arg with `undefined` will be deleted by `deleteUndefined()`, then loss of reactive
* @see {@link https://github.com/storybookjs/storybook/blob/next/code/lib/preview-api/src/modules/store/ArgsStore.ts#L63}
*/
const modelValue = vals[0] === undefined ? null : vals[0]
updateArgs({ [model]: modelValue })
}
})
return story({ ...context, updateArgs })
} |
please be aware this solution will break the source code generation |
It also works with Storybook 8. However, it's advised not to use |
Describe the bug
In vue you should not mutate props, and they propose you should implent a handler like so for v-model;
I've done this but storybook doesn't react to the input event
To Reproduce
Create a component that uses v-model and have storybook provide the value
#10019 also shows this
Expected behavior
Controls to react to the input
Screenshots
If applicable, add screenshots to help explain your problem.
Code snippets
If applicable, add code samples to help explain your problem.
System
Additional context
Add any other context about the problem here.
The text was updated successfully, but these errors were encountered: