Skip to content

Commit 833ec62

Browse files
committed
add 'attrs' support
1 parent 4bbd5d8 commit 833ec62

File tree

3 files changed

+212
-7
lines changed

3 files changed

+212
-7
lines changed

src/constructors/styled.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ import domElements from '../utils/domElements'
33
import isValidElementType from '../utils/isValidElementType'
44

55
export default (createStyledComponent) => {
6-
const styled = (tagName, props = {}) => {
6+
const styled = (tagName, props = {}, options = {}) => {
77
if (!isValidElementType(tagName)) {
88
throw new Error(tagName + ' is not allowed for styled tag type.')
99
}
10-
return (cssRules, ...interpolations) => (
11-
createStyledComponent(tagName, css(cssRules, ...interpolations), props)
10+
11+
const templateFunction = (cssRules, ...interpolations) => (
12+
createStyledComponent(tagName, css(cssRules, ...interpolations), props, options)
1213
)
14+
15+
templateFunction.attrs = attrs => styled(tagName, props, {
16+
...options,
17+
attrs: Array.prototype.concat(options.attrs, attrs).filter(Boolean)
18+
})
19+
20+
return templateFunction
1321
}
1422

1523
domElements.forEach((domElement) => {

src/models/StyledComponent.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import normalizeProps from '../utils/normalizeProps'
33
import isVueComponent from '../utils/isVueComponent'
44

55
export default (ComponentStyle) => {
6-
const createStyledComponent = (target, rules, props) => {
6+
const createStyledComponent = (target, rules, props, options) => {
7+
const {
8+
attrs = []
9+
} = options
710
const componentStyle = new ComponentStyle(rules)
811

912
// handle array-declaration props
@@ -46,6 +49,7 @@ export default (ComponentStyle) => {
4649
class: [this.generatedClassName],
4750
props: this.$props,
4851
domProps: {
52+
...this.attrs,
4953
value: this.localValue
5054
},
5155
on: {
@@ -68,11 +72,36 @@ export default (ComponentStyle) => {
6872
},
6973
computed: {
7074
generatedClassName () {
71-
const componentProps = { theme: this.theme, ...this.$props }
75+
const { context, attrs } = this
76+
const componentProps = { ...context, ...attrs }
7277
return this.generateAndInjectStyles(componentProps)
7378
},
7479
theme () {
7580
return this.$theme()
81+
},
82+
context () {
83+
return {
84+
theme: this.theme,
85+
...this.$props
86+
}
87+
},
88+
attrs () {
89+
const resolvedAttrs = {}
90+
const { context } = this
91+
92+
attrs.forEach((attrDef) => {
93+
let resolvedAttrDef = attrDef
94+
95+
if (typeof resolvedAttrDef === 'function') {
96+
resolvedAttrDef = resolvedAttrDef(context)
97+
}
98+
99+
for (const key in resolvedAttrDef) {
100+
context[key] = resolvedAttrs[key] = resolvedAttrDef[key]
101+
}
102+
})
103+
104+
return resolvedAttrs
76105
}
77106
},
78107
watch: {
@@ -85,10 +114,10 @@ export default (ComponentStyle) => {
85114
},
86115
extend (cssRules, ...interpolations) {
87116
const extendedRules = css(cssRules, ...interpolations)
88-
return createStyledComponent(target, rules.concat(extendedRules), props)
117+
return createStyledComponent(target, rules.concat(extendedRules), props, options)
89118
},
90119
withComponent (newTarget) {
91-
return createStyledComponent(newTarget, rules, props)
120+
return createStyledComponent(newTarget, rules, props, options)
92121
}
93122
}
94123

src/test/attrs.test.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import Vue from 'vue'
2+
import expect from 'expect'
3+
4+
import { resetStyled, expectCSSMatches } from './utils'
5+
6+
let styled
7+
8+
describe('"attrs" feature', () => {
9+
beforeEach(() => {
10+
styled = resetStyled()
11+
})
12+
13+
it('should add html attributes to an element', () => {
14+
const Component = styled('img', {}).attrs({ src: 'image.jpg' })`
15+
width: 50;
16+
`
17+
const vm = new Vue(Component).$mount()
18+
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg' })
19+
})
20+
21+
it('should add several html attributes to an element', () => {
22+
const Component = styled('img', {}).attrs({ src: 'image.jpg', alt: 'Test image' })`
23+
width: 50;
24+
`
25+
const vm = new Vue(Component).$mount()
26+
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image' })
27+
})
28+
29+
it('should work as expected with empty attributes object provided', () => {
30+
const Component = styled('img', {}).attrs({})`
31+
width: 50;
32+
`
33+
const vm = new Vue(Component).$mount()
34+
expectCSSMatches('.a {width: 50;}')
35+
})
36+
37+
it('should work as expected with null attributes object provided', () => {
38+
const Component = styled('img', {}).attrs(null)`
39+
width: 50;
40+
`
41+
const vm = new Vue(Component).$mount()
42+
expectCSSMatches('.a {width: 50;}')
43+
expect(vm._vnode.data.domProps).toEqual({})
44+
})
45+
46+
it('should work as expected without attributes provided', () => {
47+
const Component = styled('img')`
48+
width: 50;
49+
`
50+
const vm = new Vue(Component).$mount()
51+
expectCSSMatches('.a {width: 50;}')
52+
expect(vm._vnode.data.domProps).toEqual({})
53+
})
54+
55+
it('should work with a function as a parameter of of the method', () => {
56+
const Component = styled('img', {}).attrs(() => ({
57+
src: 'image.jpg',
58+
alt: 'Test image',
59+
height: '50'
60+
}))`
61+
width: 50;
62+
`
63+
const vm = new Vue(Component).$mount()
64+
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: '50' })
65+
})
66+
67+
it('should work with multiple attrs method call', () => {
68+
const Component = styled('img', {})
69+
.attrs(() => ({
70+
src: 'image.jpg',
71+
alt: 'Test image'
72+
}))
73+
.attrs({
74+
height: '50'
75+
})`
76+
width: 50;
77+
`
78+
const vm = new Vue(Component).$mount()
79+
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: '50' })
80+
})
81+
82+
it('should access to all previous attribute properties', () => {
83+
const Component = styled('img', {})
84+
.attrs(() => ({
85+
src: 'image',
86+
alt: 'My test image'
87+
}))
88+
.attrs((props) => ({
89+
src: props.src + '.jpg',
90+
height: 5 * 10
91+
}))`
92+
width: 50;
93+
`
94+
const vm = new Vue(Component).$mount()
95+
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'My test image', height: 50 })
96+
})
97+
98+
it('should override attribute properties', () => {
99+
const Component = styled('img', {})
100+
.attrs(() => ({
101+
src: 'image.jpg',
102+
alt: 'Test image',
103+
height: '20'
104+
}))
105+
.attrs({
106+
height: '50'
107+
})`
108+
width: 50;
109+
`
110+
const vm = new Vue(Component).$mount()
111+
expect(vm._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: '50' })
112+
})
113+
114+
it('should access to component props', () => {
115+
const Component = styled('img', { propsHeight: Number })
116+
.attrs((props) => ({
117+
src: 'image.jpg',
118+
alt: 'Test image',
119+
height: props.propsHeight * 2
120+
}))`
121+
width: 50;
122+
`
123+
124+
const vm = new Vue({
125+
render: function (h) {
126+
return h(Component, {
127+
props: {
128+
propsHeight: 20
129+
},
130+
})
131+
}
132+
}).$mount()
133+
134+
expect(vm.$children[0]._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: 40 })
135+
})
136+
137+
it('attributes should be reactive', () => {
138+
const Component = styled('img', { propsHeight: Number })
139+
.attrs((props) => ({
140+
src: 'image.jpg',
141+
alt: 'Test image',
142+
height: props.propsHeight * 2
143+
}))`
144+
width: 50;
145+
`
146+
147+
const vm = new Vue({
148+
render: function (h) {
149+
const self = this
150+
return h(Component, {
151+
props: {
152+
propsHeight: self.dataHeight
153+
},
154+
})
155+
},
156+
data: () => ({
157+
dataHeight: 20
158+
})
159+
}).$mount()
160+
161+
expect(vm.$children[0]._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: 40 })
162+
163+
vm.dataHeight = 90
164+
setTimeout(() => { // $nextTick
165+
expect(vm.$children[0]._vnode.data.domProps).toEqual({ src: 'image.jpg', alt: 'Test image', height: 180 })
166+
}, 0)
167+
})
168+
})

0 commit comments

Comments
 (0)