Skip to content

add 'attrs' support #111

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/constructors/styled.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ import domElements from '../utils/domElements'
import isValidElementType from '../utils/isValidElementType'

export default (createStyledComponent) => {
const styled = (tagName, props = {}) => {
const styled = (tagName, props = {}, options = {}) => {
if (!isValidElementType(tagName)) {
throw new Error(tagName + ' is not allowed for styled tag type.')
}
return (cssRules, ...interpolations) => (
createStyledComponent(tagName, css(cssRules, ...interpolations), props)

const templateFunction = (cssRules, ...interpolations) => (
createStyledComponent(tagName, css(cssRules, ...interpolations), props, options)
)

templateFunction.attrs = attrs => styled(tagName, props, {
...options,
attrs: Array.prototype.concat(options.attrs, attrs).filter(Boolean)
})

return templateFunction
}

domElements.forEach((domElement) => {
Expand Down
37 changes: 33 additions & 4 deletions src/models/StyledComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import normalizeProps from '../utils/normalizeProps'
import isVueComponent from '../utils/isVueComponent'

export default (ComponentStyle) => {
const createStyledComponent = (target, rules, props) => {
const createStyledComponent = (target, rules, props, options) => {
const {
attrs = []
} = options
const componentStyle = new ComponentStyle(rules)

// handle array-declaration props
Expand Down Expand Up @@ -46,6 +49,7 @@ export default (ComponentStyle) => {
class: [this.generatedClassName],
props: this.$props,
domProps: {
...this.attrs,
value: this.localValue
},
on: {
Expand All @@ -68,11 +72,36 @@ export default (ComponentStyle) => {
},
computed: {
generatedClassName () {
const componentProps = { theme: this.theme, ...this.$props }
const { context, attrs } = this
const componentProps = { ...context, ...attrs }
return this.generateAndInjectStyles(componentProps)
},
theme () {
return this.$theme()
},
context () {
return {
theme: this.theme,
...this.$props
}
},
attrs () {
const resolvedAttrs = {}
const { context } = this

attrs.forEach((attrDef) => {
let resolvedAttrDef = attrDef

if (typeof resolvedAttrDef === 'function') {
resolvedAttrDef = resolvedAttrDef(context)
}

for (const key in resolvedAttrDef) {
context[key] = resolvedAttrs[key] = resolvedAttrDef[key]
}
})

return resolvedAttrs
}
},
watch: {
Expand All @@ -85,10 +114,10 @@ export default (ComponentStyle) => {
},
extend (cssRules, ...interpolations) {
const extendedRules = css(cssRules, ...interpolations)
return createStyledComponent(target, rules.concat(extendedRules), props)
return createStyledComponent(target, rules.concat(extendedRules), props, options)
},
withComponent (newTarget) {
return createStyledComponent(newTarget, rules, props)
return createStyledComponent(newTarget, rules, props, options)
}
}

Expand Down
168 changes: 168 additions & 0 deletions src/test/attrs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import Vue from 'vue'
import expect from 'expect'

import { resetStyled, expectCSSMatches } from './utils'

let styled

describe('"attrs" feature', () => {
beforeEach(() => {
styled = resetStyled()
})

it('should add html attributes to an element', () => {
const Component = styled('img', {}).attrs({ src: 'image.jpg' })`
width: 50;
`
const vm = new Vue(Component).$mount()
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg' })
})

it('should add several html attributes to an element', () => {
const Component = styled('img', {}).attrs({ src: 'image.jpg', alt: 'Test image' })`
width: 50;
`
const vm = new Vue(Component).$mount()
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image' })
})

it('should work as expected with empty attributes object provided', () => {
const Component = styled('img', {}).attrs({})`
width: 50;
`
const vm = new Vue(Component).$mount()
expectCSSMatches('.a {width: 50;}')
})

it('should work as expected with null attributes object provided', () => {
const Component = styled('img', {}).attrs(null)`
width: 50;
`
const vm = new Vue(Component).$mount()
expectCSSMatches('.a {width: 50;}')
expect(vm._vnode.data.domProps).toEqual({})
})

it('should work as expected without attributes provided', () => {
const Component = styled('img')`
width: 50;
`
const vm = new Vue(Component).$mount()
expectCSSMatches('.a {width: 50;}')
expect(vm._vnode.data.domProps).toEqual({})
})

it('should work with a function as a parameter of of the method', () => {
const Component = styled('img', {}).attrs(() => ({
src: 'image.jpg',
alt: 'Test image',
height: '50'
}))`
width: 50;
`
const vm = new Vue(Component).$mount()
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: '50' })
})

it('should work with multiple attrs method call', () => {
const Component = styled('img', {})
.attrs(() => ({
src: 'image.jpg',
alt: 'Test image'
}))
.attrs({
height: '50'
})`
width: 50;
`
const vm = new Vue(Component).$mount()
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: '50' })
})

it('should access to all previous attribute properties', () => {
const Component = styled('img', {})
.attrs(() => ({
src: 'image',
alt: 'My test image'
}))
.attrs((props) => ({
src: props.src + '.jpg',
height: 5 * 10
}))`
width: 50;
`
const vm = new Vue(Component).$mount()
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'My test image', height: 50 })
})

it('should override attribute properties', () => {
const Component = styled('img', {})
.attrs(() => ({
src: 'image.jpg',
alt: 'Test image',
height: '20'
}))
.attrs({
height: '50'
})`
width: 50;
`
const vm = new Vue(Component).$mount()
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: '50' })
})

it('should access to component props', () => {
const Component = styled('img', { propsHeight: Number })
.attrs((props) => ({
src: 'image.jpg',
alt: 'Test image',
height: props.propsHeight * 2
}))`
width: 50;
`

const vm = new Vue({
render: function (h) {
return h(Component, {
props: {
propsHeight: 20
},
})
}
}).$mount()

expect(vm.$children[0]._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: 40 })
})

it('attributes should be reactive', () => {
const Component = styled('img', { propsHeight: Number })
.attrs((props) => ({
src: 'image.jpg',
alt: 'Test image',
height: props.propsHeight * 2
}))`
width: 50;
`

const vm = new Vue({
render: function (h) {
const self = this
return h(Component, {
props: {
propsHeight: self.dataHeight
},
})
},
data: () => ({
dataHeight: 20
})
}).$mount()

expect(vm.$children[0]._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: 40 })

vm.dataHeight = 90
setTimeout(() => { // $nextTick
expect(vm.$children[0]._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: 180 })
}, 0)
})
})