diff --git a/addons/docs/src/frameworks/vue/sourceDecorator.test.ts b/addons/docs/src/frameworks/vue/sourceDecorator.test.ts index c4aa381a3a4c..54695c1ea84f 100644 --- a/addons/docs/src/frameworks/vue/sourceDecorator.test.ts +++ b/addons/docs/src/frameworks/vue/sourceDecorator.test.ts @@ -64,30 +64,30 @@ describe('vnodeToString', () => { expect( vnodeToString( getVNode({ - template: ``, + template: ``, }) ) - ).toMatchInlineSnapshot(``); + ).toMatchInlineSnapshot(``); }); - it('merge dynamic and static classes', () => { + it('object dynamic class', () => { expect( vnodeToString( getVNode({ - template: ``, + template: ``, }) ) - ).toMatchInlineSnapshot(``); + ).toMatchInlineSnapshot(``); }); - it('object dynamic class', () => { + it('merge dynamic and static classes', () => { expect( vnodeToString( getVNode({ - template: ``, + template: ``, }) ) - ).toMatchInlineSnapshot(``); + ).toMatchInlineSnapshot(``); }); it('attributes', () => { diff --git a/addons/docs/src/frameworks/vue/sourceDecorator.ts b/addons/docs/src/frameworks/vue/sourceDecorator.ts index 6178b60dc66f..40831a68f70c 100644 --- a/addons/docs/src/frameworks/vue/sourceDecorator.ts +++ b/addons/docs/src/frameworks/vue/sourceDecorator.ts @@ -76,7 +76,7 @@ export const sourceDecorator = (storyFn: any, context: StoryContext) => { export function vnodeToString(vnode: Vue.VNode): string { const attrString = [ ...(vnode.data?.slot ? ([['slot', vnode.data.slot]] as [string, any][]) : []), - ['class', normalizeClassAttribute(vnode)], + ['class', stringifyClassAttribute(vnode)], ...(vnode.componentOptions?.propsData ? Object.entries(vnode.componentOptions.propsData) : []), ...(vnode.data?.attrs ? Object.entries(vnode.data.attrs) : []), ] @@ -123,27 +123,41 @@ export function vnodeToString(vnode: Vue.VNode): string { .join('')}`; } -function normalizeClassAttribute(vnode: Vue.VNode): string | undefined { +function stringifyClassAttribute(vnode: Vue.VNode): string | undefined { if (!vnode.data || (!vnode.data.staticClass && !vnode.data.class)) { return undefined; } - let dynamicClass: readonly string[] = []; + return ( + [...(vnode.data.staticClass?.split(' ') ?? []), ...normalizeClassBinding(vnode.data.class)] + .filter(Boolean) + .join(' ') || undefined + ); +} - if (typeof vnode.data.class === 'string') { - dynamicClass = [vnode.data.class]; - } else if (vnode.data.class instanceof Array) { - dynamicClass = vnode.data.class; - } else if (typeof vnode.data.class === 'object') { - dynamicClass = Object.entries(vnode.data.class) +// https://vuejs.org/v2/guide/class-and-style.html#Binding-HTML-Classes +function normalizeClassBinding(binding: unknown): readonly string[] { + if (!binding) { + return []; + } + + if (typeof binding === 'string') { + return [binding]; + } + + if (binding instanceof Array) { + // To handle an object-in-array binding smartly, we use recursion + return binding.map(normalizeClassBinding).reduce((a, b) => [...a, ...b], []); + } + + if (typeof binding === 'object') { + return Object.entries(binding) .filter(([, active]) => !!active) .map(([className]) => className); } - return ( - [...(vnode.data.staticClass?.split(' ') ?? []), ...dynamicClass].filter(Boolean).join(' ') || - undefined - ); + // Unknown class binding + return []; } function stringifyAttr(attrName: string, value?: any): string | null {