Skip to content

Commit 1e126b1

Browse files
committed
Add support for @layer
1 parent 6d17e19 commit 1e126b1

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

packages/jss/src/plugins/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import pluginStyleRule, {StyleRule} from './styleRule'
22
import pluginConditionalRule, {ConditionalRule} from './conditionalRule'
33
import pluginKeyframesRule, {KeyframesRule} from './keyframesRule'
44
import pluginKeyframeRule, {KeyframeRule} from './keyframeRule'
5+
import pluginLayerRule, {LayerRule} from './layerRule'
56
import pluginFontFaceRule, {FontFaceRule} from './fontFaceRule'
67
import pluginViewportRule, {ViewportRule} from './viewportRule'
78
import pluginSimpleRule, {SimpleRule} from './simpleRule'
@@ -11,6 +12,7 @@ export const plugins = [
1112
pluginConditionalRule,
1213
pluginKeyframesRule,
1314
pluginKeyframeRule,
15+
pluginLayerRule,
1416
pluginFontFaceRule,
1517
pluginViewportRule,
1618
pluginSimpleRule
@@ -21,6 +23,7 @@ export {
2123
ConditionalRule,
2224
KeyframesRule,
2325
KeyframeRule,
26+
LayerRule,
2427
FontFaceRule,
2528
ViewportRule,
2629
SimpleRule

packages/jss/src/plugins/layerRule.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import RuleList from '../RuleList'
2+
import getWhitespaceSymbols from '../utils/getWhitespaceSymbols'
3+
4+
const defaultToStringOptions = {
5+
indent: 1,
6+
children: true
7+
}
8+
9+
const atRegExp = /@([\w-]+)/
10+
11+
/**
12+
* Rule for @layer
13+
*/
14+
export class LayerRule {
15+
type = 'layer'
16+
17+
isProcessed = false
18+
19+
constructor(key, styles, options) {
20+
this.key = key
21+
const atMatch = key.match(atRegExp)
22+
this.at = atMatch ? atMatch[1] : 'unknown'
23+
// Key might contain a unique suffix in case the `name` passed by user was duplicate.
24+
this.query = options.name || `@${this.at}`
25+
this.options = options
26+
this.rules = new RuleList({...options, parent: this})
27+
28+
for (const name in styles) {
29+
this.rules.add(name, styles[name])
30+
}
31+
32+
this.rules.process()
33+
}
34+
35+
/**
36+
* Get a rule.
37+
*/
38+
getRule(name) {
39+
return this.rules.get(name)
40+
}
41+
42+
/**
43+
* Get index of a rule.
44+
*/
45+
indexOf(rule) {
46+
return this.rules.indexOf(rule)
47+
}
48+
49+
/**
50+
* Create and register rule, run plugins.
51+
*/
52+
addRule(name, style, options) {
53+
const rule = this.rules.add(name, style, options)
54+
if (!rule) return null
55+
this.options.jss.plugins.onProcessRule(rule)
56+
return rule
57+
}
58+
59+
/**
60+
* Replace rule, run plugins.
61+
*/
62+
replaceRule(name, style, options) {
63+
const newRule = this.rules.replace(name, style, options)
64+
if (newRule) this.options.jss.plugins.onProcessRule(newRule)
65+
return newRule
66+
}
67+
68+
/**
69+
* Generates a CSS string.
70+
*/
71+
toString(options = defaultToStringOptions) {
72+
const {linebreak} = getWhitespaceSymbols(options)
73+
if (options.indent == null) options.indent = defaultToStringOptions.indent
74+
if (options.children == null) options.children = defaultToStringOptions.children
75+
if (options.children === false) {
76+
return `${this.query} {}`
77+
}
78+
const children = this.rules.toString(options)
79+
return children ? `${this.query} {${linebreak}${children}${linebreak}}` : ''
80+
}
81+
}
82+
83+
const keyRegExp = /@layer\s+/
84+
85+
export default {
86+
onCreateRule(key, styles, options) {
87+
return keyRegExp.test(key) ? new LayerRule(key, styles, options) : null
88+
}
89+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// @flow
2+
import RuleList from '../RuleList'
3+
import type {
4+
CSSMediaRule,
5+
Rule,
6+
RuleOptions,
7+
ToCssOptions,
8+
JssStyle,
9+
// eslint doesn't understand usage with types
10+
// eslint-disable-next-line no-unused-vars
11+
ContainerRule
12+
} from '../flow-types'
13+
14+
declare export class LayerRule implements ContainerRule {
15+
type: string;
16+
at: string;
17+
key: string;
18+
query: string;
19+
rules: RuleList;
20+
options: RuleOptions;
21+
isProcessed: boolean;
22+
renderable: ?CSSMediaRule;
23+
constructor(key: string, styles: Object, options: RuleOptions): this;
24+
getRule(name: string): Rule;
25+
indexOf(rule: Rule): number;
26+
addRule(name: string, style: JssStyle, options?: RuleOptions): Rule | null;
27+
replaceRule(name: string, style: JssStyle, options?: RuleOptions): Rule | null;
28+
toString(options?: ToCssOptions): string;
29+
}
30+
31+
declare export default {
32+
onCreateRule(key: string, styles: JssStyle, options: RuleOptions): LayerRule | null
33+
}

packages/jss/tests/integration/rules.js

+28
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,34 @@ describe('Integration: rules', () => {
206206
`)
207207
})
208208

209+
describe('@layer rule', () => {
210+
it('should return CSS', () => {
211+
const rule = jss.createRule('@layer test', {
212+
button: {
213+
color: 'red'
214+
}
215+
})
216+
expect(rule.type).to.be('layer')
217+
expect(rule.key).to.be('@layer test')
218+
expect(rule.toString()).to.be(stripIndent`
219+
@layer test {
220+
.button-id {
221+
color: red;
222+
}
223+
}
224+
`)
225+
})
226+
227+
it('should return CSS without empty rule', () => {
228+
const rule = jss.createRule('@layer test', {
229+
button: {}
230+
})
231+
expect(rule.type).to.be('layer')
232+
expect(rule.key).to.be('@layer test')
233+
expect(rule.toString()).to.be('')
234+
})
235+
})
236+
209237
describe('@media rule', () => {
210238
it('should return CSS', () => {
211239
const rule = jss.createRule('@media print', {a: {display: 'none'}})

0 commit comments

Comments
 (0)