bem-constructor 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8d21417bbbbf72744f4f58dbf75f61313f4f0c0f
4
+ data.tar.gz: 4e6598835ffaaac7ed06cb3cfea6d8b9f14b0e7c
5
+ SHA512:
6
+ metadata.gz: b6ea8f0194709514cf397580c63858d5c5a4ea54e2e2d9f3db9de0f049db8dcf1df6399267aa19a96020d6b022a16aafbe9da568d45748bf04fc5c2a0e9e988d
7
+ data.tar.gz: 6e1f86e490a5d18ece7bfc4d9f68f9402b42de7f6595220fb0623f66daac14350a7c09db82f1ebceb2458c241f894d7b6063dc7bc9f512f0c223c519a382ddb5
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ * Initial release
data/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # BEM Constructor
2
+
3
+ BEM Constructor is a Sass library for building immutable and namespaced BEM-style CSS objects.
4
+
5
+ By enforcing a consistent and programatic way of defining objects (blocks, elements and modifiers) it ensures a more structured, robust and secure object codebase that is easy to understand and maintain. Objects defined using the constructor are impossible to modify and reassign by mistake or omission.
6
+
7
+ Jump to [🍔 The Burger Example™](#example) to see the mixins in action.
8
+
9
+ ----
10
+
11
+ ## Key ideas
12
+
13
+ The key ideas behind this library are well explained by Harry Roberts in his articles [Immutable CSS](http://csswizardry.com/2015/03/immutable-css/), [More Transparent UI Code with Namespaces](http://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/) and [MindBEMding – getting your head ’round BEM syntax](http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/),
14
+
15
+ ### 1. Immutability
16
+
17
+ Some CSS objects in your project shouldn't be able to change (mutate). They have a very specific role and you need to make sure they're not reassigned somewhere else in your codebase. In order to ensure immutability you'll need three things: a way of defining those objects, a way of recognising them and a way to guarantee you won't be able to modify them later on. By constructing objects programatically you can be confident that they are assigned once and just once.
18
+
19
+ ### 2. Namespacing
20
+
21
+ Objects have a clear function. Whethere they are components, utilities o dirty hacks, we need a consistent way of telling them apart. By namespacing objects, our UI code becomes more transparent and understandable. BEM Constructor supports the following object types:
22
+
23
+ - Objects
24
+ - Components
25
+ - Utilities
26
+ - Themes
27
+ - States
28
+ - Scopes
29
+ - Hacks
30
+
31
+ Read [Harry's post on namespaces](http://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/) to get a deep look at why and how they are used.
32
+
33
+
34
+ ### 3. BEM structure
35
+
36
+ BEM objects are composed of a block and any number of elements and/or modifiers. Using the BEM syntax for naming classes you'll produce structured code that helps you and other developers understand at a glance the relationship between those classes. The BEM constructor takes care of generating bem-compliant selectors.
37
+
38
+ ----
39
+
40
+ ## Installation
41
+
42
+ There are 3 ways of installing BEM Constructor:
43
+
44
+ ### Download
45
+
46
+ Download the Sass files in [stylesheets/](/stylesheets/) and place them in your Sass directory.
47
+
48
+ ### Bower
49
+
50
+ Run the following command:
51
+
52
+ bower install --save-dev bem-constructor
53
+
54
+ ### Compass extension
55
+
56
+ 1. `gem install bem-constructor`
57
+ 2. Add `require 'bem-constructor'` to your `config.rb`
58
+
59
+ ----
60
+
61
+ ## Usage
62
+
63
+ Import it into your main stylesheet:
64
+
65
+ @import 'bem-constructor';
66
+
67
+ ### block($name, $type)
68
+
69
+ Constructs a new block element of a given type. `$type` being one of: `'object'`, `'component'` or `'utility'`.
70
+
71
+ @include block($name, $type) { ... }
72
+
73
+
74
+ #### object($name)
75
+
76
+ A shortcut for `block($name, $type: 'object')`
77
+
78
+ #### component($name)
79
+
80
+ A shortcut for `block($name, $type: 'component')`
81
+
82
+ #### utility($name)
83
+
84
+ A shortcut for `block($name, $type: 'utility')`
85
+
86
+
87
+ ### element($name...)
88
+
89
+ Creates a new element of the parent block. It should always be nested within a block constructor. You can create multiple elements at once by passing a comma separated list (Arglist) of element names.
90
+
91
+ @include element($name...) { ... }
92
+
93
+
94
+ ### modifier($name...)
95
+
96
+ Creates a new modifier of the parent block or element. Should always be nested within a block or element constructor. You can declare multiple modifiers at once by passing a comma separated list (Arglist) of modifier names.
97
+
98
+ @include modifier($name...) { ... }
99
+
100
+
101
+ ### modifies-element($modified-elements...)
102
+
103
+ When declaring a block modifier, a state you may need to target and modify some of the block elements too. Use the following mixin to scope the ruleset to those elements.
104
+
105
+ @include modifies-element($modified-elements...) { ... }
106
+
107
+
108
+ ### theme($themes...)
109
+
110
+ Style your objects given a parent theme class.
111
+
112
+ @include theme($themes...) { ... }
113
+
114
+ ### state($states...)
115
+
116
+ Modifies objects by appending a state class
117
+
118
+ @include state($states...) { ... }
119
+
120
+ ### hack()
121
+
122
+ Signals that the following code is a hack.
123
+
124
+ @include hack() { ... }
125
+
126
+
127
+ ### scope($name)
128
+
129
+ Creates a new scope
130
+
131
+ Scopes allow you to isolate code you don't have control over.
132
+
133
+ @include scope($name) { ... }
134
+
135
+
136
+ ----
137
+
138
+ ## Options
139
+
140
+ ### Namespaces
141
+
142
+ Switch namespaces on/off by setting the following variable:
143
+
144
+ $bem-use-namespaces: false; // defaults to true
145
+
146
+ Override the default block namespaces:
147
+
148
+ $bem-block-namespaces: (
149
+ 'object': 'obj', // defaults to 'o'
150
+ 'component': 'comp', // defaults to 'c'
151
+ 'utility': 'helper', // defaults to 'u'
152
+ );
153
+
154
+ Override the default theme namespace:
155
+
156
+ $bem-theme-namespace: 'theme'; // defaults to 't'
157
+
158
+ Override the default state namespace:
159
+
160
+ $bem-state-namespace: 'has'; // defaults to 'is'
161
+
162
+ Override the default hack namespace:
163
+
164
+ $bem-hack-namespace: 'it-wasnt-me-'; // defaults to '_'
165
+
166
+
167
+
168
+ ### BEM separators
169
+
170
+ By default BEM Constructor uses the following BEM convention:
171
+ - Two underscores (__) for elements
172
+ - Two hyphens for modifiers (--).
173
+
174
+ You can customize them to whatever fits you needs:
175
+
176
+ $bem-element-separator: '-'; // Defaults to '__'
177
+
178
+ $bem-modifier-separator: '-_-_'; // Defaults to '--'
179
+
180
+
181
+
182
+ ----
183
+
184
+ ##<a name="example"></a> 🍔 The Burger Example™
185
+
186
+
187
+ *Disclaimer: the following Sass code may not compile into a real burger.*
188
+
189
+ ```` scss
190
+
191
+ @include object('burger') {
192
+ texture: juicy;
193
+
194
+ @include element('lettuce', 'tomato') {
195
+ quality: fresh;
196
+ }
197
+
198
+ @include element('cheese') {
199
+ type: gouda;
200
+
201
+ @include modifier('parmigiano') {
202
+ type: parmigiano;
203
+ }
204
+ }
205
+
206
+ @include element('extra-topping') {
207
+ ingredient: bacon;
208
+ }
209
+
210
+ @include element('meat') {
211
+ type: beef;
212
+ }
213
+
214
+ @include modifier('veggie') {
215
+ texture: smooth;
216
+
217
+ @include modifies-element('meat') {
218
+ type: lentils;
219
+ }
220
+
221
+ @include modifies-element('extra-topping') {
222
+ ingredient: avocado;
223
+
224
+ @include hack() {
225
+ ingredient: bacon;
226
+ }
227
+ }
228
+ }
229
+
230
+ @include theme('mexican') {
231
+ spicy: hell-yeah;
232
+ }
233
+
234
+ @include state('cold') {
235
+ taste: terrible;
236
+ }
237
+ }
238
+
239
+ The compiled CSS:
240
+
241
+ ```` css
242
+
243
+ /* The main Burger object */
244
+ .o-burger { texture: juicy; }
245
+
246
+ /* lettuce and Tomato elements */
247
+ .o-burger__lettuce, .o-burger__tomato { quality: fresh; }
248
+
249
+ /* Cheese element */
250
+ .o-burger__cheese { type: gouda; }
251
+
252
+ /* Cheese modifier */
253
+ .o-burger__cheese--parmigiano { type: parmigiano; }
254
+
255
+ /* Extra topping element */
256
+ .o-burger__extra-topping { ingredient: bacon; }
257
+
258
+ /* Meat element */
259
+ .o-burger__meat { type: beef; }
260
+
261
+ /* Veggie Burger block modifier */
262
+ .o-burger--veggie { texture: smooth; }
263
+
264
+ /* Veggie Burger block modifier modifies the Meat element too */
265
+ .o-burger--veggie .o-burger__meat { type: lentils; }
266
+
267
+ /* Veggie Burger block modifier modifies the Extra Topping element too */
268
+ .o-burger--veggie .o-burger__extra-topping { ingredient: avocado; }
269
+
270
+ /* But we are hackers and couldn't resist adding some Bacon back */
271
+ ._o-burger--veggie .o-burger__extra-topping { ingredient: bacon; }
272
+
273
+ /* When the party Theme is Mexican, we make everything spicy */
274
+ .t-mexican .o-burger { spicy: hell-yeah; }
275
+
276
+ /* And we're all sad when the burge Is Cold */
277
+ .o-burger.is-cold { taste: terrible; }
278
+
279
+
280
+ ## This is overkill, who is this for?
281
+
282
+ If constructing objects programatically seems too verbose or abstract to you that's perfectly OK. This tool is not for everybody. However if you need to enforce a strict way of writing BEM objects in your project, want to make sure they won't mutate and thus produce more secure CSS, then this tool might help you.
@@ -0,0 +1,8 @@
1
+ require 'compass'
2
+ extension_path = File.expand_path(File.join(File.dirname(__FILE__), ".."))
3
+ Compass::Frameworks.register('bem-constructor', :path => extension_path)
4
+
5
+ module QuantityQueries
6
+ VERSION = "0.1"
7
+ DATE = "2015-03-24"
8
+ end
@@ -0,0 +1,37 @@
1
+ // -----------------------------------------------------------------------------
2
+ // BEM constructor
3
+ // -----------------------------------------------------------------------------
4
+ // Table of contents
5
+ // 1. Default settings
6
+ // 2. Imports
7
+
8
+ // -----------------------------------------------------------------------------
9
+ // 1. Default settings
10
+ // -----------------------------------------------------------------------------
11
+
12
+ /// Use namespaced class names
13
+ /// @public
14
+ $bem-use-namespaces: true !default;
15
+
16
+ /// Bem style element separator
17
+ /// @public
18
+ $bem-element-separator: '__' !default;
19
+
20
+ /// Bem style modifier separator
21
+ /// @public
22
+ $bem-modifier-separator: '--' !default;
23
+
24
+ // -----------------------------------------------------------------------------
25
+ // 2. Imports
26
+ // -----------------------------------------------------------------------------
27
+
28
+ @import 'logger';
29
+ @import 'error-checks';
30
+ @import 'block';
31
+ @import 'element';
32
+ @import 'modifier';
33
+ @import 'modifies-element';
34
+ @import 'scope';
35
+ @import 'theme';
36
+ @import 'state';
37
+ @import 'hack';
@@ -0,0 +1,91 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Block constructor
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Set namespaces for each block type
6
+ /// @public
7
+
8
+ $bem-block-namespaces: (
9
+ 'utility': 'u',
10
+ 'object': 'o',
11
+ 'component': 'c',
12
+ ) !default;
13
+
14
+ /// Initializes a new block object
15
+ /// @private
16
+ /// @param {String} $block - Name for the new block
17
+ /// @param {String} $type - Block type: (utility, object or component)
18
+ /// @returns The final selector for the new block object
19
+
20
+ @function _block($name, $type) {
21
+
22
+ // Log new block
23
+ $new-block: _bem-log-block($name);
24
+
25
+ // Error check
26
+ $outside-check: should-not-be-called-within('scope', 'block');
27
+
28
+ // Set namespace
29
+ $namespace: '';
30
+
31
+ @if $bem-use-namespaces {
32
+ @if not map-has-key($bem-block-namespaces, $type) {
33
+ @error '`#{$type}` is not a valid `$type` for `block()`';
34
+ }
35
+ $namespace: map-get($bem-block-namespaces, $type) + '-';
36
+ }
37
+
38
+ $selector: '.' + $namespace + $name;
39
+ $set-current: set-current-context('block', $name, $selector);
40
+
41
+ @return $selector;
42
+ }
43
+
44
+
45
+ /// Creates a block object with the given type
46
+ /// @param {String} $block - Name for the new block
47
+ /// @param {String} $type - Block type: (utility, object or component)
48
+
49
+ @mixin block($name, $type) {
50
+
51
+ // Write block selector
52
+ @at-root #{_block($name, $type)} {
53
+ @content;
54
+ }
55
+
56
+ // Clear $_bem-current-context block after creation
57
+ $unset-current: unset-current-context('block');
58
+ }
59
+
60
+
61
+ // -----------------------------------------------------------------------------
62
+ // 2. Utility alias
63
+ // -----------------------------------------------------------------------------
64
+
65
+ @mixin utility($name) {
66
+ @include block($name, 'utility') {
67
+ @content;
68
+ }
69
+ }
70
+
71
+
72
+ // -----------------------------------------------------------------------------
73
+ // 3. Object alias
74
+ // -----------------------------------------------------------------------------
75
+
76
+ @mixin object($name) {
77
+ @include block($name, 'object') {
78
+ @content;
79
+ }
80
+ }
81
+
82
+
83
+ // -----------------------------------------------------------------------------
84
+ // 4. Component alias
85
+ // -----------------------------------------------------------------------------
86
+
87
+ @mixin component($name) {
88
+ @include block($name, 'component') {
89
+ @content;
90
+ }
91
+ }
@@ -0,0 +1,43 @@
1
+ // ----------------------------------------------------------------------
2
+ // Element constructor
3
+ // ----------------------------------------------------------------------
4
+
5
+ /// Initializes a new element for the current block
6
+ /// @private
7
+ /// @param {String | Arglist} $elements - List of new element names
8
+ /// @returns The final selector for the new element(s)
9
+
10
+ @function _element($elements...) {
11
+
12
+ // Log new element(s)
13
+ $new-element: _bem-log-element($elements...);
14
+
15
+ // Error checks
16
+ $inside-check: should-be-called-within('block');
17
+ $outside-check: should-not-be-called-within('modifier', 'state', 'element');
18
+
19
+ $selector: ();
20
+
21
+ @each $element in $elements {
22
+ $e: #{&}#{$bem-element-separator}#{$element};
23
+ $selector: append($selector, $e, 'comma');
24
+ }
25
+
26
+ $set-current: set-current-context('element', $elements, $selector);
27
+
28
+ @return $selector;
29
+ }
30
+
31
+
32
+ /// Creates new element(s)
33
+ /// @param {String | Arglist} $elements - Name of the new element(s)
34
+
35
+ @mixin element($elements...) {
36
+
37
+ @at-root #{_element($elements...)} {
38
+ @content;
39
+ }
40
+
41
+ // Clear $_bem-current-context element after creation
42
+ $unset-current: unset-current-context('element');
43
+ }
@@ -0,0 +1,50 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Error checks
3
+ // -----------------------------------------------------------------------------
4
+ // Table of contents:
5
+ // 1. Within
6
+ // 2. Outside
7
+
8
+ // -----------------------------------------------------------------------------
9
+ // 1. Within
10
+ // -----------------------------------------------------------------------------
11
+
12
+ /// Checks that it's being created within all of the passed $objs...
13
+ @function _should-be-called-within($objs...) {
14
+
15
+ $found: false;
16
+
17
+ @each $obj in $objs {
18
+ @if map-get($_bem-current-context, $obj) != null {
19
+ $found: true;
20
+ }
21
+ }
22
+
23
+ @if not $found {
24
+ @error 'It should be called within #{inspect($objs)}';
25
+ }
26
+
27
+ @return true;
28
+ }
29
+
30
+
31
+ // -----------------------------------------------------------------------------
32
+ // 2. Outside
33
+ // -----------------------------------------------------------------------------
34
+
35
+ /// Checks that it's being created outside all of the passed $objs...
36
+ @function _should-not-be-called-within($objs...) {
37
+
38
+ $found: false;
39
+
40
+ @each $obj in $objs {
41
+ @if map-get($_bem-current-context, $obj) != null {
42
+ $found: true;
43
+ }
44
+ }
45
+ @if $found {
46
+ @error 'It should not be called within #{inspect($objs)}';
47
+ }
48
+
49
+ @return true;
50
+ }
@@ -0,0 +1,38 @@
1
+ // -----------------------------------------------------------------------------
2
+ // 11. Hack constructor
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Hack namespace prepended to the selector
6
+ $hack-namespace: '_' !default;
7
+
8
+ @function _hack() {
9
+
10
+ // You may not hack a hack
11
+ $recursive-check: should-not-be-called-recursively('hack');
12
+
13
+ $selector: ();
14
+ $namespace: if($bem-use-namespaces, $hack-namespace, '');
15
+
16
+ @each $s in & {
17
+ $selector-to-str: inspect(nth($s, 1));
18
+ $selector-without-dot: str-slice($selector-to-str, 2, -1);
19
+ $new-selector: '.' + $namespace + $selector-without-dot;
20
+ $sl: selector-replace($s, nth($s, 1), $new-selector);;
21
+ $selector: append($selector, $sl, 'comma');
22
+ }
23
+
24
+ $set-current: set-current-context('hack', 'some-hack', $selector);
25
+
26
+ @return $selector;
27
+
28
+ }
29
+
30
+ @mixin hack() {
31
+
32
+ @at-root #{_hack()} {
33
+ @content;
34
+ }
35
+
36
+ $unset-current: unset-current-context('hack');
37
+
38
+ }
@@ -0,0 +1,7 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Logger
3
+ // -----------------------------------------------------------------------------
4
+
5
+ @import '_block';
6
+ @import '_element';
7
+ @import '_modifier';
@@ -0,0 +1,12 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Logger
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Stores the whole BEM structure
6
+ $_bem-log: () !global;
7
+
8
+ @import 'logger/_context-logger';
9
+ @import 'logger/_block-logger';
10
+ @import 'logger/_element-logger';
11
+ @import 'logger/_modifier-logger';
12
+ @import 'logger/_scope-logger';
@@ -0,0 +1,49 @@
1
+ // ----------------------------------------------------------------------
2
+ // Modifier constructor
3
+ // ----------------------------------------------------------------------
4
+
5
+ /// Initializes a new modifier for the current block or element(s)
6
+ /// @private
7
+ /// @param {String | Arglist} $modifiers - List of new modifier names
8
+ /// @returns The final selector for the new modifier(s)
9
+
10
+
11
+ @function _modifier($modifiers...) {
12
+
13
+ // Log new modifier(s)
14
+ $new-modifier: _bem-log-modifier($modifiers...);
15
+
16
+ // Error checks
17
+ $inside-check: _should-be-called-within('block');
18
+ $outside-check: _should-not-be-called-within('modifier');
19
+
20
+ $selector: ();
21
+
22
+ @each $modifier in $modifiers {
23
+ $new-selector: ();
24
+
25
+ @each $sel in & {
26
+ $modified-selector: #{$sel}#{$bem-modifier-separator}#{$modifier};
27
+ $new-selector: append($new-selector, $modified-selector, 'comma');
28
+ }
29
+
30
+ $selector: append($selector, $new-selector, 'comma');
31
+ }
32
+
33
+ $set-current: set-current-context('modifier', $modifiers, $selector);
34
+
35
+ @return $selector;
36
+ }
37
+
38
+
39
+ /// Creates new modifier(s)
40
+ /// @param {String | Arglist} $modifiers - Name of the new modifier(s)
41
+
42
+ @mixin modifier($modifiers...) {
43
+
44
+ @at-root #{_modifier($modifiers...)} {
45
+ @content;
46
+ }
47
+
48
+ $unset-current: unset-current-context('modifier');
49
+ }
@@ -0,0 +1,37 @@
1
+ // ----------------------------------------------------------------------
2
+ // Element modifier
3
+ // ----------------------------------------------------------------------
4
+
5
+ /// Scopes the @content ruleset to an element of the block being modified
6
+ /// @private
7
+ /// @param {String | Arglist} $modified-elements - List of elements that should be modified
8
+ /// @returns The final selector for the element(s) modified by the block modifier
9
+
10
+ @function _modifies-element($modified-element...) {
11
+
12
+ $inside-check: should-be-called-within('modifier', 'state');
13
+ $outside-check: should-not-be-called-within('element');
14
+
15
+ $selectors: ();
16
+
17
+ @each $element in $modified-element {
18
+ $element: map-get(map-get($_bem-current-context, 'block'), 'selector') + $bem-element-separator + $element;
19
+ $selectors: append($selectors, $element, 'comma');
20
+ }
21
+
22
+ $selector: selector-nest(&, $selectors);
23
+
24
+ $set-current: set-current-context('modifies-element', $modified-element, $selector);
25
+
26
+ @return $selector;
27
+ }
28
+
29
+
30
+ /// Scopes the @content ruleset to an element of the block being modified
31
+ /// @param {String | Arglist} $modified-elements - Name of the element(s) that should be modified
32
+
33
+ @mixin modifies-element($modified-elements...) {
34
+ @at-root #{_modifies-element($modified-elements...)} {
35
+ @content;
36
+ }
37
+ }
@@ -0,0 +1,40 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Scope constructor
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Set namespace for scopes
6
+ /// @public
7
+
8
+ $bem-scope-namespace: 's';
9
+
10
+ /// Initializes a new scope object
11
+ /// @private
12
+ /// @param {String} $scope - Name for the new scope
13
+ /// @returns The final selector for the new scope object
14
+
15
+ @function _scope($scope) {
16
+
17
+ // Log new block
18
+ $new-scope: _bem-log-scope($scope);
19
+
20
+ // Check it's not called inside another scope
21
+ // and it's called at the root-level
22
+ $recursive-check: should-not-be-called-recursively('scope');
23
+ $outside-check: should-not-be-called-within('block');
24
+
25
+ $namespace: if($bem-use-namespaces, $bem-scope-namespace + '-', '');
26
+ $selector: '.' + $namespace + $scope;
27
+
28
+ $set-current: set-current-context('scope', $scope, $selector);
29
+
30
+ @return $selector;
31
+ }
32
+
33
+ @mixin scope($scope) {
34
+
35
+ @at-root #{_scope($scope)} {
36
+ @content;
37
+ }
38
+
39
+ $unset-current: unset-current-context('scope');
40
+ }
@@ -0,0 +1,28 @@
1
+ // -----------------------------------------------------------------------------
2
+ // State constructor
3
+ // -----------------------------------------------------------------------------
4
+
5
+ $state-namespace: 'is' !default;
6
+
7
+ @function _state($states...) {
8
+ $selector: ();
9
+ $namespace: if($bem-use-namespaces, $state-namespace + '-', '');
10
+
11
+ @each $state in $states {
12
+ $s: selector-append(&, '.#{$namespace}#{$state}');
13
+ $selector: append($selector, $s, 'comma');
14
+ }
15
+
16
+ $set-current: set-current-context('state', $states, $selector);
17
+
18
+ @return $selector;
19
+ }
20
+
21
+ @mixin state($states...) {
22
+
23
+ @at-root #{_state($states...)} {
24
+ @content;
25
+ }
26
+
27
+ $unset-state: unset-current-context('state');
28
+ }
@@ -0,0 +1,33 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Theme constructor
3
+ // -----------------------------------------------------------------------------
4
+
5
+ $bem-theme-namespace: 't' !default;
6
+
7
+ @function _theme($themes...) {
8
+
9
+ // If you try to hack a hack you can break the internet.
10
+ // So please, no one try it.
11
+ $recursive-test: should-not-be-called-recursively('theme');
12
+
13
+ $selector: ();
14
+ $namespace: if($bem-use-namespaces, $bem-theme-namespace + '-', '');
15
+
16
+ @each $theme in $themes {
17
+ $t: selector-nest('.#{$namespace}#{$theme}', &);
18
+ $selector: append($selector, $t, 'comma');
19
+ }
20
+
21
+ $set-current: set-current-context('theme', $themes, $selector);
22
+
23
+ @return $selector;
24
+ }
25
+
26
+ @mixin theme($themes...) {
27
+
28
+ @at-root #{_theme($themes...)} {
29
+ @content;
30
+ }
31
+
32
+ $unset-current: unset-current-context('theme');
33
+ }
@@ -0,0 +1,30 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Block Logger
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Find if a given $block has already been created
6
+ /// @param {String} $block - Name of the block
7
+
8
+ @function block-exists($block) {
9
+ @return map-has-key($_bem-log, $block);
10
+ }
11
+
12
+ /// Log the new $block
13
+ /// @param {String} $block - Block name
14
+
15
+ @function _bem-log-block($block) {
16
+
17
+ // Check if the block has already been created
18
+ @if block-exists($block) {
19
+ @error '`#{$block}` block has already been created';
20
+ }
21
+
22
+ // Initialize a new block map
23
+ $new-block: ($block: ('elements': (), 'modifiers': ()));
24
+
25
+ // Update bem log with new block
26
+ $_bem-log: map-merge($_bem-log, $new-block) !global;
27
+
28
+ // Everything OK
29
+ @return true;
30
+ }
@@ -0,0 +1,40 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Context logger
3
+ // -----------------------------------------------------------------------------
4
+ // Table of contents:
5
+ // 1. Store current context
6
+ // 2. Clear current context
7
+
8
+
9
+ /// Used to stores the current object being constructed
10
+ /// @private
11
+
12
+ $_bem-current-context: () !global;
13
+
14
+
15
+ // -----------------------------------------------------------------------------
16
+ // 1. Store current context
17
+ // -----------------------------------------------------------------------------
18
+
19
+ /// Sets the current object, stores name and generated selector
20
+
21
+ @function set-current-context($obj, $name, $selector) {
22
+ $new-current: (#{$obj}: (name: $name, selector: $selector));
23
+ $_bem-current-context: map-merge($_bem-current-context, $new-current) !global;
24
+
25
+ @return $selector;
26
+ }
27
+
28
+
29
+ // -----------------------------------------------------------------------------
30
+ // 2. Clear current context
31
+ // -----------------------------------------------------------------------------
32
+
33
+ /// Clears the current object
34
+
35
+ @function unset-current-context($obj) {
36
+ $new-current: (#{$obj}: null);
37
+ $_bem-current-context: map-merge($_bem-current-context, $new-current) !global;
38
+
39
+ @return null;
40
+ }
@@ -0,0 +1,59 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Element Logger
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Find if the given $elements have already been created
6
+ /// @param {Arglist | String} $elements - A single or multiple element names
7
+
8
+ @function element-exists($elements...) {
9
+
10
+ // Get the current block name
11
+ // Then get the current block map
12
+ // Then get the current block element map
13
+ $current-block-name: map-get(map-get($_bem-current-context, 'block'), 'name');
14
+ $current-block: map-get($_bem-log, $current-block-name);
15
+ $current-elements: map-get($current-block, 'elements');
16
+
17
+ @each $element in $elements {
18
+ @if map-has-key($current-elements, $element) {
19
+ @return true;
20
+ }
21
+ }
22
+
23
+ @return false;
24
+ }
25
+
26
+
27
+ /// Log the new $elements
28
+ /// @param {Arglist | String} $elements - A single or multiple element names
29
+
30
+ @function _bem-log-element($elements...) {
31
+
32
+ // Check any $elements has already been defined for the current block
33
+ @if element-exists($elements...) {
34
+ @error 'One or more elements from `#{inspect($elements)}` have already been created';
35
+ }
36
+
37
+ // Find the current block name
38
+ // Then get the map for the current block
39
+ // Then get the element list
40
+ $current-block-name: map-get(map-get($_bem-current-context, 'block'), 'name');
41
+ $current-block: map-get($_bem-log, $current-block-name);
42
+ $current-elements: map-get($current-block, 'elements');
43
+
44
+ // For each possible name in $name
45
+ // Create an updated block map
46
+ // Add it to the list of elements
47
+ @each $element in $elements {
48
+ $updated: ($element: ('modifiers': ()));
49
+ $current-elements: map-merge($current-elements, $updated);
50
+ }
51
+
52
+ // Update the block
53
+ $updated-block: ($current-block-name: ('elements': ($current-elements), 'modifiers': map-get($current-block, 'modifiers')));
54
+
55
+ // Update the log
56
+ $_bem-log: map-merge($_bem-log, $updated-block) !global;
57
+
58
+ @return true;
59
+ }
@@ -0,0 +1,94 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Modifier Logger
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Find if the given $modifiers have already been created
6
+ /// @param {Arglist | String} $modifiers - A single or multiple modifier names
7
+
8
+ @function modifier-exists($modifiers...) {
9
+
10
+ // Get the current block name
11
+ // Then get the current block map
12
+ // Then get the current block modifiers map
13
+ $current-block-name: map-get(map-get($_bem-current-context, 'block'), 'name');
14
+ $current-block: map-get($_bem-log, $current-block-name);
15
+ $current-modifiers: map-get($current-block, 'modifiers');
16
+
17
+ @each $modifier in $modifiers {
18
+ @if map-has-key($current-modifiers, $modifier) {
19
+ @return true;
20
+ }
21
+ }
22
+
23
+ @return false;
24
+ }
25
+
26
+ /// Log the new $modifiers
27
+ /// @param {Arglist | String} $modifiers - A single or multiple modifier names
28
+
29
+
30
+ @function _bem-log-modifier($modifiers...) {
31
+
32
+ // Check if the modifier has already been defined for the current block or element
33
+ @if modifier-exists($modifiers...) {
34
+ @error 'One or more elements from `#{inspect($modifiers)}` have already been created';
35
+ }
36
+
37
+ // Find the current block name
38
+ $current-block-name: map-get(map-get($_bem-current-context, 'block'), 'name');
39
+ $current-item-name: $current-block-name;
40
+
41
+ // Get the map for the current block
42
+ $current-block: map-get($_bem-log, $current-block-name);
43
+ $current-item: $current-block;
44
+
45
+ // Get the map for the current block modifiers
46
+ $current-block-modifiers: map-get($current-block, 'modifiers');
47
+ $current-item-modifiers: $current-block-modifiers;
48
+
49
+ // Check whether the current context is a block or an element
50
+ $context-type: if(map-get($_bem-current-context, 'element') == null, 'block', 'element');
51
+
52
+ // Update item modifier list if within an Element
53
+ @if $context-type == 'element' {
54
+ // @todo: should work if there are multiple current items
55
+ $current-item-name: nth(map-get(map-get($_bem-current-context, 'element'), 'name'),1);
56
+ $current-item: map-get(map-get($current-block, 'elements'), $current-item-name);
57
+ $current-item-modifiers: map-get($current-item, 'modifiers');
58
+ }
59
+
60
+ // For each possible name in $name
61
+ @each $modifier in $modifiers {
62
+
63
+ // Create an updated block/element map
64
+ $updated: ();
65
+
66
+ @if $context-type == 'element' {
67
+ $updated: (#{$modifier}: ('modified-by': ()))
68
+ } @else {
69
+ $modifies-element: map-get($_bem-current-context, 'modifies-element');
70
+ $updated: (#{$modifier}: ('modifies-element': ()));
71
+ }
72
+
73
+ // Add it to the list of modifiers
74
+ $current-item-modifiers: map-merge($current-item-modifiers, $updated);
75
+ }
76
+
77
+ $updated-block: ();
78
+
79
+ @if $context-type == 'element' {
80
+ // update the element map;
81
+ $updated-item: (#{$current-item-name}: ('modifiers': $current-item-modifiers));
82
+ // @error $updated-item;
83
+ $updated-elements: map-merge(map-get($current-block, 'elements'), $updated-item);
84
+ $updated-block: (#{$current-block-name}: ('modifiers': map-get($current-block, 'modifiers'), 'elements': $updated-elements));
85
+ } @else {
86
+ $updated-block: (#{$current-block-name}: ('modifiers': ($current-item-modifiers), 'elements': map-get($current-block, 'elements')));;
87
+ }
88
+
89
+ // // Update the log
90
+ $_bem-log: map-merge($_bem-log, $updated-block) !global;
91
+
92
+ @return true;
93
+
94
+ }
@@ -0,0 +1,30 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Scope Logger
3
+ // -----------------------------------------------------------------------------
4
+
5
+ /// Find if a given $scope has already been created
6
+ /// @param {String} $scope - Name of the scope
7
+
8
+ @function scope-exists($scope) {
9
+ @return map-has-key($_bem-log, $scope);
10
+ }
11
+
12
+ /// Log the new $scope
13
+ /// @param {String} $scope - scope name
14
+
15
+ @function _bem-log-scope($scope) {
16
+
17
+ // Check if the scope has already been created
18
+ @if scope-exists($scope) {
19
+ @error '`#{$scope}` scope has already been created';
20
+ }
21
+
22
+ // Initialize a new scope map
23
+ $new-scope: ($scope: ());
24
+
25
+ // Update bem log with new scope
26
+ $_bem-log: map-merge($_bem-log, $new-scope) !global;
27
+
28
+ // Everything OK
29
+ @return true;
30
+ }
@@ -0,0 +1,26 @@
1
+ .o-burger {
2
+ texture: juicy; }
3
+ .o-burger__letuce, .o-burger__tomato {
4
+ quality: fresh; }
5
+ .o-burger__cheese {
6
+ type: gouda; }
7
+ .o-burger__cheese--parmigiano {
8
+ type: parmigiano; }
9
+ .o-burger__extra-topping {
10
+ ingredient: bacon; }
11
+ .o-burger__meat {
12
+ type: beef; }
13
+ .o-burger--veggie {
14
+ texture: smooth; }
15
+ .o-burger--veggie .o-burger__meat {
16
+ type: lentils; }
17
+ .o-burger--veggie .o-burger__extra-topping {
18
+ ingredient: avocado; }
19
+ ._o-burger--veggie .o-burger__extra-topping {
20
+ ingredient: bacon; }
21
+ .t-mexican .o-burger {
22
+ spicy: hell-yeah; }
23
+ .o-burger.is-cold {
24
+ taste: terrible; }
25
+
26
+ /*# sourceMappingURL=test.css.map */
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "mappings": "AAmDa,SAAwB;EC9CjC,OAAO,EAAE,KAAK;EC+BL,oCAA0B;ID5B/B,OAAO,EAAE,KAAK;EC4BT,iBAA0B;IDxB/B,IAAI,EAAE,KAAK;IE+BN,6BAA4B;MF5B7B,IAAI,EAAE,UAAU;ECqBf,wBAA0B;IDhB/B,UAAU,EAAE,KAAK;ECgBZ,eAA0B;IDZ/B,IAAI,EAAE,IAAI;EEmBL,iBAA4B;IFfjC,OAAO,EAAE,MAAM;IGKV,iCAA4C;MHF7C,IAAI,EAAE,OAAO;IGEZ,0CAA4C;MHE7C,UAAU,EAAE,OAAO;MIJlB,2CAAW;QJOR,UAAU,EAAE,KAAK;EKXpB,oBAAsB;ILiB3B,KAAK,EAAE,SAAS;EMtBX,iBAAsB;IN0B3B,KAAK,EAAE,QAAQ",
4
+ "sources": ["_block.scss","test.scss","_element.scss","_modifier.scss","_modifies-element.scss","_hack.scss","_theme.scss","_state.scss"],
5
+ "names": [],
6
+ "file": "test.css"
7
+ }
@@ -0,0 +1,59 @@
1
+ @import 'bem-constructor';
2
+
3
+ // The Burger Test™
4
+
5
+ @include object('burger') {
6
+ texture: juicy;
7
+
8
+ @include element('letuce', 'tomato') {
9
+ quality: fresh;
10
+ }
11
+
12
+ @include element('cheese') {
13
+ type: gouda;
14
+
15
+ @include modifier('parmigiano') {
16
+ type: parmigiano;
17
+ }
18
+ }
19
+
20
+ @include element('extra-topping') {
21
+ ingredient: bacon;
22
+ }
23
+
24
+ @include element('meat') {
25
+ type: beef;
26
+ }
27
+
28
+ @include modifier('veggie') {
29
+ texture: smooth;
30
+
31
+ @include modifies-element('meat') {
32
+ type: lentils;
33
+ }
34
+
35
+ @include modifies-element('extra-topping') {
36
+ ingredient: avocado;
37
+
38
+ @include hack() {
39
+ ingredient: bacon;
40
+ }
41
+ }
42
+ }
43
+
44
+ @include theme('mexican') {
45
+ spicy: hell-yeah;
46
+ }
47
+
48
+ @include state('cold') {
49
+ taste: terrible;
50
+ }
51
+ }
52
+
53
+ // @include scope(test) {
54
+ // a: shite
55
+ // }
56
+
57
+ // .bem-log {
58
+ // log: inspect($_bem-log);
59
+ // }
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bem-constructor
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Guillan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sass
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: compass
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ description: A Sass library for building immutable and namespaced BEM-style CSS objects
42
+ email:
43
+ - daniel.guillan@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - README.md
50
+ - lib/bem-constructor.rb
51
+ - stylesheets/_bem-constructor.scss
52
+ - stylesheets/_block.scss
53
+ - stylesheets/_element.scss
54
+ - stylesheets/_error-checks.scss
55
+ - stylesheets/_hack.scss
56
+ - stylesheets/_index.scss
57
+ - stylesheets/_logger.scss
58
+ - stylesheets/_modifier.scss
59
+ - stylesheets/_modifies-element.scss
60
+ - stylesheets/_scope.scss
61
+ - stylesheets/_state.scss
62
+ - stylesheets/_theme.scss
63
+ - stylesheets/logger/_block-logger.scss
64
+ - stylesheets/logger/_context-logger.scss
65
+ - stylesheets/logger/_element-logger.scss
66
+ - stylesheets/logger/_modifier-logger.scss
67
+ - stylesheets/logger/_scope-logger.scss
68
+ - stylesheets/test.css
69
+ - stylesheets/test.css.map
70
+ - stylesheets/test.scss
71
+ homepage: https://github.com/danielguillan/bem-constructor
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.6
89
+ requirements: []
90
+ rubyforge_project: bem-constructor
91
+ rubygems_version: 2.2.2
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: BEM Constructor is a Sass library for building immutable and namespaced BEM-style
95
+ CSS objects. By enforcing a consistent and programatic way of defining objects (blocks,
96
+ elements and modifiers) it ensures a more structured, robust and secure object codebase
97
+ that is easy to understand and maintain. Objects defined using the constructor are
98
+ impossible to modify and reassign by mistake or omission.
99
+ test_files: []