diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/_bem.scss | 62 | ||||
| -rw-r--r-- | src/_contexts.scss | 315 | ||||
| -rw-r--r-- | src/_easing.scss | 483 | ||||
| -rw-r--r-- | src/_functions.scss | 328 | ||||
| -rw-r--r-- | src/_gradients.scss | 600 | ||||
| -rw-r--r-- | src/_harmony.scss | 94 | ||||
| -rw-r--r-- | src/_math.scss | 62 | ||||
| -rw-r--r-- | src/_props.scss | 281 | ||||
| -rw-r--r-- | src/_responsive.scss | 406 | ||||
| -rw-r--r-- | src/_vars.scss | 16 | ||||
| -rw-r--r-- | src/bem-shortcodes.scss | 349 | ||||
| -rw-r--r-- | src/bem/_block.scss | 392 | ||||
| -rw-r--r-- | src/bem/_debug.scss | 16 | ||||
| -rw-r--r-- | src/bem/_element.scss | 622 | ||||
| -rw-r--r-- | src/bem/_functions.scss | 26 | ||||
| -rw-r--r-- | src/bem/_modifier.scss | 246 | ||||
| -rw-r--r-- | src/bem/_multi.scss | 131 | ||||
| -rw-r--r-- | src/bem/_state.scss | 146 | ||||
| -rw-r--r-- | src/bem/_suffix.scss | 118 | ||||
| -rw-r--r-- | src/bem/_theme.scss | 61 | ||||
| -rw-r--r-- | src/bem/_validators.scss | 176 | ||||
| -rw-r--r-- | src/bem/_vars.scss | 108 | ||||
| -rw-r--r-- | src/harmony-shortcodes.scss | 35 | ||||
| -rw-r--r-- | src/main.scss | 10 | ||||
| -rw-r--r-- | src/prep.scss | 2 | ||||
| -rw-r--r-- | src/responsive-shortcodes.scss | 14 | 
26 files changed, 5099 insertions, 0 deletions
| diff --git a/src/_bem.scss b/src/_bem.scss new file mode 100644 index 0000000..b6032ea --- /dev/null +++ b/src/_bem.scss | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | //// | ||
| 2 | /// BEM. | ||
| 3 | /// | ||
| 4 | /// BEM is a methodology for structuring websites and is mostly known for it's CSS naming convention. | ||
| 5 | /// BEMIT is in extension of this methodology and allows you to give blocks a more fine-grained purpose | ||
| 6 | /// than BEM alone would let you do. | ||
| 7 | /// | ||
| 8 | /// Sass does support BEM quite well thanks to the ampersand (&) and the @at-root directive. However, | ||
| 9 | /// there is no way to make sure users adhere to the BEM or BEMIT methodology. | ||
| 10 | /// That's where the mixins in this file come into play: They automatically generate the right selectors | ||
| 11 | /// and perform checks regarding the nesting order, nesting depth, and so on. | ||
| 12 | /// | ||
| 13 | /// There are comments in the mixins explaining what selector is generated. The EBNF grammar is as follows: | ||
| 14 | /// | ||
| 15 | /// (* Shorthands for block, element, modifier, suffix *) | ||
| 16 | /// entity_shorthand = "b" "e" "m" "s" "t" ; | ||
| 17 | /// | ||
| 18 | /// (* One or multiple BEMIT entities that were generated with an earlier mixin invocation *) | ||
| 19 | /// existing_entities = "{" entity_shorthand { "," entity_shorthand } "}" ; | ||
| 20 | /// | ||
| 21 | /// (* A BEM entity that doesn't depend on a parent entity *) | ||
| 22 | /// generated_independent_entity = "block" ; | ||
| 23 | /// | ||
| 24 | /// (* A BEM entity that is attached to a parent entity *) | ||
| 25 | /// generated_attached_entity = existing_entities ( "__element" | "--modifier" | "@suffix" ) ; | ||
| 26 | /// | ||
| 27 | /// (* A selector created by the user, such as "&:hover", "> a", and so on *) | ||
| 28 | /// manual_selector_part = "[manual selector]" ; | ||
| 29 | /// | ||
| 30 | /// (* A part of the selector that may or may not be in the generated result *) | ||
| 31 | /// optional_selector_part = "(" ( existing_entities | manual_selector_part ) ")" ; | ||
| 32 | /// | ||
| 33 | /// (* One part of the selector *) | ||
| 34 | /// selector_part = existing_entities | manual_selector_part | optional_selector_part | generated_independent_entity | generated_attached_entity ; | ||
| 35 | /// | ||
| 36 | /// (* How the left and right selector are related, i.e. space means right is a descendant of left, and dot means right specializes left *) | ||
| 37 | /// selector_link = " " | "." ; | ||
| 38 | /// | ||
| 39 | /// (* The whole selector *) | ||
| 40 | /// selector = selector_part { ( selector_link ) selector_part } ; | ||
| 41 | /// | ||
| 42 | /// @link https://en.bem.info/ Information about BEM | ||
| 43 | /// @link https://csswizardry.com/2015/08/bemit-taking-the-bem-naming-convention-a-step-further/ Information about BEMIT | ||
| 44 | /// | ||
| 45 | /// @group BEM | ||
| 46 | /// | ||
| 47 | /// @access public | ||
| 48 | //// | ||
| 49 | |||
| 50 | @import 'bem/vars'; | ||
| 51 | @import 'bem/functions'; | ||
| 52 | @import 'bem/validators'; | ||
| 53 | @import 'bem/block'; | ||
| 54 | @import 'bem/element'; | ||
| 55 | @import 'bem/modifier'; | ||
| 56 | @import 'bem/suffix'; | ||
| 57 | @import 'bem/state'; | ||
| 58 | @import 'bem/theme'; | ||
| 59 | @import 'bem/multi'; | ||
| 60 | @import 'bem/debug'; | ||
| 61 | |||
| 62 | @include iro-context-stack-create($iro-bem-context-id); | ||
| diff --git a/src/_contexts.scss b/src/_contexts.scss new file mode 100644 index 0000000..556fde3 --- /dev/null +++ b/src/_contexts.scss | |||
| @@ -0,0 +1,315 @@ | |||
| 1 | //// | ||
| 2 | /// Context handling. | ||
| 3 | /// | ||
| 4 | /// Contexts allow you to pass data between mixins and let you enforce a certain nesting order. | ||
| 5 | /// It's an essential part for the BEM-related mixins. | ||
| 6 | /// | ||
| 7 | /// If you want to create a new context, the easiest pattern is to create a new mixin and wrap | ||
| 8 | /// the @content between a pair of iro-context-push and iro-context-pop. | ||
| 9 | /// From within the @content, you can access the context's data with iro-context-get. | ||
| 10 | /// To make the compilation fail if a certain nesting order is violated, use | ||
| 11 | /// iro-context-assert-stack-must-contain and iro-context-assert-stack-must-not-contain. | ||
| 12 | /// | ||
| 13 | /// @group Contexts | ||
| 14 | /// | ||
| 15 | /// @access public | ||
| 16 | //// | ||
| 17 | |||
| 18 | /// | ||
| 19 | /// Map of all context stacks. | ||
| 20 | /// | ||
| 21 | /// @type map | ||
| 22 | /// | ||
| 23 | /// @access private | ||
| 24 | /// | ||
| 25 | $iro-context-stacks: (); | ||
| 26 | |||
| 27 | /// | ||
| 28 | /// Create a new context stack. | ||
| 29 | /// | ||
| 30 | /// @param {string} $stack-id - ID of context stack | ||
| 31 | /// | ||
| 32 | /// @throw If context stack already exists | ||
| 33 | /// | ||
| 34 | @mixin iro-context-stack-create($stack-id) { | ||
| 35 | $noop: iro-context-stack-create($stack-id); | ||
| 36 | } | ||
| 37 | |||
| 38 | /// | ||
| 39 | /// Create a new context stack. | ||
| 40 | /// | ||
| 41 | /// @param {string} $stack-id - ID of context stack | ||
| 42 | /// | ||
| 43 | @function iro-context-stack-create($stack-id) { | ||
| 44 | @if map-has-key($iro-context-stacks, $stack-id) { | ||
| 45 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 46 | } | ||
| 47 | |||
| 48 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: ())) !global; | ||
| 49 | |||
| 50 | @return null; | ||
| 51 | } | ||
| 52 | |||
| 53 | /// | ||
| 54 | /// Clear a context stack. | ||
| 55 | /// | ||
| 56 | /// @param {string} $stack-id - ID of context stack | ||
| 57 | /// | ||
| 58 | @mixin iro-context-stack-clear($stack-id) { | ||
| 59 | $noop: iro-context-stack-clear($stack-id); | ||
| 60 | } | ||
| 61 | |||
| 62 | /// | ||
| 63 | /// Clear a context stack. | ||
| 64 | /// | ||
| 65 | /// @param {string} $stack-id - ID of context stack | ||
| 66 | /// | ||
| 67 | @function iro-context-stack-clear($stack-id) { | ||
| 68 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 69 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 70 | } | ||
| 71 | |||
| 72 | $context-stack: (); | ||
| 73 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; | ||
| 74 | |||
| 75 | @return null; | ||
| 76 | } | ||
| 77 | |||
| 78 | /// | ||
| 79 | /// Delete a context stack. | ||
| 80 | /// | ||
| 81 | /// @param {string} $stack-id - ID of context stack | ||
| 82 | /// | ||
| 83 | @mixin iro-context-stack-delete($stack-id) { | ||
| 84 | $noop: iro-context-stack-delete($stack-id); | ||
| 85 | } | ||
| 86 | |||
| 87 | /// | ||
| 88 | /// Delete a context stack. | ||
| 89 | /// | ||
| 90 | /// @param {string} $stack-id - ID of context stack | ||
| 91 | /// | ||
| 92 | @function iro-context-stack-delete($stack-id) { | ||
| 93 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 94 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 95 | } | ||
| 96 | |||
| 97 | $iro-context-stacks: map-remove($iro-context-stacks, $stack-id) !global; | ||
| 98 | |||
| 99 | @return null; | ||
| 100 | } | ||
| 101 | |||
| 102 | /// | ||
| 103 | /// Push a new context to a context stack. | ||
| 104 | /// | ||
| 105 | /// @param {string} $stack-id - ID of context stack to use | ||
| 106 | /// @param {string} $id - ID of new context | ||
| 107 | /// @param {any} $data [()] - Data that belongs to the context | ||
| 108 | /// | ||
| 109 | @mixin iro-context-push($stack-id, $id, $data: ()) { | ||
| 110 | $noop: iro-context-push($stack-id, $id, $data); | ||
| 111 | } | ||
| 112 | |||
| 113 | /// | ||
| 114 | /// Push a new context to a context stack. | ||
| 115 | /// | ||
| 116 | /// @param {string} $stack-id - ID of context stack to use | ||
| 117 | /// @param {string} $id - ID of new context | ||
| 118 | /// @param {any} $data [()] - Data that belongs to the context | ||
| 119 | /// | ||
| 120 | /// @return {list} A list with two items: 1 = context id, 2 = context data | ||
| 121 | /// | ||
| 122 | @function iro-context-push($stack-id, $id, $data: ()) { | ||
| 123 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 124 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 125 | } | ||
| 126 | |||
| 127 | $context: $id $data; | ||
| 128 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
| 129 | $context-stack: append($context-stack, $context); | ||
| 130 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; | ||
| 131 | |||
| 132 | @return $context; | ||
| 133 | } | ||
| 134 | |||
| 135 | /// | ||
| 136 | /// Pop the latest context from a context stack. | ||
| 137 | /// | ||
| 138 | /// @param {string} $stack-id - ID of context stack to use | ||
| 139 | /// | ||
| 140 | /// @throw If context stack doesn't exist | ||
| 141 | /// | ||
| 142 | @mixin iro-context-pop($stack-id) { | ||
| 143 | $noop: iro-context-pop($stack-id); | ||
| 144 | } | ||
| 145 | |||
| 146 | /// | ||
| 147 | /// Pop the latest context from a context stack. | ||
| 148 | /// | ||
| 149 | /// @param {string} $stack-id - ID of context stack to use | ||
| 150 | /// | ||
| 151 | /// @return {list} A list with two items: 1 = context id, 2 = context data | ||
| 152 | /// | ||
| 153 | @function iro-context-pop($stack-id) { | ||
| 154 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 155 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 156 | } | ||
| 157 | |||
| 158 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
| 159 | |||
| 160 | @if length($context-stack) == 0 { | ||
| 161 | @error 'Context stack "#{inspect($stack-id)}" is already empty.'; | ||
| 162 | } | ||
| 163 | |||
| 164 | $popped-context: nth($context-stack, -1); | ||
| 165 | |||
| 166 | @if length($context-stack) == 1 { | ||
| 167 | $context-stack: (); | ||
| 168 | } @else { | ||
| 169 | $context-stack: iro-list-slice($context-stack, 1, length($context-stack) - 1); | ||
| 170 | } | ||
| 171 | |||
| 172 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; | ||
| 173 | |||
| 174 | @return $popped-context; | ||
| 175 | } | ||
| 176 | |||
| 177 | /// | ||
| 178 | /// Assert that a context stack must contain one of the given context IDs. | ||
| 179 | /// | ||
| 180 | /// @param {string} $stack-id - ID of context stack to use | ||
| 181 | /// @param {list} $context-ids - Context IDs | ||
| 182 | /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. | ||
| 183 | /// | ||
| 184 | /// @throw If assertion fails | ||
| 185 | /// | ||
| 186 | @mixin iro-context-assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { | ||
| 187 | @if not iro-context-stack-contains($stack-id, $context-ids, $check-head-only) { | ||
| 188 | @error 'Must be called inside of contexts "#{inspect($context-ids)}".'; | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | /// | ||
| 193 | /// Assert that a context stack must not contain any of the given context IDs. | ||
| 194 | /// | ||
| 195 | /// @param {string} $stack-id - ID of context stack to use | ||
| 196 | /// @param {list} $context-ids - Context IDs | ||
| 197 | /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. | ||
| 198 | /// | ||
| 199 | /// @throw If assertion fails | ||
| 200 | /// | ||
| 201 | @mixin iro-context-assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { | ||
| 202 | @if iro-context-stack-contains($stack-id, $context-ids, $check-head-only) { | ||
| 203 | @error 'Must not be called inside of contexts "#{inspect($context-ids)}".'; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | /// | ||
| 208 | /// Check if a context stack contains one of the given context IDs. | ||
| 209 | /// | ||
| 210 | /// @param {string} $stack-id - ID of context stack to use | ||
| 211 | /// @param {list} $context-ids - Context IDs | ||
| 212 | /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. | ||
| 213 | /// | ||
| 214 | /// @return {bool} `true` if the context stack contains one of the context IDs, otherwise `false` | ||
| 215 | /// | ||
| 216 | @function iro-context-stack-contains($stack-id, $context-ids, $check-head-only: false) { | ||
| 217 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 218 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 219 | } | ||
| 220 | |||
| 221 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
| 222 | |||
| 223 | @if length($context-stack) == 0 { | ||
| 224 | @return false; | ||
| 225 | } | ||
| 226 | |||
| 227 | $end-idx: if($check-head-only, length($context-stack), 1); | ||
| 228 | |||
| 229 | @for $i from length($context-stack) through $end-idx { | ||
| 230 | $context: nth($context-stack, $i); | ||
| 231 | |||
| 232 | @each $chk-context in $context-ids { | ||
| 233 | @if nth($context, 1) == $chk-context { | ||
| 234 | @return true; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | @return false; | ||
| 240 | } | ||
| 241 | |||
| 242 | /// | ||
| 243 | /// Assert that a context stack must contain a number of contexts smaller than $max-count. | ||
| 244 | /// | ||
| 245 | /// @param {string} $stack-id - ID of context stack to use | ||
| 246 | /// @param {number} $max-count - Maximum number ofg contexts in context stack | ||
| 247 | /// | ||
| 248 | /// @throw If assertion fails | ||
| 249 | /// | ||
| 250 | @mixin iro-context-assert-stack-count($stack-id, $max-count) { | ||
| 251 | @if iro-context-stack-count($stack-id) > $max-count { | ||
| 252 | @error 'Maximum context count "#{inspect($max-count)}" exceeded.'; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | /// | ||
| 257 | /// Get the number of contexts from a context stack. | ||
| 258 | /// | ||
| 259 | /// @param {string} $stack-id - ID of context stack to use | ||
| 260 | /// | ||
| 261 | /// @return {number} The number of contexts | ||
| 262 | /// | ||
| 263 | @function iro-context-stack-count($stack-id) { | ||
| 264 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 265 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 266 | } | ||
| 267 | |||
| 268 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
| 269 | |||
| 270 | @return length($context-stack); | ||
| 271 | } | ||
| 272 | |||
| 273 | /// | ||
| 274 | /// Get a specific context from the stack. | ||
| 275 | /// | ||
| 276 | /// @param {string} $stack-id - ID of context stack to use | ||
| 277 | /// @param {number | string | list} $type-or-level - If this is a number (!= 0), the nth context from the head will be returned. If it is a string, the first context with a matching ID will be returned. If it is a list, the first context that matches one of the IDs in the list will be returned. | ||
| 278 | /// | ||
| 279 | /// @return {list} Null if no match was found, otherwise a list with two items: 1. context ID, 2. context data. | ||
| 280 | /// | ||
| 281 | @function iro-context-get($stack-id, $type-or-level: null) { | ||
| 282 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
| 283 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
| 284 | } | ||
| 285 | |||
| 286 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
| 287 | |||
| 288 | @if length($context-stack) == 0 { | ||
| 289 | @return null; | ||
| 290 | } | ||
| 291 | |||
| 292 | @if type-of($type-or-level) == number { | ||
| 293 | $context: nth($context-stack, -$type-or-level); | ||
| 294 | |||
| 295 | @return $context; | ||
| 296 | } @else { | ||
| 297 | @for $i from -1 through -(length($context-stack)) { | ||
| 298 | $context: nth($context-stack, $i); | ||
| 299 | |||
| 300 | @if type-of($type-or-level) == list { | ||
| 301 | @for $j from 1 through length($type-or-level) { | ||
| 302 | $ctx: nth($type-or-level, $j); | ||
| 303 | |||
| 304 | @if nth($context, 1) == $ctx { | ||
| 305 | @return $context; | ||
| 306 | } | ||
| 307 | } | ||
| 308 | } @else if nth($context, 1) == $type-or-level { | ||
| 309 | @return $context; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | @return null; | ||
| 315 | } | ||
| diff --git a/src/_easing.scss b/src/_easing.scss new file mode 100644 index 0000000..c41635b --- /dev/null +++ b/src/_easing.scss | |||
| @@ -0,0 +1,483 @@ | |||
| 1 | //// | ||
| 2 | /// Easing. | ||
| 3 | /// | ||
| 4 | /// A collection of easing functions which are commonly used for animations. | ||
| 5 | /// This code is based on https://github.com/gre/bezier-easing. | ||
| 6 | /// | ||
| 7 | /// @group Easing | ||
| 8 | /// | ||
| 9 | /// @access public | ||
| 10 | //// | ||
| 11 | |||
| 12 | /// | ||
| 13 | /// @access private | ||
| 14 | /// | ||
| 15 | $iro-cubic-bezier-sample-pool: (); | ||
| 16 | |||
| 17 | /// | ||
| 18 | /// Sample pool size for cubic bezier calculations. | ||
| 19 | /// | ||
| 20 | $iro-cubic-bezier-sample-pool-size: 10 !default; | ||
| 21 | |||
| 22 | /// | ||
| 23 | /// Minimum slope required to use the Newton-Raphson method for cubic bezier calculations. | ||
| 24 | /// | ||
| 25 | $iro-cubic-bezier-newton-min-slope: 0.001 !default; | ||
| 26 | |||
| 27 | /// | ||
| 28 | /// Number of iterations of the Newton-Raphson method. | ||
| 29 | /// | ||
| 30 | $iro-cubic-bezier-newton-iters: 4 !default; | ||
| 31 | |||
| 32 | /// | ||
| 33 | /// Precision of the subdivision method for cubic bezier calculations. | ||
| 34 | /// | ||
| 35 | $iro-cubic-bezier-subdiv-precision: 0.0000001 !default; | ||
| 36 | |||
| 37 | /// | ||
| 38 | /// Maximum iterations of the subdivision method for cubic bezier calculations. | ||
| 39 | /// | ||
| 40 | $iro-cubic-bezier-subdiv-max-iters: 10 !default; | ||
| 41 | |||
| 42 | /// | ||
| 43 | /// A cubic bezier function identical to the CSS cubic-bezier function. | ||
| 44 | /// | ||
| 45 | /// @param {number} $x1 - X of first point | ||
| 46 | /// @param {number} $y1 - Y of first point | ||
| 47 | /// @param {number} $x2 - X of second point | ||
| 48 | /// @param {number} $y2 - Y of second point | ||
| 49 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 50 | /// | ||
| 51 | /// @return {number} | ||
| 52 | /// | ||
| 53 | @function iro-cubic-bezier($x1, $y1, $x2, $y2, $x) { | ||
| 54 | // | ||
| 55 | // Cover simple cases | ||
| 56 | // | ||
| 57 | |||
| 58 | @if ($x1 == $y1) and ($x2 == $y2) { | ||
| 59 | @return $x; | ||
| 60 | } | ||
| 61 | @if $x == 0 { | ||
| 62 | @return 0; | ||
| 63 | } | ||
| 64 | @if $x == 1 { | ||
| 65 | @return 1; | ||
| 66 | } | ||
| 67 | |||
| 68 | // | ||
| 69 | // Generate samples | ||
| 70 | // | ||
| 71 | |||
| 72 | $sample-pool-key: $x1 + '_' + $x2; | ||
| 73 | |||
| 74 | @if not map-has-key($iro-cubic-bezier-sample-pool, $sample-pool-key) { | ||
| 75 | $samples: (); | ||
| 76 | |||
| 77 | @for $i from 0 through $iro-cubic-bezier-sample-pool-size { | ||
| 78 | $samples: append($samples, iro-cubic-bezier-func($x1, $x2, $i / $iro-cubic-bezier-sample-pool-size)); | ||
| 79 | } | ||
| 80 | |||
| 81 | $iro-cubic-bezier-sample-pool: map-merge($iro-cubic-bezier-sample-pool, ($sample-pool-key: $samples)) !global; | ||
| 82 | } | ||
| 83 | |||
| 84 | // | ||
| 85 | // Calculate cubic bezier | ||
| 86 | // | ||
| 87 | |||
| 88 | @return iro-cubic-bezier-func($y1, $y2, iro-cubic-bezier-t-for-x($x1, $x2, $x)); | ||
| 89 | } | ||
| 90 | |||
| 91 | /// | ||
| 92 | /// @access private | ||
| 93 | /// | ||
| 94 | @function iro-cubic-bezier-func-a($p1, $p2) { | ||
| 95 | @return 1 - 3 * $p2 + 3 * $p1; | ||
| 96 | } | ||
| 97 | |||
| 98 | /// | ||
| 99 | /// @access private | ||
| 100 | /// | ||
| 101 | @function iro-cubic-bezier-func-b($p1, $p2) { | ||
| 102 | @return 3 * $p2 - 6 * $p1; | ||
| 103 | } | ||
| 104 | |||
| 105 | /// | ||
| 106 | /// @access private | ||
| 107 | /// | ||
| 108 | @function iro-cubic-bezier-func-c($p1) { | ||
| 109 | @return 3 * $p1; | ||
| 110 | } | ||
| 111 | |||
| 112 | /// | ||
| 113 | /// One-dimensional cubic bezier function. | ||
| 114 | /// | ||
| 115 | /// @access private | ||
| 116 | /// | ||
| 117 | @function iro-cubic-bezier-func($p1, $p2, $t) { | ||
| 118 | @return ((iro-cubic-bezier-func-a($p1, $p2) * $t + iro-cubic-bezier-func-b($p1, $p2)) * $t + iro-cubic-bezier-func-c($p1)) * $t; | ||
| 119 | } | ||
| 120 | |||
| 121 | /// | ||
| 122 | /// Derivative of the one-dimensional cubic bezier function. | ||
| 123 | /// | ||
| 124 | /// @access private | ||
| 125 | /// | ||
| 126 | @function iro-cubic-bezier-func-slope($p1, $p2, $t) { | ||
| 127 | @return 3 * iro-cubic-bezier-func-a($p1, $p2) * $t * $t + 2 * iro-cubic-bezier-func-b($p1, $p2) * $t + iro-cubic-bezier-func-c($p1); | ||
| 128 | } | ||
| 129 | |||
| 130 | /// | ||
| 131 | /// Newton-Raphson method to calculate the t parameter for a given x parameter. | ||
| 132 | /// | ||
| 133 | /// @access private | ||
| 134 | /// | ||
| 135 | @function iro-cubic-bezier-newton-raphson($x1, $x2, $x, $t) { | ||
| 136 | @for $i from 1 through $iro-cubic-bezier-newton-iters { | ||
| 137 | $cur-slope: iro-cubic-bezier-func-slope($x1, $x2, $t); | ||
| 138 | |||
| 139 | @if $cur-slope == 0 { | ||
| 140 | @return $t; | ||
| 141 | } | ||
| 142 | |||
| 143 | $cur-x: iro-cubic-bezier-func($x1, $x2, $t) - $x; | ||
| 144 | $t: $t - $cur-x / $cur-slope; | ||
| 145 | } | ||
| 146 | |||
| 147 | @return $t; | ||
| 148 | } | ||
| 149 | |||
| 150 | /// | ||
| 151 | /// Subdivision method to calculate the t parameter for a given x parameter. | ||
| 152 | /// | ||
| 153 | /// @access private | ||
| 154 | /// | ||
| 155 | @function iro-cubic-bezier-binary-subdivide($x1, $x2, $x, $a, $b) { | ||
| 156 | $cur-x: 0; | ||
| 157 | $cur-t: 0; | ||
| 158 | $i: 0; | ||
| 159 | |||
| 160 | @while $i < $iro-cubic-bezier-subdiv-max-iters { | ||
| 161 | $cur-t: $a + ($b - $a) / 2; | ||
| 162 | $cur-x: iro-cubic-bezier-func($x1, $x2, $cur-t) - $x; | ||
| 163 | |||
| 164 | @if $cur-x > 0 { | ||
| 165 | $b: $cur-t; | ||
| 166 | } @else { | ||
| 167 | $a: $cur-t; | ||
| 168 | } | ||
| 169 | |||
| 170 | @if abs($cur-x) < $iro-cubic-bezier-subdiv-precision { | ||
| 171 | @return $cur-t; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | @return $cur-t; | ||
| 176 | } | ||
| 177 | |||
| 178 | /// | ||
| 179 | /// Calculate the t parameter for a given x parameter. | ||
| 180 | /// | ||
| 181 | /// @access private | ||
| 182 | /// | ||
| 183 | @function iro-cubic-bezier-t-for-x($x1, $x2, $x) { | ||
| 184 | $sample-pool-key: $x1 + '_' + $x2; | ||
| 185 | $samples: map-get($iro-cubic-bezier-sample-pool, $sample-pool-key); | ||
| 186 | |||
| 187 | $intv-start: 0; | ||
| 188 | $cur-sample: 1; | ||
| 189 | $last-sample: $iro-cubic-bezier-sample-pool-size; | ||
| 190 | |||
| 191 | @while ($cur-sample != $last-sample) and (nth($samples, $cur-sample) <= $x) { | ||
| 192 | $intv-start: $intv-start + (1 / $iro-cubic-bezier-sample-pool-size); | ||
| 193 | $cur-sample: $cur-sample + 1; | ||
| 194 | } | ||
| 195 | $cur-sample: $cur-sample - 1; | ||
| 196 | |||
| 197 | $dist: ($x - nth($samples, $cur-sample)) / (nth($samples, $cur-sample + 1) - nth($samples, $cur-sample)); | ||
| 198 | $guess-t: $intv-start + $dist / $iro-cubic-bezier-sample-pool-size; | ||
| 199 | |||
| 200 | $init-slope: iro-cubic-bezier-func-slope($x1, $x2, $guess-t); | ||
| 201 | @if $init-slope >= $iro-cubic-bezier-newton-min-slope { | ||
| 202 | @return iro-cubic-bezier-newton-raphson($x1, $x2, $x, $guess-t); | ||
| 203 | } @else if $init-slope == 0 { | ||
| 204 | @return $guess-t; | ||
| 205 | } @else { | ||
| 206 | @return iro-cubic-bezier-binary-subdivide($x1, $x2, $x, $intv-start, $intv-start + 1 / $iro-cubic-bezier-sample-pool-size); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | /// | ||
| 211 | /// Sinusoidal easing function (in direction). | ||
| 212 | /// | ||
| 213 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 214 | /// | ||
| 215 | /// @return {number} | ||
| 216 | /// | ||
| 217 | @function iro-ease($x) { | ||
| 218 | @return iro-cubic-bezier(0.25, 0.1, 0.25, 1, $x); | ||
| 219 | } | ||
| 220 | |||
| 221 | /// | ||
| 222 | /// Sinusoidal easing function (in direction). | ||
| 223 | /// | ||
| 224 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 225 | /// | ||
| 226 | /// @return {number} | ||
| 227 | /// | ||
| 228 | @function iro-ease-in($x) { | ||
| 229 | @return iro-cubic-bezier(0.42, 0, 1, 1, $x); | ||
| 230 | } | ||
| 231 | |||
| 232 | /// | ||
| 233 | /// Sinusoidal easing function (out direction). | ||
| 234 | /// | ||
| 235 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 236 | /// | ||
| 237 | /// @return {number} | ||
| 238 | /// | ||
| 239 | @function iro-ease-out($x) { | ||
| 240 | @return iro-cubic-bezier(0, 0, 0.58, 1, $x); | ||
| 241 | } | ||
| 242 | |||
| 243 | /// | ||
| 244 | /// Sinusoidal easing function (in-out direction). | ||
| 245 | /// | ||
| 246 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 247 | /// | ||
| 248 | /// @return {number} | ||
| 249 | /// | ||
| 250 | @function iro-ease-in-out($x) { | ||
| 251 | @return iro-cubic-bezier(0.42, 0, 0.58, 1, $x); | ||
| 252 | } | ||
| 253 | |||
| 254 | /// | ||
| 255 | /// Sinusoidal easing function (in direction). | ||
| 256 | /// | ||
| 257 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 258 | /// | ||
| 259 | /// @return {number} | ||
| 260 | /// | ||
| 261 | @function iro-ease-in-sine($x) { | ||
| 262 | @return iro-cubic-bezier(0.47, 0, 0.745, 0.715, $x); | ||
| 263 | } | ||
| 264 | |||
| 265 | /// | ||
| 266 | /// Sinusoidal easing function (out direction). | ||
| 267 | /// | ||
| 268 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 269 | /// | ||
| 270 | /// @return {number} | ||
| 271 | /// | ||
| 272 | @function iro-ease-out-sine($x) { | ||
| 273 | @return iro-cubic-bezier(0.39, 0.575, 0.565, 1, $x); | ||
| 274 | } | ||
| 275 | |||
| 276 | /// | ||
| 277 | /// Sinusoidal easing function (in-out direction). | ||
| 278 | /// | ||
| 279 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 280 | /// | ||
| 281 | /// @return {number} | ||
| 282 | /// | ||
| 283 | @function iro-ease-in-out-sine($x) { | ||
| 284 | @return iro-cubic-bezier(0.445, 0.05, 0.55, 0.95, $x); | ||
| 285 | } | ||
| 286 | |||
| 287 | /// | ||
| 288 | /// Quadratic easing function (in direction). | ||
| 289 | /// | ||
| 290 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 291 | /// | ||
| 292 | /// @return {number} | ||
| 293 | /// | ||
| 294 | @function iro-ease-in-quad($x) { | ||
| 295 | @return iro-cubic-bezier(0.55, 0.085, 0.68, 0.53, $x); | ||
| 296 | } | ||
| 297 | |||
| 298 | /// | ||
| 299 | /// Quadratic easing function (out direction). | ||
| 300 | /// | ||
| 301 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 302 | /// | ||
| 303 | /// @return {number} | ||
| 304 | /// | ||
| 305 | @function iro-ease-out-quad($x) { | ||
| 306 | @return iro-cubic-bezier(0.25, 0.46, 0.45, 0.94, $x); | ||
| 307 | } | ||
| 308 | |||
| 309 | /// | ||
| 310 | /// Quadratic easing function (in-out direction). | ||
| 311 | /// | ||
| 312 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 313 | /// | ||
| 314 | /// @return {number} | ||
| 315 | /// | ||
| 316 | @function iro-ease-in-out-quad($x) { | ||
| 317 | @return iro-cubic-bezier(0.455, 0.03, 0.515, 0.955, $x); | ||
| 318 | } | ||
| 319 | |||
| 320 | /// | ||
| 321 | /// Cubic easing function (in direction). | ||
| 322 | /// | ||
| 323 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 324 | /// | ||
| 325 | /// @return {number} | ||
| 326 | /// | ||
| 327 | @function iro-ease-in-cubic($x) { | ||
| 328 | @return iro-cubic-bezier(0.55, 0.055, 0.675, 0.19, $x); | ||
| 329 | } | ||
| 330 | |||
| 331 | /// | ||
| 332 | /// Cubic easing function (out direction). | ||
| 333 | /// | ||
| 334 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 335 | /// | ||
| 336 | /// @return {number} | ||
| 337 | /// | ||
| 338 | @function iro-ease-out-cubic($x) { | ||
| 339 | @return iro-cubic-bezier(0.215, 0.61, 0.355, 1, $x); | ||
| 340 | } | ||
| 341 | |||
| 342 | /// | ||
| 343 | /// Cubic easing function (in-out direction). | ||
| 344 | /// | ||
| 345 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 346 | /// | ||
| 347 | /// @return {number} | ||
| 348 | /// | ||
| 349 | @function iro-ease-in-out-cubic($x) { | ||
| 350 | @return iro-cubic-bezier(0.645, 0.045, 0.355, 1, $x); | ||
| 351 | } | ||
| 352 | |||
| 353 | /// | ||
| 354 | /// Quart easing function (in direction). | ||
| 355 | /// | ||
| 356 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 357 | /// | ||
| 358 | /// @return {number} | ||
| 359 | /// | ||
| 360 | @function iro-ease-in-quart($x) { | ||
| 361 | @return iro-cubic-bezier(0.895, 0.03, 0.685, 0.22, $x); | ||
| 362 | } | ||
| 363 | |||
| 364 | /// | ||
| 365 | /// Quart easing function (out direction). | ||
| 366 | /// | ||
| 367 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 368 | /// | ||
| 369 | /// @return {number} | ||
| 370 | /// | ||
| 371 | @function iro-ease-out-quart($x) { | ||
| 372 | @return iro-cubic-bezier(0.165, 0.84, 0.44, 1, $x); | ||
| 373 | } | ||
| 374 | |||
| 375 | /// | ||
| 376 | /// Quart easing function (in-out direction). | ||
| 377 | /// | ||
| 378 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 379 | /// | ||
| 380 | /// @return {number} | ||
| 381 | /// | ||
| 382 | @function iro-ease-in-out-quart($x) { | ||
| 383 | @return iro-cubic-bezier(0.77, 0, 0.175, 1, $x); | ||
| 384 | } | ||
| 385 | |||
| 386 | /// | ||
| 387 | /// Quint easing function (in direction). | ||
| 388 | /// | ||
| 389 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 390 | /// | ||
| 391 | /// @return {number} | ||
| 392 | /// | ||
| 393 | @function iro-ease-in-quint($x) { | ||
| 394 | @return iro-cubic-bezier(0.755, 0.05, 0.855, 0.06, $x); | ||
| 395 | } | ||
| 396 | |||
| 397 | /// | ||
| 398 | /// Quint easing function (out direction). | ||
| 399 | /// | ||
| 400 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 401 | /// | ||
| 402 | /// @return {number} | ||
| 403 | /// | ||
| 404 | @function iro-ease-out-quint($x) { | ||
| 405 | @return iro-cubic-bezier(0.23, 1, 0.32, 1, $x); | ||
| 406 | } | ||
| 407 | |||
| 408 | /// | ||
| 409 | /// Quint easing function (in-out direction). | ||
| 410 | /// | ||
| 411 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 412 | /// | ||
| 413 | /// @return {number} | ||
| 414 | /// | ||
| 415 | @function iro-ease-in-out-quint($x) { | ||
| 416 | @return iro-cubic-bezier(0.86, 0, 0.07, 1, $x); | ||
| 417 | } | ||
| 418 | |||
| 419 | /// | ||
| 420 | /// Exponential easing function (in direction). | ||
| 421 | /// | ||
| 422 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 423 | /// | ||
| 424 | /// @return {number} | ||
| 425 | /// | ||
| 426 | @function iro-ease-in-expo($x) { | ||
| 427 | @return iro-cubic-bezier(0.95, 0.05, 0.795, 0.035, $x); | ||
| 428 | } | ||
| 429 | |||
| 430 | /// | ||
| 431 | /// Exponential easing function (out direction). | ||
| 432 | /// | ||
| 433 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 434 | /// | ||
| 435 | /// @return {number} | ||
| 436 | /// | ||
| 437 | @function iro-ease-out-expo($x) { | ||
| 438 | @return iro-cubic-bezier(0.19, 1, 0.22, 1, $x); | ||
| 439 | } | ||
| 440 | |||
| 441 | /// | ||
| 442 | /// Exponential easing function (in-out direction). | ||
| 443 | /// | ||
| 444 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 445 | /// | ||
| 446 | /// @return {number} | ||
| 447 | /// | ||
| 448 | @function iro-ease-in-out-expo($x) { | ||
| 449 | @return iro-cubic-bezier(1, 0, 0, 1, $x); | ||
| 450 | } | ||
| 451 | |||
| 452 | /// | ||
| 453 | /// Circular easing function (in direction). | ||
| 454 | /// | ||
| 455 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 456 | /// | ||
| 457 | /// @return {number} | ||
| 458 | /// | ||
| 459 | @function iro-ease-in-circ($x) { | ||
| 460 | @return iro-cubic-bezier(0.6, 0.04, 0.98, 0.335, $x); | ||
| 461 | } | ||
| 462 | |||
| 463 | /// | ||
| 464 | /// Circular easing function (out direction). | ||
| 465 | /// | ||
| 466 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 467 | /// | ||
| 468 | /// @return {number} | ||
| 469 | /// | ||
| 470 | @function iro-ease-out-circ($x) { | ||
| 471 | @return iro-cubic-bezier(0.075, 0.82, 0.165, 1, $x); | ||
| 472 | } | ||
| 473 | |||
| 474 | /// | ||
| 475 | /// Circular easing function (in-out direction). | ||
| 476 | /// | ||
| 477 | /// @param {number} $x - Progress between 0 and 1 inclusive | ||
| 478 | /// | ||
| 479 | /// @return {number} | ||
| 480 | /// | ||
| 481 | @function iro-ease-in-out-circ($x) { | ||
| 482 | @return iro-cubic-bezier(0.785, 0.135, 0.15, 0.86, $x); | ||
| 483 | } | ||
| diff --git a/src/_functions.scss b/src/_functions.scss new file mode 100644 index 0000000..2f34dc4 --- /dev/null +++ b/src/_functions.scss | |||
| @@ -0,0 +1,328 @@ | |||
| 1 | //// | ||
| 2 | /// Various functions. | ||
| 3 | /// | ||
| 4 | /// This file contains various and mostly unrelated functions. Some of which | ||
| 5 | /// are used in this framework, while others are just there and may be used. | ||
| 6 | /// | ||
| 7 | /// @group Functions | ||
| 8 | /// | ||
| 9 | /// @access public | ||
| 10 | //// | ||
| 11 | |||
| 12 | /// | ||
| 13 | /// Replace a substring with a new string. | ||
| 14 | /// | ||
| 15 | /// @param {string} $string - String to search | ||
| 16 | /// @param {string} $search - Substring that gets replaced | ||
| 17 | /// @param {string} $replace - String that replaces $search | ||
| 18 | /// | ||
| 19 | /// @return {string} A string with all instances of $search replaced with $replace | ||
| 20 | /// | ||
| 21 | @function iro-str-replace($string, $search, $replace) { | ||
| 22 | $index: str-index($string, $search); | ||
| 23 | |||
| 24 | @if $index { | ||
| 25 | @return str-slice($string, 1, $index - 1) + $replace + iro-str-replace(str-slice($string, $index + str-length($search)), $search, $replace); | ||
| 26 | } | ||
| 27 | |||
| 28 | @return $string; | ||
| 29 | } | ||
| 30 | |||
| 31 | /// | ||
| 32 | /// Concatenate all items from $list. | ||
| 33 | /// | ||
| 34 | /// @param {list} $list | ||
| 35 | /// @param {number} $glue - Delimiter | ||
| 36 | /// | ||
| 37 | /// @return {string} | ||
| 38 | /// | ||
| 39 | @function iro-str-implode($list, $glue: '') { | ||
| 40 | $result: ''; | ||
| 41 | |||
| 42 | @each $item in $list { | ||
| 43 | $result: $result + if(length($item) > 1, iro-str-implode($item, $glue), $item); | ||
| 44 | |||
| 45 | @if $item != nth($list, length($list)) { | ||
| 46 | $result: $result + $glue; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | @return $result; | ||
| 51 | } | ||
| 52 | |||
| 53 | /// | ||
| 54 | /// Extract a subset from the given list. | ||
| 55 | /// | ||
| 56 | /// @param {list} $list | ||
| 57 | /// @param {number} $start [1] - Indices before this value will be discarded | ||
| 58 | /// @param {number} $end [length($list)] - Indices starting after this value will be discarded | ||
| 59 | /// | ||
| 60 | /// @return {list} A slice of the list | ||
| 61 | /// | ||
| 62 | @function iro-list-slice($list, $start: 1, $end: length($list)) { | ||
| 63 | $result: (); | ||
| 64 | |||
| 65 | @for $i from $start through $end { | ||
| 66 | @if $i != 0 { | ||
| 67 | $result: append($result, nth($list, $i), list-separator($list)); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | @return $result; | ||
| 72 | } | ||
| 73 | |||
| 74 | /// | ||
| 75 | /// Add a new item to the beginning of a list. | ||
| 76 | /// | ||
| 77 | /// @param {list} $list | ||
| 78 | /// @param {number} $value | ||
| 79 | /// | ||
| 80 | /// @return {list} A list with $value at the beginning, followed by the other items | ||
| 81 | /// | ||
| 82 | @function iro-list-prepend($list, $value) { | ||
| 83 | $result: append((), $value, list-separator($list)); | ||
| 84 | |||
| 85 | @if length($list) > 0 { | ||
| 86 | @for $i from 1 through length($list) { | ||
| 87 | $result: append($result, nth($list, $i), list-separator($list)); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | @return $result; | ||
| 92 | } | ||
| 93 | |||
| 94 | /// | ||
| 95 | /// Sort numeric items in a list. | ||
| 96 | /// | ||
| 97 | /// The implementation is based on the algorithm on the German Wikipedia article | ||
| 98 | /// about quicksort: https://de.wikipedia.org/wiki/Quicksort#Pseudocode | ||
| 99 | /// | ||
| 100 | /// @param {list} $l | ||
| 101 | /// | ||
| 102 | /// @return {list} Sorted list | ||
| 103 | /// | ||
| 104 | @function iro-quicksort($l, $left: 1, $right: length($l)) { | ||
| 105 | @if $left < $right { | ||
| 106 | $pvr: iro-quicksort-partition($l, $left, $right); | ||
| 107 | $pivot: nth($pvr, 1); | ||
| 108 | $l: nth($pvr, 2); | ||
| 109 | $l: iro-quicksort($l, $left, $pivot); | ||
| 110 | $l: iro-quicksort($l, $pivot + 1, $right); | ||
| 111 | } | ||
| 112 | |||
| 113 | @return $l; | ||
| 114 | } | ||
| 115 | |||
| 116 | /// | ||
| 117 | /// @access private | ||
| 118 | /// | ||
| 119 | @function iro-quicksort-partition($l, $left, $right) { | ||
| 120 | $start: true; | ||
| 121 | $i: $left; | ||
| 122 | $j: $right - 1; | ||
| 123 | $pivot: nth($l, $right); | ||
| 124 | |||
| 125 | @while ($i < $j) or $start { | ||
| 126 | @while (nth($l, $i) < $pivot) and ($i < $right - 1) { | ||
| 127 | $i: $i + 1; | ||
| 128 | } | ||
| 129 | |||
| 130 | @while (nth($l, $j)>= $pivot) and ($j > $left) { | ||
| 131 | $j: $j - 1; | ||
| 132 | } | ||
| 133 | |||
| 134 | @if $i < $j { | ||
| 135 | $i-val: nth($l, $i); | ||
| 136 | $l: set-nth($l, $i, nth($l, $j)); | ||
| 137 | $l: set-nth($l, $j, $i-val); | ||
| 138 | } | ||
| 139 | |||
| 140 | $start: false; | ||
| 141 | } | ||
| 142 | |||
| 143 | @if nth($l, $i) > $pivot { | ||
| 144 | $i-val: nth($l, $i); | ||
| 145 | $l: set-nth($l, $i, nth($l, $right)); | ||
| 146 | $l: set-nth($l, $right, $i-val); | ||
| 147 | } | ||
| 148 | |||
| 149 | @return $i $l; | ||
| 150 | } | ||
| 151 | |||
| 152 | /// | ||
| 153 | /// Try to get the value for the given key from the given map. If it doesn't contain that key, | ||
| 154 | /// return the provided default value instead. | ||
| 155 | /// | ||
| 156 | /// @param {map} $map | ||
| 157 | /// @param {string} $key | ||
| 158 | /// @param {any} $default | ||
| 159 | /// | ||
| 160 | /// @return {any} Either the value assigned to $key or $default | ||
| 161 | /// | ||
| 162 | @function iro-map-get-default($map, $key, $default) { | ||
| 163 | @return if(map-has-key($map, $key), map-get($map, $key), $default); | ||
| 164 | } | ||
| 165 | |||
| 166 | /// | ||
| 167 | /// Get the value for a map within a map (or deeper). | ||
| 168 | /// | ||
| 169 | /// @param {map} $map | ||
| 170 | /// @param {string | list} $key | ||
| 171 | /// @param {any} $default [null] | ||
| 172 | /// | ||
| 173 | /// @return {any} Either the value assigned to $key or $default | ||
| 174 | /// | ||
| 175 | @function iro-map-get-deep($map, $key, $default: null) { | ||
| 176 | $value: null; | ||
| 177 | |||
| 178 | @if type-of($key) == list { | ||
| 179 | $value: $map; | ||
| 180 | |||
| 181 | @each $k in $key { | ||
| 182 | $value: map-get($value, $k); | ||
| 183 | |||
| 184 | @if $value == null { | ||
| 185 | @return $default; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } @else { | ||
| 189 | $value: map-get($map, $key); | ||
| 190 | |||
| 191 | @if $value == null { | ||
| 192 | @return $default; | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | @return $value; | ||
| 197 | } | ||
| 198 | |||
| 199 | /// | ||
| 200 | /// Merge two maps recursively. | ||
| 201 | /// | ||
| 202 | /// @param {map} $map1 | ||
| 203 | /// @param {map} $map2 | ||
| 204 | /// | ||
| 205 | /// @return {map} The result of a recursive merge of $map1 and $map2 | ||
| 206 | /// | ||
| 207 | @function iro-map-merge-recursive($map1, $map2) { | ||
| 208 | @if type-of($map1) != map or type-of($map2) != map { | ||
| 209 | @error 'Two maps expected.'; | ||
| 210 | } | ||
| 211 | |||
| 212 | $result: $map1; | ||
| 213 | |||
| 214 | @each $key, $value in $map2 { | ||
| 215 | @if type-of(map-get($result, $key)) == map and type-of($value) == map { | ||
| 216 | $result: map-merge($result, ($key: iro-map-merge-recursive(map-get($result, $key), $value))); | ||
| 217 | } @else { | ||
| 218 | $result: map-merge($result, ($key: $value)); | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | @return $result; | ||
| 223 | } | ||
| 224 | |||
| 225 | /// | ||
| 226 | /// Get the contents of a map as a user-friendly string representation. | ||
| 227 | /// | ||
| 228 | /// @param {map} $map | ||
| 229 | /// | ||
| 230 | /// @return {string} | ||
| 231 | /// | ||
| 232 | @function iro-map-print($map) { | ||
| 233 | $output: ''; | ||
| 234 | |||
| 235 | @each $key, $value in $map { | ||
| 236 | $value-str: ''; | ||
| 237 | |||
| 238 | @if type-of($value) == map { | ||
| 239 | $value-str: '[ ' + iro-map-print($value) + ' ]'; | ||
| 240 | } @else if type-of($value) == list { | ||
| 241 | $value-str: '[ ' + iro-str-implode($value, ', ') + ' ]'; | ||
| 242 | } @else if type-of($value) == string { | ||
| 243 | $value-str: '\'' + $value + '\''; | ||
| 244 | } @else { | ||
| 245 | $value-str: $value; | ||
| 246 | } | ||
| 247 | |||
| 248 | @if $output == '' { | ||
| 249 | $output: $key + ': ' + $value-str; | ||
| 250 | } @else { | ||
| 251 | $output: $output + ', ' + $key + ': ' + $value-str; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | @return $output; | ||
| 256 | } | ||
| 257 | |||
| 258 | /// | ||
| 259 | /// Check if the given selector ends with one of the provided suffixes. | ||
| 260 | /// | ||
| 261 | /// @param {selector} $selector | ||
| 262 | /// @param {selector} $suffixes | ||
| 263 | /// | ||
| 264 | /// @return {bool} `true` if the selector matches at least one suffix, otherwise `false`. | ||
| 265 | /// | ||
| 266 | @function iro-selector-suffix-match($selector, $suffixes) { | ||
| 267 | $match: true; | ||
| 268 | |||
| 269 | @each $sel in $selector { | ||
| 270 | @if $match { | ||
| 271 | $sel-match: false; | ||
| 272 | |||
| 273 | @each $suffix in $suffixes { | ||
| 274 | @if not $sel-match { | ||
| 275 | $suf-match: true; | ||
| 276 | |||
| 277 | @for $i from 1 through length($suffix) { | ||
| 278 | @if $suf-match and (nth($sel, -$i) != nth($suffix, -$i)) { | ||
| 279 | $suf-match: false; | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | @if $suf-match { | ||
| 284 | $sel-match: true; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | @if not $sel-match { | ||
| 290 | $match: false; | ||
| 291 | } | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 295 | @return $match; | ||
| 296 | } | ||
| 297 | |||
| 298 | /// | ||
| 299 | /// Remove the unit from any variable. | ||
| 300 | /// | ||
| 301 | /// @param {any} $n | ||
| 302 | /// | ||
| 303 | /// @return {number} Unit-less variable | ||
| 304 | /// | ||
| 305 | @function iro-strip-unit($n) { | ||
| 306 | @return $n / ($n * 0 + 1); | ||
| 307 | } | ||
| 308 | |||
| 309 | /// | ||
| 310 | /// Convert a pixel value to a rem value. | ||
| 311 | /// | ||
| 312 | /// @param {number} $size - Pixel value to convert | ||
| 313 | /// @param {number} $base [$iro-root-size] - Reference base font size used for conversion | ||
| 314 | /// | ||
| 315 | /// @return {number} Pixel value converted to rem | ||
| 316 | /// | ||
| 317 | @function iro-px-to-rem($size, $base: $iro-root-size) { | ||
| 318 | @return $size / $base * 1rem; | ||
| 319 | } | ||
| 320 | |||
| 321 | /// | ||
| 322 | /// A mixin with the sole purpose of letting you use temporary variables without polluting the global namespace. | ||
| 323 | /// | ||
| 324 | /// @content | ||
| 325 | /// | ||
| 326 | @mixin iro-execute { | ||
| 327 | @content; | ||
| 328 | } | ||
| diff --git a/src/_gradients.scss b/src/_gradients.scss new file mode 100644 index 0000000..7c52d63 --- /dev/null +++ b/src/_gradients.scss | |||
| @@ -0,0 +1,600 @@ | |||
| 1 | //// | ||
| 2 | /// Smoother background gradients. | ||
| 3 | /// | ||
| 4 | /// The default background gradients produced by any browser have a quite harsh transition between | ||
| 5 | /// colors. This is especially apparent if you, for example, use a strong fade-out gradient to make | ||
| 6 | /// text in front of a background more readable. | ||
| 7 | /// | ||
| 8 | /// The function in this file generates smoother gradients by using easing functions of the user's | ||
| 9 | /// choice. | ||
| 10 | /// It's essentially a more flexible alternative to the PostCSS plugin "PostCSS Easing Gradients": | ||
| 11 | /// https://github.com/larsenwork/postcss-easing-gradients | ||
| 12 | /// | ||
| 13 | /// @group Background gradients | ||
| 14 | /// | ||
| 15 | /// @access public | ||
| 16 | //// | ||
| 17 | |||
| 18 | /// | ||
| 19 | /// Number of intermediate color stops generated to achieve easing. | ||
| 20 | /// A higher value results in better quality, but also much more generated code. | ||
| 21 | /// | ||
| 22 | /// @type number | ||
| 23 | /// | ||
| 24 | $iro-easing-gradient-steps: 10 !default; | ||
| 25 | |||
| 26 | /// | ||
| 27 | /// Generate a new easing background gradient. | ||
| 28 | /// This function is intended to be as similar as possible to the newly proposed syntax for | ||
| 29 | /// linear-gradient and radial-gradient which includes easing hints. | ||
| 30 | /// | ||
| 31 | /// @param {string} $type - Either 'linear' or 'radial', which means the gradient will be either a linear-gradient or a radial-gradient. | ||
| 32 | /// @param {string} $dir - The direction of the gradient. Depending on $type, this value must be a valid direction for linear-gradient or radial-gradient. | ||
| 33 | /// @param {color | list} $stop - A color stop as used for linear-gradient or radial-gradient. | ||
| 34 | /// @param {arglist} $stops - More color stops as used for linear-gradient or radial-gradient. Between two color stops, you may also define an easing hint such as `ease-in-out`, `cubic-bezier 0.42 0 0.58 1`, `steps 3 jump-end`, and so on. | ||
| 35 | /// | ||
| 36 | /// @return {string} A linear-gradient or radial-gradient with an alternative transitioning behavior. | ||
| 37 | /// | ||
| 38 | /// @throw If $type is invalid | ||
| 39 | /// | ||
| 40 | /// @link https://github.com/w3c/csswg-drafts/issues/1332 The new CSSWG proposal | ||
| 41 | /// | ||
| 42 | /// @example scss - A smoother linear gradient | ||
| 43 | /// .background { | ||
| 44 | /// background-image: iro-easing-gradient( | ||
| 45 | /// linear, | ||
| 46 | /// to top, | ||
| 47 | /// #000, | ||
| 48 | /// in-out-sine, | ||
| 49 | /// transparent | ||
| 50 | /// ); | ||
| 51 | /// } | ||
| 52 | /// | ||
| 53 | /// // Generates: | ||
| 54 | /// | ||
| 55 | /// .background { | ||
| 56 | /// background-image: linear-gradient( | ||
| 57 | /// to top, | ||
| 58 | /// black 0%, | ||
| 59 | /// rgba(0, 0, 0, 0.975528) 10%, | ||
| 60 | /// rgba(0, 0, 0, 0.904508) 20%, | ||
| 61 | /// rgba(0, 0, 0, 0.793893) 30%, | ||
| 62 | /// rgba(0, 0, 0, 0.654508) 40%, | ||
| 63 | /// rgba(0, 0, 0, 0.5) 50%, | ||
| 64 | /// rgba(0, 0, 0, 0.345492) 60%, | ||
| 65 | /// rgba(0, 0, 0, 0.206107) 70%, | ||
| 66 | /// rgba(0, 0, 0, 0.0954915) 80%, | ||
| 67 | /// rgba(0, 0, 0, 0.0244717) 90%, | ||
| 68 | /// rgba(0, 0, 0, 3.78257e-11) 100% | ||
| 69 | /// ); | ||
| 70 | /// } | ||
| 71 | /// | ||
| 72 | /// @example scss - A smoother radial gradient | ||
| 73 | /// .background { | ||
| 74 | /// background-image: iro-easing-gradient( | ||
| 75 | /// radial, | ||
| 76 | /// 50em 16em at 0 0, | ||
| 77 | /// #000, | ||
| 78 | /// in-out-sine, | ||
| 79 | /// transparent | ||
| 80 | /// ); | ||
| 81 | /// } | ||
| 82 | /// | ||
| 83 | /// // Generates: | ||
| 84 | /// | ||
| 85 | /// .background { | ||
| 86 | /// background-image: radial-gradient( | ||
| 87 | /// 50em 16em at 0 0, | ||
| 88 | /// black 0%, | ||
| 89 | /// rgba(0, 0, 0, 0.975528) 10%, | ||
| 90 | /// rgba(0, 0, 0, 0.904508) 20%, | ||
| 91 | /// rgba(0, 0, 0, 0.793893) 30%, | ||
| 92 | /// rgba(0, 0, 0, 0.654508) 40%, | ||
| 93 | /// rgba(0, 0, 0, 0.5) 50%, | ||
| 94 | /// rgba(0, 0, 0, 0.345492) 60%, | ||
| 95 | /// rgba(0, 0, 0, 0.206107) 70%, | ||
| 96 | /// rgba(0, 0, 0, 0.0954915) 80%, | ||
| 97 | /// rgba(0, 0, 0, 0.0244717) 90%, | ||
| 98 | /// rgba(0, 0, 0, 3.78257e-11) 100% | ||
| 99 | /// ); | ||
| 100 | /// } | ||
| 101 | /// | ||
| 102 | /// @example scss - A smoother linear gradient with complex color positions | ||
| 103 | /// .background { | ||
| 104 | /// background-image: iro-easing-gradient( | ||
| 105 | /// linear, | ||
| 106 | /// to top, | ||
| 107 | /// #000 20%, | ||
| 108 | /// in-out-sine, | ||
| 109 | /// transparent calc(20% + 25em) | ||
| 110 | /// ); | ||
| 111 | /// } | ||
| 112 | /// | ||
| 113 | /// // Generates: | ||
| 114 | /// | ||
| 115 | /// .background { | ||
| 116 | /// background-image: linear-gradient( | ||
| 117 | /// to top, | ||
| 118 | /// black 20%, | ||
| 119 | /// rgba(0, 0, 0, 0.975528) calc(20% + (20% + 25em - 20%) * 0.1), | ||
| 120 | /// rgba(0, 0, 0, 0.904508) calc(20% + (20% + 25em - 20%) * 0.2), | ||
| 121 | /// rgba(0, 0, 0, 0.793893) calc(20% + (20% + 25em - 20%) * 0.3), | ||
| 122 | /// rgba(0, 0, 0, 0.654508) calc(20% + (20% + 25em - 20%) * 0.4), | ||
| 123 | /// rgba(0, 0, 0, 0.5) calc(20% + (20% + 25em - 20%) * 0.5), | ||
| 124 | /// rgba(0, 0, 0, 0.345492) calc(20% + (20% + 25em - 20%) * 0.6), | ||
| 125 | /// rgba(0, 0, 0, 0.206107) calc(20% + (20% + 25em - 20%) * 0.7), | ||
| 126 | /// rgba(0, 0, 0, 0.0954915) calc(20% + (20% + 25em - 20%) * 0.8), | ||
| 127 | /// rgba(0, 0, 0, 0.0244717) calc(20% + (20% + 25em - 20%) * 0.9), | ||
| 128 | /// transparent calc(20% + 25em)) | ||
| 129 | /// ); | ||
| 130 | /// } | ||
| 131 | /// | ||
| 132 | @function iro-easing-gradient($type, $dir, $stop, $stops...) { | ||
| 133 | $pos-template: null; | ||
| 134 | $stops: iro-list-prepend($stops, $stop); | ||
| 135 | |||
| 136 | $last-positioned-stop: 1; | ||
| 137 | $generated-stops: (); | ||
| 138 | |||
| 139 | // | ||
| 140 | // Generate gradient | ||
| 141 | // | ||
| 142 | |||
| 143 | @for $i from 1 through length($stops) { | ||
| 144 | $stop: nth($stops, $i); | ||
| 145 | |||
| 146 | @if $i == 1 { | ||
| 147 | @if not iro-easing-gradient-is-color-stop($stop) { | ||
| 148 | @error 'The first color stop argument must be a color stop.'; | ||
| 149 | } | ||
| 150 | |||
| 151 | @if type-of($stop) == color { | ||
| 152 | // | ||
| 153 | // The first color stop is unpositioned. The default position for the first | ||
| 154 | // color stop is 0, which is explicitly added for easier calculations. | ||
| 155 | // | ||
| 156 | |||
| 157 | $stop: $stop 0; | ||
| 158 | $stops: set-nth($stops, $i, $stop); | ||
| 159 | } | ||
| 160 | |||
| 161 | $generated-stops: append($generated-stops, iro-str-implode($stop, ' ')); | ||
| 162 | } @else if iro-easing-gradient-is-positioned-color-stop($stop) or ($i == length($stops)) { | ||
| 163 | @if not iro-easing-gradient-is-color-stop($stop) { | ||
| 164 | @error 'The last color stop argument must be a color stop.'; | ||
| 165 | } | ||
| 166 | |||
| 167 | // | ||
| 168 | // Either the current stops list item is a positioned color stop, or the end of | ||
| 169 | // the stops list has been reached. | ||
| 170 | // | ||
| 171 | |||
| 172 | @if (type-of($stop) == color) and ($i == length($stops)) { | ||
| 173 | // | ||
| 174 | // The current stop is an unpositioned color stop, which means this is the end | ||
| 175 | // of the stops list. The default position for the last color stop is 100%, which | ||
| 176 | // is explicitly added for easier calculations. | ||
| 177 | // | ||
| 178 | |||
| 179 | $stop: $stop 100%; | ||
| 180 | $stops: set-nth($stops, $i, $stop); | ||
| 181 | } | ||
| 182 | |||
| 183 | // | ||
| 184 | // Now the current color stop is guaranteed to be a positioned color stop. | ||
| 185 | // | ||
| 186 | |||
| 187 | @if $i > $last-positioned-stop + 1 { | ||
| 188 | // | ||
| 189 | // There is at least one stops list item (unpositioned color stop or easing function) | ||
| 190 | // between the last positioned color stop and the current stops list item. Interpolate | ||
| 191 | // the positions of all stops list items that are color stops. | ||
| 192 | // | ||
| 193 | |||
| 194 | $interpolated-stops: iro-easing-gradient-interpolate-stop-positions( | ||
| 195 | nth($stops, $last-positioned-stop), | ||
| 196 | iro-list-slice($stops, $last-positioned-stop + 1, $i - 1), | ||
| 197 | $stop | ||
| 198 | ); | ||
| 199 | |||
| 200 | $new-stops: join( | ||
| 201 | iro-list-slice($stops, 1, $last-positioned-stop), | ||
| 202 | $interpolated-stops | ||
| 203 | ); | ||
| 204 | $new-stops: join( | ||
| 205 | $new-stops, | ||
| 206 | iro-list-slice($stops, $i) | ||
| 207 | ); | ||
| 208 | $stops: $new-stops; | ||
| 209 | } | ||
| 210 | |||
| 211 | // | ||
| 212 | // Now all color stops between this one and the last positioned one have | ||
| 213 | // interpolated positions. | ||
| 214 | // Next task is to perform an easing transition between all color stops that | ||
| 215 | // have an easing function specified. The rest can be left alone since the | ||
| 216 | // browser will automatically apply a linear transition between them. | ||
| 217 | // | ||
| 218 | |||
| 219 | $j: $last-positioned-stop + 1; | ||
| 220 | @while $j <= $i { | ||
| 221 | $easing: null; | ||
| 222 | $prev-stop: nth($stops, $j - 1); | ||
| 223 | $next-stop: nth($stops, $j); | ||
| 224 | |||
| 225 | @if not iro-easing-gradient-is-color-stop($next-stop) { | ||
| 226 | $j: $j + 1; | ||
| 227 | |||
| 228 | $easing: $next-stop; | ||
| 229 | $next-stop: nth($stops, $j); | ||
| 230 | |||
| 231 | @if not iro-easing-gradient-is-color-stop($next-stop) { | ||
| 232 | @error 'There can be at most one interpolation hint between to color stops.'; | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | @if $easing != null { | ||
| 237 | @if type-of($easing) == number { | ||
| 238 | @error 'Midpoint shifts are not supported.'; | ||
| 239 | } | ||
| 240 | |||
| 241 | $easing-func: null; | ||
| 242 | $easing-args: (); | ||
| 243 | |||
| 244 | @if type-of($easing) == list { | ||
| 245 | $easing-args: iro-list-slice($easing, 2); | ||
| 246 | $easing: nth($easing, 1); | ||
| 247 | } | ||
| 248 | |||
| 249 | $generated-stops: join( | ||
| 250 | $generated-stops, | ||
| 251 | iro-easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args) | ||
| 252 | ); | ||
| 253 | } @else { | ||
| 254 | $generated-stops: append($generated-stops, iro-str-implode($next-stop, ' ')); | ||
| 255 | } | ||
| 256 | |||
| 257 | $j: $j + 1; | ||
| 258 | } | ||
| 259 | |||
| 260 | $last-positioned-stop: $i; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | @if $type == 'linear' { | ||
| 265 | @return linear-gradient($dir, unquote(iro-str-implode($generated-stops, ', '))); | ||
| 266 | } @else if $type == 'radial' { | ||
| 267 | @return radial-gradient($dir, unquote(iro-str-implode($generated-stops, ', '))); | ||
| 268 | } @else { | ||
| 269 | @error 'Invalid gradient type: #{inspect($type)}.'; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | /// | ||
| 274 | /// Alias for iro-easing-gradient('linear',...) | ||
| 275 | /// | ||
| 276 | /// @see {function} iro-easing-gradient | ||
| 277 | /// | ||
| 278 | @function iro-easing-linear-gradient($dir, $stop, $stops...) { | ||
| 279 | @return iro-easing-gradient('linear', $dir, $stop, $stops...); | ||
| 280 | } | ||
| 281 | |||
| 282 | /// | ||
| 283 | /// Alias for iro-easing-gradient('radial',...) | ||
| 284 | /// | ||
| 285 | /// @see {function} iro-easing-gradient | ||
| 286 | /// | ||
| 287 | @function iro-easing-radial-gradient($dir, $stop, $stops...) { | ||
| 288 | @return iro-easing-gradient('radial', $dir, $stop, $stops...); | ||
| 289 | } | ||
| 290 | |||
| 291 | /// | ||
| 292 | /// Generate a smooth transition from one color stop to another using the provided easing function. | ||
| 293 | /// | ||
| 294 | /// @access private | ||
| 295 | /// | ||
| 296 | @function iro-easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args: ()) { | ||
| 297 | @if $easing == 'steps' { | ||
| 298 | $steps: null; | ||
| 299 | $jump: null; | ||
| 300 | |||
| 301 | @if length($easing-args) > 1 { | ||
| 302 | $steps: nth($easing-args, 1); | ||
| 303 | $jump: nth($easing-args, 2); | ||
| 304 | } @else { | ||
| 305 | $steps: nth($easing-args, 1); | ||
| 306 | $jump: jump-end; | ||
| 307 | } | ||
| 308 | |||
| 309 | @return iro-easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump); | ||
| 310 | } @else { | ||
| 311 | $easing-func: null; | ||
| 312 | @if function-exists('iro-' + $easing) { | ||
| 313 | $easing-func: get-function('iro-' + $easing); | ||
| 314 | } @else { | ||
| 315 | $easing-func: get-function($easing); | ||
| 316 | } | ||
| 317 | |||
| 318 | @return iro-easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args); | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | /// | ||
| 323 | /// Generate a smooth transition from one color stop to another using the provided cubic-bezier function. | ||
| 324 | /// | ||
| 325 | /// @access private | ||
| 326 | /// | ||
| 327 | @function iro-easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args: ()) { | ||
| 328 | $prev-stop-color: nth($prev-stop, 1); | ||
| 329 | $prev-stop-pos: nth($prev-stop, 2); | ||
| 330 | $next-stop-color: nth($next-stop, 1); | ||
| 331 | $next-stop-pos: nth($next-stop, 2); | ||
| 332 | |||
| 333 | $stops: (); | ||
| 334 | |||
| 335 | @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) { | ||
| 336 | // | ||
| 337 | // The transition color stop positions can be statically calculated. | ||
| 338 | // | ||
| 339 | |||
| 340 | $distance: $next-stop-pos - $prev-stop-pos; | ||
| 341 | |||
| 342 | @for $i from 1 through $iro-easing-gradient-steps { | ||
| 343 | $perc: $i / $iro-easing-gradient-steps; | ||
| 344 | |||
| 345 | $color: null; | ||
| 346 | $pos: $prev-stop-pos + $perc * $distance; | ||
| 347 | @if $perc == 1 { | ||
| 348 | $color: $next-stop-color; | ||
| 349 | } @else { | ||
| 350 | $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%); | ||
| 351 | } | ||
| 352 | |||
| 353 | $stops: append($stops, $color + ' ' + $pos); | ||
| 354 | } | ||
| 355 | } @else { | ||
| 356 | // | ||
| 357 | // The transition color stop positions have to be dynamically calculated with the calc() function. | ||
| 358 | // | ||
| 359 | |||
| 360 | @if type-of($prev-stop-pos) != number { | ||
| 361 | // must be calc() | ||
| 362 | @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) { | ||
| 363 | @error 'Invalid color stop position: #{inspect($prev-stop-pos)}'; | ||
| 364 | } | ||
| 365 | |||
| 366 | $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1); | ||
| 367 | } | ||
| 368 | |||
| 369 | @if type-of($next-stop-pos) != number { | ||
| 370 | // must be calc() | ||
| 371 | @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) { | ||
| 372 | @error 'Invalid color stop position: #{inspect($next-stop-pos)}'; | ||
| 373 | } | ||
| 374 | |||
| 375 | $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1); | ||
| 376 | } | ||
| 377 | |||
| 378 | @for $i from 1 through $iro-easing-gradient-steps { | ||
| 379 | $perc: $i / $iro-easing-gradient-steps; | ||
| 380 | |||
| 381 | $color: null; | ||
| 382 | $pos: null; | ||
| 383 | @if $perc == 1 { | ||
| 384 | $color: $next-stop-color; | ||
| 385 | $pos: calc(#{$next-stop-pos}); | ||
| 386 | } @else { | ||
| 387 | $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%); | ||
| 388 | $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc}); | ||
| 389 | } | ||
| 390 | |||
| 391 | $stops: append($stops, $color + ' ' + $pos); | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | @return $stops; | ||
| 396 | } | ||
| 397 | |||
| 398 | /// | ||
| 399 | /// Generate a step transition from one color stop to another. | ||
| 400 | /// | ||
| 401 | /// @access private | ||
| 402 | /// | ||
| 403 | @function iro-easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump: jump-end) { | ||
| 404 | $prev-stop-color: nth($prev-stop, 1); | ||
| 405 | $prev-stop-pos: nth($prev-stop, 2); | ||
| 406 | $next-stop-color: nth($next-stop, 1); | ||
| 407 | $next-stop-pos: nth($next-stop, 2); | ||
| 408 | |||
| 409 | $stops: (); | ||
| 410 | |||
| 411 | @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) { | ||
| 412 | // | ||
| 413 | // The transition color stop positions can be statically calculated. | ||
| 414 | // | ||
| 415 | |||
| 416 | $distance: $next-stop-pos - $prev-stop-pos; | ||
| 417 | |||
| 418 | @for $i from 1 through $steps { | ||
| 419 | $x1: ($i - 1) / $steps; | ||
| 420 | $x2: $i / $steps; | ||
| 421 | $y: null; | ||
| 422 | |||
| 423 | @if $jump == jump-start { | ||
| 424 | $y: $i / $steps; | ||
| 425 | } @else if $jump == jump-end { | ||
| 426 | $y: ($i - 1) / $steps; | ||
| 427 | } @else if $jump == jump-both { | ||
| 428 | $y: $i / ($steps + 1); | ||
| 429 | } @else if $jump == jump-none { | ||
| 430 | $y: ($i - 1) / ($steps - 1); | ||
| 431 | } @else { | ||
| 432 | @error 'Invalid $jump: #{inspect($jump)}'; | ||
| 433 | } | ||
| 434 | |||
| 435 | $color: null; | ||
| 436 | $pos1: if($x1 == 0, $prev-stop-pos, $prev-stop-pos + $x1 * $distance); | ||
| 437 | $pos2: if($x2 == 1, $next-stop-pos, $prev-stop-pos + $x2 * $distance); | ||
| 438 | |||
| 439 | @if $y == 0 { | ||
| 440 | $color: $prev-stop-color; | ||
| 441 | } @else if $y == 1 { | ||
| 442 | $color: $next-stop-color; | ||
| 443 | } @else { | ||
| 444 | $color: mix($next-stop-color, $prev-stop-color, $y * 100%); | ||
| 445 | } | ||
| 446 | |||
| 447 | $stops: append($stops, $color + ' ' + $pos1); | ||
| 448 | $stops: append($stops, $color + ' ' + $pos2); | ||
| 449 | } | ||
| 450 | } @else { | ||
| 451 | // | ||
| 452 | // The transition color stop positions have to be dynamically calculated with the calc() function. | ||
| 453 | // | ||
| 454 | |||
| 455 | @if type-of($prev-stop-pos) != number { | ||
| 456 | // must be calc() | ||
| 457 | @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) { | ||
| 458 | @error 'Invalid color stop position: #{inspect($prev-stop-pos)}'; | ||
| 459 | } | ||
| 460 | |||
| 461 | $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1); | ||
| 462 | } | ||
| 463 | |||
| 464 | @if type-of($next-stop-pos) != number { | ||
| 465 | // must be calc() | ||
| 466 | @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) { | ||
| 467 | @error 'Invalid color stop position: #{inspect($next-stop-pos)}'; | ||
| 468 | } | ||
| 469 | |||
| 470 | $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1); | ||
| 471 | } | ||
| 472 | |||
| 473 | @for $i from 1 through $steps { | ||
| 474 | $x1: ($i - 1) / $steps; | ||
| 475 | $x2: $i / $steps; | ||
| 476 | $y: null; | ||
| 477 | |||
| 478 | @if $jump == jump-start { | ||
| 479 | $y: $i / $steps; | ||
| 480 | } @else if $jump == jump-end { | ||
| 481 | $y: ($i - 1) / $steps; | ||
| 482 | } @else if $jump == jump-both { | ||
| 483 | $y: $i / ($steps + 1); | ||
| 484 | } @else if $jump == jump-none { | ||
| 485 | $y: ($i - 1) / ($steps - 1); | ||
| 486 | } @else { | ||
| 487 | @error 'Invalid $jump: #{inspect($jump)}'; | ||
| 488 | } | ||
| 489 | |||
| 490 | $color: null; | ||
| 491 | $pos1: if($x1 == 0, $prev-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x1})); | ||
| 492 | $pos2: if($x2 == 1, $next-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x2})); | ||
| 493 | |||
| 494 | @if $y == 0 { | ||
| 495 | $color: $prev-stop-color; | ||
| 496 | } @else if $y == 1 { | ||
| 497 | $color: $next-stop-color; | ||
| 498 | } @else { | ||
| 499 | $color: mix($next-stop-color, $prev-stop-color, $y * 100%); | ||
| 500 | } | ||
| 501 | |||
| 502 | $stops: append($stops, $color + ' ' + $pos1); | ||
| 503 | $stops: append($stops, $color + ' ' + $pos2); | ||
| 504 | } | ||
| 505 | } | ||
| 506 | |||
| 507 | @return $stops; | ||
| 508 | } | ||
| 509 | |||
| 510 | /// | ||
| 511 | /// Interpolate the positions of multiple color stops between two color stops whose positions are set. | ||
| 512 | /// | ||
| 513 | /// @access private | ||
| 514 | /// | ||
| 515 | @function iro-easing-gradient-interpolate-stop-positions($prev-stop, $stops, $next-stop) { | ||
| 516 | $prev-stop-pos: nth($prev-stop, 2); | ||
| 517 | $next-stop-pos: nth($next-stop, 2); | ||
| 518 | |||
| 519 | $stops-num: 0; | ||
| 520 | @for $i from 1 through length($stops) { | ||
| 521 | $stop: nth($stops, $i); | ||
| 522 | @if iro-easing-gradient-is-color-stop($stop) { | ||
| 523 | $stops-num: $stops-num + 1; | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | $i: 1; | ||
| 528 | $cur-stop-num: 1; | ||
| 529 | |||
| 530 | @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) { | ||
| 531 | // | ||
| 532 | // The color stop positions can be statically calculated. | ||
| 533 | // | ||
| 534 | |||
| 535 | $distance: $next-stop-pos - $prev-stop-pos; | ||
| 536 | |||
| 537 | @for $i from 1 through length($stops) { | ||
| 538 | $stop: nth($stops, $i); | ||
| 539 | @if iro-easing-gradient-is-color-stop($stop) { | ||
| 540 | $pos: $prev-stop-pos + $distance / ($stops-num + 1) * $cur-stop-num; | ||
| 541 | $stops: set-nth($stops, $i, $stop $pos); | ||
| 542 | |||
| 543 | $cur-stop-num: $cur-stop-num + 1; | ||
| 544 | } | ||
| 545 | } | ||
| 546 | } @else { | ||
| 547 | // | ||
| 548 | // The color stop positions have to be dynamically calculated with the calc() function. | ||
| 549 | // | ||
| 550 | |||
| 551 | @if type-of($prev-stop-pos) != number { | ||
| 552 | // must be calc() | ||
| 553 | @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) { | ||
| 554 | @error 'Invalid color stop position: #{inspect($prev-stop-pos)}'; | ||
| 555 | } | ||
| 556 | |||
| 557 | $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1); | ||
| 558 | } | ||
| 559 | |||
| 560 | @if type-of($next-stop-pos) != number { | ||
| 561 | // must be calc() | ||
| 562 | @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) { | ||
| 563 | @error 'Invalid color stop position: #{inspect($next-stop-pos)}'; | ||
| 564 | } | ||
| 565 | |||
| 566 | $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1); | ||
| 567 | } | ||
| 568 | |||
| 569 | @for $i from 1 through length($stops) { | ||
| 570 | $stop: nth($stops, $i); | ||
| 571 | @if iro-easing-gradient-is-color-stop($stop) { | ||
| 572 | $perc: $cur-stop-num / ($stops-num + 1); | ||
| 573 | $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc}); | ||
| 574 | $stops: set-nth($stops, $i, $stop $pos); | ||
| 575 | |||
| 576 | $cur-stop-num: $cur-stop-num + 1; | ||
| 577 | } | ||
| 578 | } | ||
| 579 | } | ||
| 580 | |||
| 581 | @return $stops; | ||
| 582 | } | ||
| 583 | |||
| 584 | /// | ||
| 585 | /// Check if the input is a valid color stop. | ||
| 586 | /// | ||
| 587 | /// @access private | ||
| 588 | /// | ||
| 589 | @function iro-easing-gradient-is-color-stop($input) { | ||
| 590 | @return (type-of($input) == color) or iro-easing-gradient-is-positioned-color-stop($input); | ||
| 591 | } | ||
| 592 | |||
| 593 | /// | ||
| 594 | /// Check if the input is a valid positioned color stop. | ||
| 595 | /// | ||
| 596 | /// @access private | ||
| 597 | /// | ||
| 598 | @function iro-easing-gradient-is-positioned-color-stop($input) { | ||
| 599 | @return (type-of($input) == list) and (type-of(nth($input, 1)) == color); | ||
| 600 | } | ||
| diff --git a/src/_harmony.scss b/src/_harmony.scss new file mode 100644 index 0000000..c3c8633 --- /dev/null +++ b/src/_harmony.scss | |||
| @@ -0,0 +1,94 @@ | |||
| 1 | //// | ||
| 2 | /// Harmony. | ||
| 3 | /// | ||
| 4 | /// Contains functions to make a design appear more harmonic. | ||
| 5 | /// | ||
| 6 | /// @group Harmony | ||
| 7 | /// | ||
| 8 | /// @access public | ||
| 9 | //// | ||
| 10 | |||
| 11 | /// | ||
| 12 | /// Adjust a value to a modular scale. | ||
| 13 | /// | ||
| 14 | /// For a more sophisticated solution, check out [modularscale-sass](https://github.com/modularscale/modularscale-sass). | ||
| 15 | /// | ||
| 16 | /// @link http://alistapart.com/article/more-meaningful-typography An article about modular scales by Tim Brown | ||
| 17 | /// | ||
| 18 | /// @param {number} $times - Number of iterations. If positive, $base will be multiplied with $ratio. If negative, $base will be divided by $ratio. | ||
| 19 | /// @param {number | list} $base - Single base value or, for a multi-stranded modular scale, a list of base values | ||
| 20 | /// @param {number} $ratio - Ratio | ||
| 21 | /// | ||
| 22 | /// @return {number} | ||
| 23 | /// | ||
| 24 | @function iro-harmony-modular-scale($times, $base, $ratio) { | ||
| 25 | @if type-of($base) == number { | ||
| 26 | @return $base * iro-math-pow($ratio, $times); | ||
| 27 | } | ||
| 28 | |||
| 29 | $main-base: nth($base, 1); | ||
| 30 | $norm-bases: (); | ||
| 31 | |||
| 32 | @each $b in iro-list-slice($base, 2) { | ||
| 33 | @if $b > $main-base { | ||
| 34 | @while $b > $main-base { | ||
| 35 | $b: $b / $ratio; | ||
| 36 | } | ||
| 37 | $b: $b * $ratio; | ||
| 38 | } @else if $b < $main-base { | ||
| 39 | @while $b < $main-base { | ||
| 40 | $b: $b * $ratio; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | $norm-bases: append($norm-bases, $b); | ||
| 45 | } | ||
| 46 | |||
| 47 | $all-bases: append($norm-bases, $main-base); | ||
| 48 | $all-bases: iro-quicksort($all-bases); | ||
| 49 | |||
| 50 | $base-index: $times % length($all-bases) + 1; | ||
| 51 | $exp: floor($times / length($all-bases)); | ||
| 52 | |||
| 53 | @return nth($all-bases, $base-index) * iro-math-pow($ratio, $exp); | ||
| 54 | } | ||
| 55 | |||
| 56 | /// | ||
| 57 | /// Combine responsive properties with modular scales to achieve responsive modular scales. | ||
| 58 | /// | ||
| 59 | /// @param {string | list} $props - Property or list of properties to set | ||
| 60 | /// @param {number} $times - Number of iterations. See iro-harmony-modular-scale for more information. | ||
| 61 | /// @param {number} $responsive-map - A map with keys = viewports and values = modular scales | ||
| 62 | /// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next | ||
| 63 | /// | ||
| 64 | /// @see {function} iro-harmony-modular-scale | ||
| 65 | /// | ||
| 66 | /// @example scss - Responsive font sizes between 2 viewports based on modular scales | ||
| 67 | /// $ms: ( | ||
| 68 | /// 320px: (1rem 2rem, 1.1), | ||
| 69 | /// 640px: (1rem 2rem, 1.2) | ||
| 70 | /// ); | ||
| 71 | /// | ||
| 72 | /// h1 { | ||
| 73 | /// @include iro-responsive-modular-scale(font-size, 3, $ms); | ||
| 74 | /// } | ||
| 75 | /// | ||
| 76 | /// h2 { | ||
| 77 | /// @include iro-responsive-modular-scale(font-size, 2, $ms); | ||
| 78 | /// } | ||
| 79 | /// | ||
| 80 | /// h3 { | ||
| 81 | /// @include iro-responsive-modular-scale(font-size, 1, $ms); | ||
| 82 | /// } | ||
| 83 | /// | ||
| 84 | @mixin iro-responsive-modular-scale($props, $times, $responsive-map, $fluid: true) { | ||
| 85 | $new-map: (); | ||
| 86 | |||
| 87 | @each $key, $value in $responsive-map { | ||
| 88 | $new-map: map-merge($new-map, ( | ||
| 89 | $key: iro-harmony-modular-scale($times, $value...) | ||
| 90 | )); | ||
| 91 | } | ||
| 92 | |||
| 93 | @include iro-responsive-property($props, $new-map, $fluid); | ||
| 94 | } | ||
| diff --git a/src/_math.scss b/src/_math.scss new file mode 100644 index 0000000..9b71bf6 --- /dev/null +++ b/src/_math.scss | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | //// | ||
| 2 | /// Basic mathematical functions. | ||
| 3 | /// | ||
| 4 | /// @group Math functions | ||
| 5 | /// | ||
| 6 | /// @access public | ||
| 7 | //// | ||
| 8 | |||
| 9 | /// | ||
| 10 | /// Perform exponentiation. Only integer exponents are supported. | ||
| 11 | /// | ||
| 12 | /// @param {number} $base | ||
| 13 | /// @param {number} $exp | ||
| 14 | /// | ||
| 15 | /// @return {number} | ||
| 16 | /// | ||
| 17 | /// @example scss - Exponentiation with a positive exponent | ||
| 18 | /// $result: iro-math-pow(3, 2); // The value of $result is 3^2 = 9 | ||
| 19 | /// | ||
| 20 | /// @example scss - Exponentiation with a negative exponent | ||
| 21 | /// $result: iro-math-pow(2, -3); // The value of $result is 1/(2^3) = 1/8 | ||
| 22 | /// | ||
| 23 | @function iro-math-pow($base, $exp) { | ||
| 24 | $value: 1; | ||
| 25 | |||
| 26 | @if $exp > 0 { | ||
| 27 | @for $i from 1 through $exp { | ||
| 28 | $value: $value * $base; | ||
| 29 | } | ||
| 30 | } @else if $exp < 0 { | ||
| 31 | @for $i from 1 through -$exp { | ||
| 32 | $value: $value / $base; | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | @return $value; | ||
| 37 | } | ||
| 38 | |||
| 39 | /// | ||
| 40 | /// Clamp a number between a minimum and maximum value. | ||
| 41 | /// | ||
| 42 | /// @param {number} $value - Value to clamp | ||
| 43 | /// @param {number} $min - Minimum value | ||
| 44 | /// @param {number} $max - Maximum value | ||
| 45 | /// | ||
| 46 | /// @return {number} | ||
| 47 | /// | ||
| 48 | /// @example scss | ||
| 49 | /// $result: iro-math-clamp(20, 0, 10); // The value of $result is 10 | ||
| 50 | /// | ||
| 51 | /// @example scss | ||
| 52 | /// $result: iro-math-clamp(50, 20, 100); // The value of $result is 50 | ||
| 53 | /// | ||
| 54 | @function iro-math-clamp($value, $min, $max) { | ||
| 55 | @if $value < $min { | ||
| 56 | @return $min; | ||
| 57 | } | ||
| 58 | @if $value > $max { | ||
| 59 | @return $max; | ||
| 60 | } | ||
| 61 | @return $value; | ||
| 62 | } | ||
| diff --git a/src/_props.scss b/src/_props.scss new file mode 100644 index 0000000..7377c88 --- /dev/null +++ b/src/_props.scss | |||
| @@ -0,0 +1,281 @@ | |||
| 1 | //// | ||
| 2 | /// Property trees. | ||
| 3 | /// | ||
| 4 | /// Property trees allow you to organize properties in a tree structure (internally nested maps). | ||
| 5 | /// The intended use is to store all your properties at the beginning and for the rest of the | ||
| 6 | /// stylesheet you just get them. | ||
| 7 | /// | ||
| 8 | /// @group Property trees | ||
| 9 | /// | ||
| 10 | /// @access public | ||
| 11 | //// | ||
| 12 | |||
| 13 | /// | ||
| 14 | /// The maximum depth of resolved iro-prop-ref() references. | ||
| 15 | /// | ||
| 16 | /// @type number | ||
| 17 | /// | ||
| 18 | $iro-props-native-assing-max-depth: 2 !default; | ||
| 19 | |||
| 20 | /// | ||
| 21 | /// Indicate if property names must start with two dashes (--). | ||
| 22 | /// This is required if property trees are also used for native CSS custom properties. | ||
| 23 | /// | ||
| 24 | /// @type bool | ||
| 25 | /// | ||
| 26 | $iro-props-enforce-double-dashes: true !default; | ||
| 27 | |||
| 28 | /// | ||
| 29 | /// Default tree name to use if no name is specified. | ||
| 30 | /// | ||
| 31 | /// @type string | ||
| 32 | /// | ||
| 33 | $iro-props-default-tree: 'default' !default; | ||
| 34 | |||
| 35 | /// | ||
| 36 | /// List of all created property trees. | ||
| 37 | /// | ||
| 38 | /// @type list | ||
| 39 | /// | ||
| 40 | /// @access private | ||
| 41 | /// | ||
| 42 | $iro-props-trees: (); | ||
| 43 | |||
| 44 | /// | ||
| 45 | /// Save a property tree. If a tree with the sane name already exists, the trees | ||
| 46 | /// will be merged. | ||
| 47 | /// | ||
| 48 | /// @param {map} $map - Map containing properties | ||
| 49 | /// @param {string} $tree [$iro-props-default-tree] - ID the map is saved as | ||
| 50 | /// @param {bool} $merge [false] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted. | ||
| 51 | /// | ||
| 52 | @mixin iro-props-save($map, $tree: $iro-props-default-tree, $merge: false) { | ||
| 53 | $noop: iro-props-save($map, $tree, $merge); | ||
| 54 | } | ||
| 55 | |||
| 56 | /// | ||
| 57 | /// Save a property tree. | ||
| 58 | /// | ||
| 59 | /// @param {map} $map - Map containing properties | ||
| 60 | /// @param {string} $tree [$iro-props-default-tree] - ID the map is saved as | ||
| 61 | /// @param {bool} $merge [false] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted. | ||
| 62 | /// | ||
| 63 | @function iro-props-save($map, $tree: $iro-props-default-tree, $merge: false) { | ||
| 64 | $prop-map: null; | ||
| 65 | |||
| 66 | @if $iro-props-enforce-double-dashes { | ||
| 67 | @if not iro-props-validate($map) { | ||
| 68 | @error 'Property tree keys must start with two dashes (--). If you don\'t use property trees for native CSS custom properties, set $iro-props-enforce-double-dashes to false.'; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | @if map-has-key($iro-props-trees, $tree) { | ||
| 73 | @if $merge { | ||
| 74 | $map: iro-map-merge-recursive(map-get($iro-props-trees, $tree), $map); | ||
| 75 | } @else { | ||
| 76 | @error 'Property tree #{inspect($tree)} does already exist.'; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | $iro-props-trees: map-merge($iro-props-trees, ($tree: $map)) !global; | ||
| 81 | |||
| 82 | @return null; | ||
| 83 | } | ||
| 84 | |||
| 85 | /// | ||
| 86 | /// Delete a property tree. | ||
| 87 | /// | ||
| 88 | /// @param {string} $tree [$iro-props-default-tree] - ID of the tree to be deleted | ||
| 89 | /// | ||
| 90 | @mixin iro-props-delete($tree: $iro-props-default-tree) { | ||
| 91 | $noop: iro-props-delete($tree); | ||
| 92 | } | ||
| 93 | |||
| 94 | /// | ||
| 95 | /// Unset a property tree. | ||
| 96 | /// | ||
| 97 | /// @param {string} $tree [$iro-props-default-tree] - ID of the tree to be deleted | ||
| 98 | /// | ||
| 99 | /// @throw If the property tree does not exist | ||
| 100 | /// | ||
| 101 | @function iro-props-delete($tree: $iro-props-default-tree) { | ||
| 102 | @if not map-has-key($iro-props-trees, $tree) { | ||
| 103 | @error 'Property tree "#{inspect($tree)}" does not exist.'; | ||
| 104 | } | ||
| 105 | |||
| 106 | $iro-props-trees: map-remove($iro-props-trees, $tree) !global; | ||
| 107 | |||
| 108 | @return null; | ||
| 109 | } | ||
| 110 | |||
| 111 | /// | ||
| 112 | /// Access a whole property or a subsection (i.e. value) of it. | ||
| 113 | /// | ||
| 114 | /// @param {string | list} $key [null] - Key of the property to read. If this is a list of keys, the map will be traversed in that order. | ||
| 115 | /// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use | ||
| 116 | /// @param {any} $default [null] - Default value to return of no match was found. If null, this function will throw an error instead. | ||
| 117 | /// | ||
| 118 | /// @return {any} Value assigned to property or $default | ||
| 119 | /// | ||
| 120 | /// @throw If there was no match for $key and $default is null | ||
| 121 | /// | ||
| 122 | @function iro-props-get($key: (), $tree: $iro-props-default-tree, $default: null) { | ||
| 123 | @if not map-has-key($iro-props-trees, $tree) { | ||
| 124 | @error 'Unknown tree "#{$tree}".'; | ||
| 125 | } | ||
| 126 | |||
| 127 | $result: map-get($iro-props-trees, $tree); | ||
| 128 | |||
| 129 | @if type-of($key) == list { | ||
| 130 | $stop: false; | ||
| 131 | |||
| 132 | @each $k in $key { | ||
| 133 | @if map-has-key($result, $k) and not $stop { | ||
| 134 | $result: map-get($result, $k); | ||
| 135 | |||
| 136 | @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' { | ||
| 137 | @if length($result) == 2 { | ||
| 138 | $result: iro-props-get($tree: nth($result, 2)); | ||
| 139 | } @else { | ||
| 140 | $result: iro-props-get(nth($result, 3), nth($result, 2)); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } @else { | ||
| 144 | $stop: true; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | @if $stop { | ||
| 149 | $result: null; | ||
| 150 | } | ||
| 151 | } @else { | ||
| 152 | $result: map-get($result, $key); | ||
| 153 | |||
| 154 | @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' { | ||
| 155 | @if length($result) == 2 { | ||
| 156 | $result: iro-props-get($tree: nth($result, 2)); | ||
| 157 | } @else { | ||
| 158 | $result: iro-props-get(nth($result, 3), nth($result, 2)); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | @if $result == null { | ||
| 164 | @if $default == null { | ||
| 165 | @error '"#{$key}" is null.'; | ||
| 166 | } @else { | ||
| 167 | @return $default; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | @return $result; | ||
| 172 | } | ||
| 173 | |||
| 174 | /// | ||
| 175 | /// Generate a var() function call to get native CSS custom property. | ||
| 176 | /// | ||
| 177 | /// @param {string | list} $key - Key of the property to read. If this is a list of keys, the map will be traversed in that order. | ||
| 178 | /// @param {string | null} $tree [null] - Optional tree to check if the property actually exists. | ||
| 179 | /// @param {any} $default [null] - Default value to return of no match was found. | ||
| 180 | /// | ||
| 181 | /// @return {string} var() | ||
| 182 | /// | ||
| 183 | @function iro-props-get-native($key, $tree: null, $default: null) { | ||
| 184 | @if $tree != null { | ||
| 185 | $noop: iro-props-get($key, $tree, $default); | ||
| 186 | } | ||
| 187 | |||
| 188 | $native-var: ''; | ||
| 189 | |||
| 190 | @if type-of($key) == list { | ||
| 191 | @each $subkey in $key { | ||
| 192 | $native-var: $native-var + $subkey; | ||
| 193 | } | ||
| 194 | } @else { | ||
| 195 | $native-var: $key; | ||
| 196 | } | ||
| 197 | |||
| 198 | @if $default == null { | ||
| 199 | @return var(#{$native-var}); | ||
| 200 | } @else { | ||
| 201 | @return var(#{$native-var}, #{$default}); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | /// | ||
| 206 | /// Generate assignments for native CSS custom properties with the values from the specified tree. | ||
| 207 | /// | ||
| 208 | /// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use | ||
| 209 | /// @param {string} $root [()] - Sub-tree to use for assignment | ||
| 210 | /// | ||
| 211 | @mixin iro-props-assign-native($tree: $iro-props-default-tree, $root: (), $skip: ()) { | ||
| 212 | $map: iro-props-get($root, $tree); | ||
| 213 | $map: map-remove($map, $skip...); | ||
| 214 | |||
| 215 | @include iro-props-assign-native-internal($map); | ||
| 216 | } | ||
| 217 | |||
| 218 | /// | ||
| 219 | /// @access private | ||
| 220 | /// | ||
| 221 | @mixin iro-props-assign-native-internal($map, $prefix: '', $ref-depth: $iro-props-native-assing-max-depth) { | ||
| 222 | @each $key, $value in $map { | ||
| 223 | $rd: $ref-depth; | ||
| 224 | @if type-of($value) == list and nth($value, 1) == 'iro-prop-ref' { | ||
| 225 | @if $ref-depth != 0 { | ||
| 226 | $rd: $rd - 1; | ||
| 227 | @if length($value) == 2 { | ||
| 228 | $value: iro-props-get($tree: nth($value, 2)); | ||
| 229 | } @else { | ||
| 230 | $value: iro-props-get(nth($value, 3), nth($value, 2)); | ||
| 231 | } | ||
| 232 | } @else { | ||
| 233 | $value: null; | ||
| 234 | } | ||
| 235 | } | ||
| 236 | @if type-of($value) != map { | ||
| 237 | #{$prefix + $key}: #{$value}; | ||
| 238 | } @else { | ||
| 239 | @include iro-props-assign-native-internal($value, $prefix + $key, $rd); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | /// | ||
| 245 | /// Validate property names. | ||
| 246 | /// | ||
| 247 | /// @access private | ||
| 248 | /// | ||
| 249 | @function iro-props-validate($map) { | ||
| 250 | @each $key, $value in $map { | ||
| 251 | @if str-index($key, '--') != 1 { | ||
| 252 | @return false; | ||
| 253 | } | ||
| 254 | |||
| 255 | @if type-of($value) == map { | ||
| 256 | @if not iro-props-validate($value) { | ||
| 257 | @return false; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | @return true; | ||
| 263 | } | ||
| 264 | |||
| 265 | /// | ||
| 266 | /// Generate a reference to another tree. Dereferencing is lazy, so you may specify a tree that hasn't been created yet. | ||
| 267 | /// | ||
| 268 | /// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use | ||
| 269 | /// @param {string | list} $key - Key of the property to read. If this is a list of keys, the map will be traversed in that order. | ||
| 270 | /// | ||
| 271 | /// @return {list} A special list that let's Ignis know that this is a lazy value. | ||
| 272 | /// | ||
| 273 | /// @throw If there was no match for $key and $default is null | ||
| 274 | /// | ||
| 275 | @function iro-props-ref($tree: $iro-props-default-tree, $key: null) { | ||
| 276 | @if $key == null { | ||
| 277 | @return ('iro-prop-ref' $tree); | ||
| 278 | } @else { | ||
| 279 | @return ('iro-prop-ref' $tree $key); | ||
| 280 | } | ||
| 281 | } | ||
| diff --git a/src/_responsive.scss b/src/_responsive.scss new file mode 100644 index 0000000..6f2a416 --- /dev/null +++ b/src/_responsive.scss | |||
| @@ -0,0 +1,406 @@ | |||
| 1 | //// | ||
| 2 | /// responsive properties. | ||
| 3 | /// | ||
| 4 | /// The mixins and functions in this file allow you to scale any px- or rem-based value depending on | ||
| 5 | /// the available viewport width. One popular use case is the dynamic scaling of fonts. | ||
| 6 | /// | ||
| 7 | /// The code in this file is based on an article by Niklas Postulart: | ||
| 8 | /// http://niklaspostulart.de/2015/10/sass-responsive-type-mixin | ||
| 9 | /// | ||
| 10 | /// The following adjustments were made: | ||
| 11 | /// - Support any property passed by the user, not just font-size | ||
| 12 | /// - Allow multiple target viewports / values | ||
| 13 | /// - Provide a variant of the mixin which integrates include-media for media queries | ||
| 14 | /// | ||
| 15 | /// @group Responsive | ||
| 16 | /// | ||
| 17 | /// @access public | ||
| 18 | //// | ||
| 19 | |||
| 20 | /// | ||
| 21 | /// If true, named viewports will be supported if a compatible $breakpoints map exists. | ||
| 22 | /// This is the case for [include-media](https://include-media.com/), for example. | ||
| 23 | /// | ||
| 24 | /// @type bool | ||
| 25 | /// | ||
| 26 | $iro-responsive-support-named-viewports: true !default; | ||
| 27 | |||
| 28 | /// | ||
| 29 | /// Context ID used for responsive environment-related mixins. | ||
| 30 | /// | ||
| 31 | /// @type string | ||
| 32 | /// | ||
| 33 | $iro-responsive-context-id: 'responsive' !default; | ||
| 34 | |||
| 35 | /// | ||
| 36 | /// Scale a property uniformly between a specific set of target viewports / values. | ||
| 37 | /// | ||
| 38 | /// @param {string | list} $props - Property or list of properties to set | ||
| 39 | /// @param {number} $responsive-map - A map with keys = viewports and values = target value | ||
| 40 | /// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next | ||
| 41 | /// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width | ||
| 42 | /// | ||
| 43 | /// @example scss - Responsive font-size between 2 viewports | ||
| 44 | /// .something { | ||
| 45 | /// @include iro-responsive-property(font-size, ( 320px: 20px, 720px: 30px )); | ||
| 46 | /// } | ||
| 47 | /// | ||
| 48 | /// // Generates: | ||
| 49 | /// | ||
| 50 | /// @media (min-width: 320px) and (max-width: 720px) { | ||
| 51 | /// .something { | ||
| 52 | /// font-size: calc(20px + 10 * ((100vw - 20px) / 400)); | ||
| 53 | /// } | ||
| 54 | /// } | ||
| 55 | /// | ||
| 56 | /// @media (max-width: 320px) { | ||
| 57 | /// .something { | ||
| 58 | /// font-size: 20px; | ||
| 59 | /// } | ||
| 60 | /// } | ||
| 61 | /// | ||
| 62 | /// @media (min-width: 720px) { | ||
| 63 | /// .something { | ||
| 64 | /// font-size: 30px; | ||
| 65 | /// } | ||
| 66 | /// } | ||
| 67 | /// | ||
| 68 | /// @example scss - Responsive font-size between 3 viewports | ||
| 69 | /// .something { | ||
| 70 | /// @include iro-responsive-property(font-size, ( 320px: 20px, 720px: 30px, 1280px: 40px )); | ||
| 71 | /// } | ||
| 72 | /// | ||
| 73 | /// // Generates: | ||
| 74 | /// | ||
| 75 | /// @media (min-width: 320px) and (max-width: 720px) { | ||
| 76 | /// .something { | ||
| 77 | /// font-size: calc(20px + 10 * ((100vw - 20px) / 400)); | ||
| 78 | /// } | ||
| 79 | /// } | ||
| 80 | /// | ||
| 81 | /// @media (min-width: 720px) and (max-width: 1280px) { | ||
| 82 | /// .something { | ||
| 83 | /// font-size: calc(30px + 10 * ((100vw - 30px) / 400)); | ||
| 84 | /// } | ||
| 85 | /// } | ||
| 86 | /// | ||
| 87 | /// @media (max-width: 320px) { | ||
| 88 | /// .something { | ||
| 89 | /// font-size: 20px; | ||
| 90 | /// } | ||
| 91 | /// } | ||
| 92 | /// | ||
| 93 | /// @media (min-width: 720px) { | ||
| 94 | /// .something { | ||
| 95 | /// font-size: 30px; | ||
| 96 | /// } | ||
| 97 | /// } | ||
| 98 | /// | ||
| 99 | @mixin iro-responsive-property($props, $responsive-map, $fluid: true, $vertical: false) { | ||
| 100 | @include iro-responsive-env(map-keys($responsive-map), $fluid, $vertical) { | ||
| 101 | @if type-of($props) == list { | ||
| 102 | @each $prop in $props { | ||
| 103 | #{$prop}: iro-responsive-set(map-values($responsive-map)); | ||
| 104 | } | ||
| 105 | } @else { | ||
| 106 | #{$props}: iro-responsive-set(map-values($responsive-map)); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | /// | ||
| 112 | /// Create a new responsive environment by specifying a set of viewports. | ||
| 113 | /// Inside a responsive environment, use the iro-responsive-set function to make a property scale automatically. | ||
| 114 | /// | ||
| 115 | /// @param {list} $viewports - Viewports sorted in ascending order | ||
| 116 | /// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next | ||
| 117 | /// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width | ||
| 118 | /// | ||
| 119 | /// @content | ||
| 120 | /// | ||
| 121 | /// @see {function} iro-responsive-set | ||
| 122 | /// | ||
| 123 | /// @example scss - Responsive font-size between 2 viewports | ||
| 124 | /// .something { | ||
| 125 | /// @include iro-responsive-env((320px, 720px)) { | ||
| 126 | /// font-size: iro-responsive-set(20px, 30px); | ||
| 127 | /// } | ||
| 128 | /// } | ||
| 129 | /// | ||
| 130 | /// // Generates: | ||
| 131 | /// | ||
| 132 | /// @media (min-width: 320px) and (max-width: 720px) { | ||
| 133 | /// .something { | ||
| 134 | /// font-size: calc(20px + 10 * ((100vw - 20px) / 400)); | ||
| 135 | /// } | ||
| 136 | /// } | ||
| 137 | /// | ||
| 138 | /// @media (max-width: 320px) { | ||
| 139 | /// .something { | ||
| 140 | /// font-size: 20px; | ||
| 141 | /// } | ||
| 142 | /// } | ||
| 143 | /// | ||
| 144 | /// @media (min-width: 720px) { | ||
| 145 | /// .something { | ||
| 146 | /// font-size: 30px; | ||
| 147 | /// } | ||
| 148 | /// } | ||
| 149 | /// | ||
| 150 | @mixin iro-responsive-env($viewports, $fluid: true, $vertical: false) { | ||
| 151 | @if length($viewports) <= 1 { | ||
| 152 | @error '$viewports must contain at least two viewports.'; | ||
| 153 | } | ||
| 154 | |||
| 155 | $new-viewports: (); | ||
| 156 | |||
| 157 | @each $viewport in $viewports { | ||
| 158 | @if $iro-responsive-support-named-viewports and global-variable-exists(breakpoints) { | ||
| 159 | @if map-has-key($breakpoints, $viewport) { | ||
| 160 | $viewport: map-get($breakpoints, $viewport); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | @if (type-of($viewport) != number) or unitless($viewport) { | ||
| 165 | @error '$viewports contains invalid viewports.'; | ||
| 166 | } | ||
| 167 | |||
| 168 | $new-viewports: append($new-viewports, $viewport); | ||
| 169 | } | ||
| 170 | |||
| 171 | $viewports: iro-quicksort($new-viewports); | ||
| 172 | |||
| 173 | @if $new-viewports != $viewports { | ||
| 174 | @error '$viewports was not sorted in ascending order.'; | ||
| 175 | } | ||
| 176 | |||
| 177 | @if $fluid { | ||
| 178 | $first-vp: nth($viewports, 1); | ||
| 179 | $last-vp: nth($viewports, length($viewports)); | ||
| 180 | |||
| 181 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 182 | 'viewports': $viewports, | ||
| 183 | 'mode': set, | ||
| 184 | 'index': 1, | ||
| 185 | 'fluid': $fluid, | ||
| 186 | 'vertical': $vertical, | ||
| 187 | )); | ||
| 188 | |||
| 189 | @content; | ||
| 190 | |||
| 191 | @include iro-context-pop($iro-responsive-context-id); | ||
| 192 | |||
| 193 | @for $i from 1 to length($viewports) { | ||
| 194 | $prev-vp: nth($viewports, $i); | ||
| 195 | $next-vp: nth($viewports, $i + 1); | ||
| 196 | |||
| 197 | @if not $vertical { | ||
| 198 | @media (min-width: $prev-vp) and (max-width: $next-vp) { | ||
| 199 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 200 | 'viewports': $viewports, | ||
| 201 | 'mode': transition, | ||
| 202 | 'index': $i, | ||
| 203 | 'fluid': $fluid, | ||
| 204 | 'vertical': $vertical, | ||
| 205 | )); | ||
| 206 | |||
| 207 | @content; | ||
| 208 | |||
| 209 | @include iro-context-pop($iro-responsive-context-id); | ||
| 210 | } | ||
| 211 | } @else { | ||
| 212 | @media (min-height: $prev-vp) and (max-height: $next-vp) { | ||
| 213 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 214 | 'viewports': $viewports, | ||
| 215 | 'mode': transition, | ||
| 216 | 'index': $i, | ||
| 217 | 'fluid': $fluid, | ||
| 218 | 'vertical': $vertical, | ||
| 219 | )); | ||
| 220 | |||
| 221 | @content; | ||
| 222 | |||
| 223 | @include iro-context-pop($iro-responsive-context-id); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | @if not $vertical { | ||
| 229 | @media (min-width: $last-vp) { | ||
| 230 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 231 | 'viewports': $viewports, | ||
| 232 | 'mode': set, | ||
| 233 | 'index': length($viewports), | ||
| 234 | 'fluid': $fluid, | ||
| 235 | 'vertical': $vertical, | ||
| 236 | )); | ||
| 237 | |||
| 238 | @content; | ||
| 239 | |||
| 240 | @include iro-context-pop($iro-responsive-context-id); | ||
| 241 | } | ||
| 242 | } @else { | ||
| 243 | @media (min-height: $last-vp) { | ||
| 244 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 245 | 'viewports': $viewports, | ||
| 246 | 'mode': set, | ||
| 247 | 'index': length($viewports), | ||
| 248 | 'fluid': $fluid, | ||
| 249 | 'vertical': $vertical, | ||
| 250 | )); | ||
| 251 | |||
| 252 | @content; | ||
| 253 | |||
| 254 | @include iro-context-pop($iro-responsive-context-id); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } @else { | ||
| 258 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 259 | 'viewports': $viewports, | ||
| 260 | 'mode': set, | ||
| 261 | 'index': 1, | ||
| 262 | 'fluid': $fluid, | ||
| 263 | 'vertical': $vertical, | ||
| 264 | )); | ||
| 265 | |||
| 266 | @content; | ||
| 267 | |||
| 268 | @include iro-context-pop($iro-responsive-context-id); | ||
| 269 | |||
| 270 | @for $i from 2 through length($viewports) { | ||
| 271 | $vp: nth($viewports, $i); | ||
| 272 | |||
| 273 | @if not $vertical { | ||
| 274 | @media (min-width: $vp) { | ||
| 275 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 276 | 'viewports': $viewports, | ||
| 277 | 'mode': set, | ||
| 278 | 'index': $i | ||
| 279 | )); | ||
| 280 | |||
| 281 | @content; | ||
| 282 | |||
| 283 | @include iro-context-pop($iro-responsive-context-id); | ||
| 284 | } | ||
| 285 | } @else { | ||
| 286 | @media (min-height: $vp) { | ||
| 287 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
| 288 | 'viewports': $viewports, | ||
| 289 | 'mode': set, | ||
| 290 | 'index': $i | ||
| 291 | )); | ||
| 292 | |||
| 293 | @content; | ||
| 294 | |||
| 295 | @include iro-context-pop($iro-responsive-context-id); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | } | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | /// | ||
| 303 | /// Make a property scale automatically inside a responsive environment. | ||
| 304 | /// | ||
| 305 | /// @param {list} $values - Value for each viewport in the responsive environment. The first value will be used for the first viewport, the second value for the second viewport, and so on. | ||
| 306 | /// | ||
| 307 | /// @return {number|string} | ||
| 308 | /// | ||
| 309 | @function iro-responsive-set($values, $without-calc: false) { | ||
| 310 | $noop: iro-context-assert-stack-must-contain($iro-responsive-context-id, 'env'); | ||
| 311 | |||
| 312 | $data: nth(iro-context-get($iro-responsive-context-id, 'env'), 2); | ||
| 313 | $viewports: map-get($data, 'viewports'); | ||
| 314 | $mode: map-get($data, 'mode'); | ||
| 315 | $fluid: map-get($data, 'fluid'); | ||
| 316 | $vertical: map-get($data, 'vertical'); | ||
| 317 | |||
| 318 | @if length($values) != length($viewports) { | ||
| 319 | @error '$values must contain the same number of items as the responsive environment\'s $viewports.'; | ||
| 320 | } | ||
| 321 | |||
| 322 | @if $mode == set { | ||
| 323 | @return nth($values, map-get($data, 'index')); | ||
| 324 | } @else { | ||
| 325 | $index: map-get($data, 'index'); | ||
| 326 | $prev-vp: nth($viewports, $index); | ||
| 327 | $next-vp: nth($viewports, $index + 1); | ||
| 328 | $prev-value: nth($values, $index); | ||
| 329 | $next-value: nth($values, $index + 1); | ||
| 330 | |||
| 331 | @return iro-responsive-fluid-calc($prev-value, $next-value, $prev-vp, $next-vp, $vertical, $without-calc); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | /// | ||
| 336 | /// Generate the calc() function that uniformly scales a value from $min-value to $max-value depending | ||
| 337 | /// on the viewport width. | ||
| 338 | /// | ||
| 339 | /// @param {number} $min-value - Minimum value | ||
| 340 | /// @param {number} $max-value - Maximum value | ||
| 341 | /// @param {number} $min-viewport - Minimum viewport size | ||
| 342 | /// @param {number} $max-viewport - Maximum viewport size | ||
| 343 | /// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width | ||
| 344 | /// | ||
| 345 | /// @access private | ||
| 346 | /// | ||
| 347 | @function iro-responsive-fluid-calc($min-value, $max-value, $min-viewport, $max-viewport, $vertical: false, $without-calc: false) { | ||
| 348 | $value-unit: unit($min-value); | ||
| 349 | $max-value-unit: unit($max-value); | ||
| 350 | $viewport-unit: unit($min-viewport); | ||
| 351 | $max-viewport-unit: unit($max-viewport); | ||
| 352 | |||
| 353 | @if $min-value == 0 { | ||
| 354 | $value-unit: $max-value-unit; | ||
| 355 | } | ||
| 356 | @if $max-value == 0 { | ||
| 357 | $max-value-unit: $value-unit; | ||
| 358 | } | ||
| 359 | @if $min-viewport == 0 { | ||
| 360 | $viewport-unit: $max-viewport-unit; | ||
| 361 | } | ||
| 362 | @if $max-viewport == 0 { | ||
| 363 | $max-viewport-unit: $viewport-unit; | ||
| 364 | } | ||
| 365 | |||
| 366 | @if ($value-unit != $max-value-unit) or ($viewport-unit != $max-viewport-unit) { | ||
| 367 | @error 'Units of $min-value and $max-value, $min-viewport and $max-viewport must match.'; | ||
| 368 | } | ||
| 369 | |||
| 370 | @if ($value-unit == rem) and ($viewport-unit == px) { | ||
| 371 | $min-viewport: iro-px-to-rem($min-viewport); | ||
| 372 | $max-viewport: iro-px-to-rem($max-viewport); | ||
| 373 | $viewport-unit: rem; | ||
| 374 | } @else if ($value-unit == px) and ($viewport-unit == rem) { | ||
| 375 | $min-value: iro-px-to-rem($min-value); | ||
| 376 | $max-value: iro-px-to-rem($max-value); | ||
| 377 | $value-unit: rem; | ||
| 378 | } | ||
| 379 | |||
| 380 | @if $value-unit != $viewport-unit { | ||
| 381 | @error 'This combination of units is not supported.'; | ||
| 382 | } | ||
| 383 | |||
| 384 | $value-diff: iro-strip-unit($max-value - $min-value); | ||
| 385 | $viewport-diff: iro-strip-unit($max-viewport - $min-viewport); | ||
| 386 | |||
| 387 | $calc: ''; | ||
| 388 | |||
| 389 | @if $min-value != 0 { | ||
| 390 | $calc: '#{$min-value} + '; | ||
| 391 | } | ||
| 392 | |||
| 393 | @if not $vertical { | ||
| 394 | $calc: unquote('#{$calc}#{$value-diff} * ((100vw - #{$min-viewport}) / #{$viewport-diff})'); | ||
| 395 | } @else { | ||
| 396 | $calc: unquote('#{$calc}#{$value-diff} * ((100vh - #{$min-viewport}) / #{$viewport-diff})'); | ||
| 397 | } | ||
| 398 | |||
| 399 | @if $without-calc { | ||
| 400 | @return $calc; | ||
| 401 | } @else { | ||
| 402 | @return calc(#{$calc}); | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | @include iro-context-stack-create($iro-responsive-context-id); | ||
| diff --git a/src/_vars.scss b/src/_vars.scss new file mode 100644 index 0000000..ce6efda --- /dev/null +++ b/src/_vars.scss | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | //// | ||
| 2 | /// Variables. | ||
| 3 | /// | ||
| 4 | /// Global variables that are used throughout the framework. | ||
| 5 | /// | ||
| 6 | /// @group Global variables | ||
| 7 | /// | ||
| 8 | /// @access public | ||
| 9 | //// | ||
| 10 | |||
| 11 | /// | ||
| 12 | /// Reference root font size in px that is used for px -> rem conversions. | ||
| 13 | /// | ||
| 14 | /// @type number | ||
| 15 | /// | ||
| 16 | $iro-root-size: 16px !default; | ||
| diff --git a/src/bem-shortcodes.scss b/src/bem-shortcodes.scss new file mode 100644 index 0000000..11abeed --- /dev/null +++ b/src/bem-shortcodes.scss | |||
| @@ -0,0 +1,349 @@ | |||
| 1 | //// | ||
| 2 | /// Shorter version of the bem-related mixins. Useful to reduce clutter. | ||
| 3 | /// | ||
| 4 | /// @group BEM shortcodes | ||
| 5 | /// | ||
| 6 | /// @access public | ||
| 7 | //// | ||
| 8 | |||
| 9 | /// | ||
| 10 | /// @alias iro-bem-block | ||
| 11 | /// | ||
| 12 | @mixin block($name, $type: null) { | ||
| 13 | @include iro-bem-block($name, $type: null) { | ||
| 14 | @content; | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// | ||
| 19 | /// @alias block | ||
| 20 | /// | ||
| 21 | @mixin b($name, $type: null) { | ||
| 22 | @include block($name, $type: null) { | ||
| 23 | @content; | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | /// | ||
| 28 | /// @alias iro-bem-object | ||
| 29 | /// | ||
| 30 | @mixin object($name) { | ||
| 31 | @include iro-bem-object($name) { | ||
| 32 | @content; | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | /// | ||
| 37 | /// @alias object | ||
| 38 | /// | ||
| 39 | @mixin ob($name) { | ||
| 40 | @include object($name) { | ||
| 41 | @content; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// | ||
| 46 | /// @alias iro-bem-component | ||
| 47 | /// | ||
| 48 | @mixin component($name) { | ||
| 49 | @include iro-bem-component($name) { | ||
| 50 | @content; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// | ||
| 55 | /// @alias component | ||
| 56 | /// | ||
| 57 | @mixin cb($name) { | ||
| 58 | @include component($name) { | ||
| 59 | @content; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | /// | ||
| 64 | /// @alias iro-bem-layout | ||
| 65 | /// | ||
| 66 | @mixin layout($name) { | ||
| 67 | @include iro-bem-layout($name) { | ||
| 68 | @content; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /// | ||
| 73 | /// @alias layout | ||
| 74 | /// | ||
| 75 | @mixin lb($name) { | ||
| 76 | @include layout($name) { | ||
| 77 | @content; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | /// | ||
| 82 | /// @alias iro-bem-utility | ||
| 83 | /// | ||
| 84 | @mixin utility($name) { | ||
| 85 | @include iro-bem-utility($name) { | ||
| 86 | @content; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | /// | ||
| 91 | /// @alias utility | ||
| 92 | /// | ||
| 93 | @mixin ub($name) { | ||
| 94 | @include utility($name) { | ||
| 95 | @content; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | /// | ||
| 100 | /// @alias iro-bem-scope | ||
| 101 | /// | ||
| 102 | @mixin scope($name) { | ||
| 103 | @include iro-bem-scope($name) { | ||
| 104 | @content; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | /// | ||
| 109 | /// @alias scope | ||
| 110 | /// | ||
| 111 | @mixin sb($name) { | ||
| 112 | @include scope($name) { | ||
| 113 | @content; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// | ||
| 118 | /// @alias iro-bem-theme | ||
| 119 | /// | ||
| 120 | @mixin theme($name) { | ||
| 121 | @include iro-bem-theme($name) { | ||
| 122 | @content; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | /// | ||
| 127 | /// @alias theme | ||
| 128 | /// | ||
| 129 | @mixin tb($name) { | ||
| 130 | @include theme($name) { | ||
| 131 | @content; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | /// | ||
| 136 | /// @alias iro-bem-js | ||
| 137 | /// | ||
| 138 | @mixin js($name) { | ||
| 139 | @include iro-bem-js($name) { | ||
| 140 | @content; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | /// | ||
| 145 | /// @alias iro-bem-qa | ||
| 146 | /// | ||
| 147 | @mixin qa($name) { | ||
| 148 | @include iro-bem-qa($name) { | ||
| 149 | @content; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | /// | ||
| 154 | /// @alias iro-bem-hack | ||
| 155 | /// | ||
| 156 | @mixin hack($name) { | ||
| 157 | @include iro-bem-hack($name) { | ||
| 158 | @content; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | /// | ||
| 163 | /// @alias iro-bem-composed-of | ||
| 164 | /// | ||
| 165 | @mixin composed-of($block, $blocks...) { | ||
| 166 | @include iro-bem-composed-of($block, $blocks...) { | ||
| 167 | @content; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | /// | ||
| 172 | /// @alias composed-of | ||
| 173 | /// | ||
| 174 | @mixin co($block, $blocks...) { | ||
| 175 | @include composed-of($block, $blocks...) { | ||
| 176 | @content; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | /// | ||
| 181 | /// @alias iro-bem-element | ||
| 182 | /// | ||
| 183 | @mixin element($name, $names...) { | ||
| 184 | @include iro-bem-element($name, $names...) { | ||
| 185 | @content; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | /// | ||
| 190 | /// @alias element | ||
| 191 | /// | ||
| 192 | @mixin e($name, $names...) { | ||
| 193 | @include element($name, $names...) { | ||
| 194 | @content; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | /// | ||
| 199 | /// @alias iro-bem-related-element | ||
| 200 | /// | ||
| 201 | @mixin related-element($sign, $name, $names...) { | ||
| 202 | @include iro-bem-related-element($sign, $name, $names...) { | ||
| 203 | @content; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | /// | ||
| 208 | /// @alias related-element | ||
| 209 | /// | ||
| 210 | @mixin re($sign, $name, $names...) { | ||
| 211 | @include related-element($sign, $name, $names...) { | ||
| 212 | @content; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | /// | ||
| 217 | /// @alias iro-bem-sibling-element | ||
| 218 | /// | ||
| 219 | @mixin sibling-element($name, $names...) { | ||
| 220 | @include iro-bem-sibling-element($name, $names...) { | ||
| 221 | @content; | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | /// | ||
| 226 | /// @alias sibling-element | ||
| 227 | /// | ||
| 228 | @mixin se($name, $names...) { | ||
| 229 | @include sibling-element($name, $names...) { | ||
| 230 | @content; | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | /// | ||
| 235 | /// @alias iro-bem-next-element | ||
| 236 | /// | ||
| 237 | @mixin next-element($name, $names...) { | ||
| 238 | @include iro-bem-next-element($name, $names...) { | ||
| 239 | @content; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | /// | ||
| 244 | /// @alias next-element | ||
| 245 | /// | ||
| 246 | @mixin ne($name, $names...) { | ||
| 247 | @include next-element($name, $names...) { | ||
| 248 | @content; | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | /// | ||
| 253 | /// @alias iro-bem-next-twin-element | ||
| 254 | /// | ||
| 255 | @mixin next-twin-element { | ||
| 256 | @include iro-bem-next-twin-element { | ||
| 257 | @content; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | /// | ||
| 262 | /// @alias next-twin-element | ||
| 263 | /// | ||
| 264 | @mixin te { | ||
| 265 | @include next-twin-element { | ||
| 266 | @content; | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /// | ||
| 271 | /// @alias iro-bem-modifier | ||
| 272 | /// | ||
| 273 | @mixin modifier($name, $names...) { | ||
| 274 | @include iro-bem-modifier($name, $names...) { | ||
| 275 | @content; | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | /// | ||
| 280 | /// @alias modifier | ||
| 281 | /// | ||
| 282 | @mixin m($name, $names...) { | ||
| 283 | @include modifier($name, $names...) { | ||
| 284 | @content; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | /// | ||
| 289 | /// @alias iro-bem-suffix | ||
| 290 | /// | ||
| 291 | @mixin suffix($name) { | ||
| 292 | @include iro-bem-suffix($name) { | ||
| 293 | @content; | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | /// | ||
| 298 | /// @alias suffix | ||
| 299 | /// | ||
| 300 | @mixin s($name) { | ||
| 301 | @include suffix($name) { | ||
| 302 | @content; | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | /// | ||
| 307 | /// @alias iro-bem-is | ||
| 308 | /// | ||
| 309 | @mixin is($state, $states...) { | ||
| 310 | @include iro-bem-is($state, $states...) { | ||
| 311 | @content; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | /// | ||
| 316 | /// @alias iro-bem-has | ||
| 317 | /// | ||
| 318 | @mixin has($state, $states...) { | ||
| 319 | @include iro-bem-has($state, $states...) { | ||
| 320 | @content; | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | /// | ||
| 325 | /// @alias iro-bem-at-theme | ||
| 326 | /// | ||
| 327 | @mixin at-theme($name, $names...) { | ||
| 328 | @include iro-bem-at-theme($name, $names...) { | ||
| 329 | @content; | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | /// | ||
| 334 | /// @alias theme | ||
| 335 | /// | ||
| 336 | @mixin at($name, $names...) { | ||
| 337 | @include at-theme($name, $names...) { | ||
| 338 | @content; | ||
| 339 | } | ||
| 340 | } | ||
| 341 | |||
| 342 | /// | ||
| 343 | /// @alias iro-bem-multi | ||
| 344 | /// | ||
| 345 | @mixin multi($first, $others...) { | ||
| 346 | @include iro-bem-multi($first, $others...) { | ||
| 347 | @content; | ||
| 348 | } | ||
| 349 | } | ||
| diff --git a/src/bem/_block.scss b/src/bem/_block.scss new file mode 100644 index 0000000..d065891 --- /dev/null +++ b/src/bem/_block.scss | |||
| @@ -0,0 +1,392 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Generate a new block. | ||
| 9 | /// | ||
| 10 | /// This mixin simply creates a new block with the name {namespace}_{name}, | ||
| 11 | /// where {namespace} is the prefix assigned to $type and {name} is the | ||
| 12 | /// block's name. | ||
| 13 | /// | ||
| 14 | /// @param {string} $name - Block name | ||
| 15 | /// @param {string} $type [null] - BEMIT namespace of the block | ||
| 16 | /// | ||
| 17 | /// @content | ||
| 18 | /// | ||
| 19 | /// @throw If $type is invalid | ||
| 20 | /// @throw If the block is preceded by another block, element, modifier or suffix | ||
| 21 | /// | ||
| 22 | /// @example scss - Creating a new block | ||
| 23 | /// @include iro-bem-block('something', 'component') { | ||
| 24 | /// /* some definitions */ | ||
| 25 | /// } | ||
| 26 | /// | ||
| 27 | /// // Generates: | ||
| 28 | /// | ||
| 29 | /// .c-something { | ||
| 30 | /// /* some definitions */ | ||
| 31 | /// } | ||
| 32 | /// | ||
| 33 | @mixin iro-bem-block($name, $type: null) { | ||
| 34 | $result: iro-bem-block($name, $type); | ||
| 35 | $selector: nth($result, 1); | ||
| 36 | $context: nth($result, 2); | ||
| 37 | |||
| 38 | @include iro-bem-validate( | ||
| 39 | 'block', | ||
| 40 | (name: $name, type: $type), | ||
| 41 | $selector, | ||
| 42 | $context | ||
| 43 | ); | ||
| 44 | |||
| 45 | @if $type != null { | ||
| 46 | $iro-bem-blocks: append($iro-bem-blocks, $name + '_' + $type) !global; | ||
| 47 | } @else { | ||
| 48 | $iro-bem-blocks: append($iro-bem-blocks, $name) !global; | ||
| 49 | } | ||
| 50 | |||
| 51 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 52 | @at-root #{$selector} { | ||
| 53 | @content; | ||
| 54 | } | ||
| 55 | @include iro-context-pop($iro-bem-context-id); | ||
| 56 | } | ||
| 57 | |||
| 58 | /// | ||
| 59 | /// Generate a new block. Check the respective mixin documentation for more information. | ||
| 60 | /// | ||
| 61 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 62 | /// | ||
| 63 | /// @see {mixin} iro-bem-block | ||
| 64 | /// | ||
| 65 | @function iro-bem-block($name, $type: null) { | ||
| 66 | // | ||
| 67 | // Possible outcomes: | ||
| 68 | // - ({b,e,m,s}) block | ||
| 69 | // | ||
| 70 | |||
| 71 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 72 | |||
| 73 | $selector: null; | ||
| 74 | $base-selector: null; | ||
| 75 | |||
| 76 | @if $type != null { | ||
| 77 | $namespace: map-get($iro-bem-namespaces, $type); | ||
| 78 | |||
| 79 | @if not $namespace { | ||
| 80 | @error '"#{$type}" is not a valid type.'; | ||
| 81 | } | ||
| 82 | |||
| 83 | $base-selector: selector-parse('.' + $namespace + '-' + $name); | ||
| 84 | |||
| 85 | @if $type != 'theme' or & { | ||
| 86 | $selector: $base-selector; | ||
| 87 | } @else if not & { | ||
| 88 | $selector: iro-bem-theme-selector($name); | ||
| 89 | } | ||
| 90 | } @else { | ||
| 91 | $base-selector: selector-parse('.' + $name); | ||
| 92 | $selector: $base-selector; | ||
| 93 | } | ||
| 94 | |||
| 95 | @if & { | ||
| 96 | $selector: selector-nest(&, $selector); | ||
| 97 | } | ||
| 98 | |||
| 99 | $context: 'block', ( | ||
| 100 | 'name': $name, | ||
| 101 | 'type': $type, | ||
| 102 | 'selector': $selector, | ||
| 103 | 'base-selector': $base-selector | ||
| 104 | ); | ||
| 105 | |||
| 106 | @return $selector $context; | ||
| 107 | } | ||
| 108 | |||
| 109 | /// | ||
| 110 | /// Generate a new object block. It's a shorthand for iro-bem-block($name, 'object'). | ||
| 111 | /// | ||
| 112 | /// @param {string} $name - Object block name | ||
| 113 | /// | ||
| 114 | /// @content | ||
| 115 | /// | ||
| 116 | @mixin iro-bem-object($name) { | ||
| 117 | @include iro-bem-block($name, 'object') { | ||
| 118 | @content; | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | /// | ||
| 123 | /// Generate a new object block. Check the respective mixin documentation for more information. | ||
| 124 | /// | ||
| 125 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 126 | /// | ||
| 127 | /// @see {mixin} iro-bem-object | ||
| 128 | /// | ||
| 129 | @function iro-bem-object($name) { | ||
| 130 | @return iro-bem-block($name, 'object'); | ||
| 131 | } | ||
| 132 | |||
| 133 | /// | ||
| 134 | /// Generate a new component block. It's a shorthand for iro-bem-block($name, 'component'). | ||
| 135 | /// | ||
| 136 | /// @param {string} $name - Component block name | ||
| 137 | /// | ||
| 138 | /// @content | ||
| 139 | /// | ||
| 140 | @mixin iro-bem-component($name) { | ||
| 141 | @include iro-bem-block($name, 'component') { | ||
| 142 | @content; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | /// | ||
| 147 | /// Generate a new component block. Check the respective mixin documentation for more information. | ||
| 148 | /// | ||
| 149 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 150 | /// | ||
| 151 | /// @see {mixin} iro-bem-component | ||
| 152 | /// | ||
| 153 | @function iro-bem-component($name) { | ||
| 154 | @return iro-bem-block($name, 'component'); | ||
| 155 | } | ||
| 156 | |||
| 157 | /// | ||
| 158 | /// Generate a new layout block. It's a shorthand for iro-bem-block($name, 'layout'). | ||
| 159 | /// | ||
| 160 | /// @param {string} $name - Layout block name | ||
| 161 | /// | ||
| 162 | /// @content | ||
| 163 | /// | ||
| 164 | @mixin iro-bem-layout($name) { | ||
| 165 | @include iro-bem-block($name, 'layout') { | ||
| 166 | @content; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | /// | ||
| 171 | /// Generate a new layout block. Check the respective mixin documentation for more information. | ||
| 172 | /// | ||
| 173 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 174 | /// | ||
| 175 | /// @see {mixin} iro-bem-layout | ||
| 176 | /// | ||
| 177 | @function iro-bem-layout($name) { | ||
| 178 | @return iro-bem-block($name, 'layout'); | ||
| 179 | } | ||
| 180 | |||
| 181 | /// | ||
| 182 | /// Generate a new utility block. It's a shorthand for iro-bem-block($name, 'utility'). | ||
| 183 | /// | ||
| 184 | /// @param {string} $name - Utility block name | ||
| 185 | /// | ||
| 186 | /// @content | ||
| 187 | /// | ||
| 188 | @mixin iro-bem-utility($name) { | ||
| 189 | @include iro-bem-block($name, 'utility') { | ||
| 190 | @content; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | /// | ||
| 195 | /// Generate a new utility block. Check the respective mixin documentation for more information. | ||
| 196 | /// | ||
| 197 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 198 | /// | ||
| 199 | /// @see {mixin} iro-bem-utility | ||
| 200 | /// | ||
| 201 | @function iro-bem-utility($name) { | ||
| 202 | @return iro-bem-block($name, 'utility'); | ||
| 203 | } | ||
| 204 | |||
| 205 | /// | ||
| 206 | /// Generate a new scope block. It's a shorthand for iro-bem-block($name, 'scope'). | ||
| 207 | /// | ||
| 208 | /// @param {string} $name - Scope block name | ||
| 209 | /// | ||
| 210 | /// @content | ||
| 211 | /// | ||
| 212 | @mixin iro-bem-scope($name) { | ||
| 213 | @include iro-bem-block($name, 'scope') { | ||
| 214 | @content; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | /// | ||
| 219 | /// Generate a new scope block. Check the respective mixin documentation for more information. | ||
| 220 | /// | ||
| 221 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 222 | /// | ||
| 223 | /// @see {mixin} iro-bem-scope | ||
| 224 | /// | ||
| 225 | @function iro-bem-scope($name) { | ||
| 226 | @return iro-bem-block($name, 'scope'); | ||
| 227 | } | ||
| 228 | |||
| 229 | /// | ||
| 230 | /// Generate a new theme block. It's a shorthand for iro-bem-block($name, 'theme'). | ||
| 231 | /// | ||
| 232 | /// @param {string} $name - Theme block name | ||
| 233 | /// | ||
| 234 | /// @content | ||
| 235 | /// | ||
| 236 | @mixin iro-bem-theme($name) { | ||
| 237 | @include iro-bem-block($name, 'theme') { | ||
| 238 | @content; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | /// | ||
| 243 | /// Generate a new theme block. Check the respective mixin documentation for more information. | ||
| 244 | /// | ||
| 245 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 246 | /// | ||
| 247 | /// @see {mixin} iro-bem-theme | ||
| 248 | /// | ||
| 249 | @function iro-bem-theme($name) { | ||
| 250 | @return iro-bem-block($name, 'theme'); | ||
| 251 | } | ||
| 252 | |||
| 253 | /// | ||
| 254 | /// Generate a new JS block. It's a shorthand for iro-bem-block($name, 'js'). | ||
| 255 | /// | ||
| 256 | /// @param {string} $name - JS block name | ||
| 257 | /// | ||
| 258 | /// @content | ||
| 259 | /// | ||
| 260 | @mixin iro-bem-js($name) { | ||
| 261 | @include iro-bem-block($name, 'js') { | ||
| 262 | @content; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | /// | ||
| 267 | /// Generate a new JS block. Check the respective mixin documentation for more information. | ||
| 268 | /// | ||
| 269 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 270 | /// | ||
| 271 | /// @see {mixin} iro-bem-js | ||
| 272 | /// | ||
| 273 | @function iro-bem-js($name) { | ||
| 274 | @return iro-bem-block($name, 'js'); | ||
| 275 | } | ||
| 276 | |||
| 277 | /// | ||
| 278 | /// Generate a new QA block. It's a shorthand for iro-bem-block($name, 'qa'). | ||
| 279 | /// | ||
| 280 | /// @param {string} $name - QA block name | ||
| 281 | /// | ||
| 282 | /// @content | ||
| 283 | /// | ||
| 284 | @mixin iro-bem-qa($name) { | ||
| 285 | @include iro-bem-block($name, 'qa') { | ||
| 286 | @content; | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | /// | ||
| 291 | /// Generate a new QA block. Check the respective mixin documentation for more information. | ||
| 292 | /// | ||
| 293 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 294 | /// | ||
| 295 | /// @see {mixin} iro-bem-qa | ||
| 296 | /// | ||
| 297 | @function iro-bem-qa($name) { | ||
| 298 | @return iro-bem-block($name, 'qa'); | ||
| 299 | } | ||
| 300 | |||
| 301 | /// | ||
| 302 | /// Generate a new hack block. It's a shorthand for iro-bem-block($name, 'hack'). | ||
| 303 | /// | ||
| 304 | /// @param {string} $name - Hack block name | ||
| 305 | /// | ||
| 306 | /// @content | ||
| 307 | /// | ||
| 308 | @mixin iro-bem-hack($name) { | ||
| 309 | @include iro-bem-block($name, 'hack') { | ||
| 310 | @content; | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | /// | ||
| 315 | /// Generate a new hack block. Check the respective mixin documentation for more information. | ||
| 316 | /// | ||
| 317 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 318 | /// | ||
| 319 | /// @see {mixin} iro-bem-hack | ||
| 320 | /// | ||
| 321 | @function iro-bem-hack($name) { | ||
| 322 | @return iro-bem-block($name, 'hack'); | ||
| 323 | } | ||
| 324 | |||
| 325 | /// | ||
| 326 | /// Assert that a block or element is composed of another block. In BEM, such a relationship is referred to | ||
| 327 | /// as a "mix": https://en.bem.info/methodology/key-concepts/#mix | ||
| 328 | /// | ||
| 329 | /// Compilation will fail if the foreign block doesn't exist. This way, you can ensure that blocks are | ||
| 330 | /// defined in the right order so that composed blocks/elements will actually override the foreign | ||
| 331 | /// declarations without having to artificially increase the specificity. | ||
| 332 | /// | ||
| 333 | /// @param {string | list} $block - Either first block name, or list with two items: 1. block name, 2. block type | ||
| 334 | /// @param {string | list} $blocks - Either other block names, or list with two items: 1. block name, 2. block type | ||
| 335 | /// | ||
| 336 | /// @throw If a block type is invalid | ||
| 337 | /// @throw If a block doesn't exist | ||
| 338 | /// | ||
| 339 | /// @example scss - Successful assertion | ||
| 340 | /// @include iro-bem-component('someBlock') { | ||
| 341 | /// /* some definitions */ | ||
| 342 | /// } | ||
| 343 | /// | ||
| 344 | /// @include iro-bem-component('anotherBlock') { | ||
| 345 | /// /* some definitions */ | ||
| 346 | /// | ||
| 347 | /// @include iro-bem-element('elem') { | ||
| 348 | /// @include iro-bem-composed-of('someBlock' 'component'); | ||
| 349 | /// | ||
| 350 | /// /* some definitions */ | ||
| 351 | /// } | ||
| 352 | /// } | ||
| 353 | /// | ||
| 354 | /// // Intended use: <div class="c-anotherBlock__elem c-someBlock">...</div> | ||
| 355 | /// | ||
| 356 | /// @example scss - Failing assertion | ||
| 357 | /// @include iro-bem-component('anotherBlock') { | ||
| 358 | /// /* some definitions */ | ||
| 359 | /// | ||
| 360 | /// @include iro-bem-element('elem') { | ||
| 361 | /// @include iro-bem-composed-of('someBlock' 'component'); | ||
| 362 | /// | ||
| 363 | /// /* some definitions */ | ||
| 364 | /// } | ||
| 365 | /// } | ||
| 366 | /// | ||
| 367 | /// @include iro-bem-component('someBlock') { | ||
| 368 | /// /* some definitions */ | ||
| 369 | /// } | ||
| 370 | /// | ||
| 371 | /// // Compilation will fail because c-someBlock is defined after c-anotherBlock__elem | ||
| 372 | /// | ||
| 373 | @mixin iro-bem-composed-of($block, $blocks...) { | ||
| 374 | @each $block in iro-list-prepend($blocks, $block) { | ||
| 375 | @if type-of($block) == string { | ||
| 376 | @if not index($iro-bem-blocks, $block) { | ||
| 377 | @error 'Block "#{$block}" does not exist.'; | ||
| 378 | } | ||
| 379 | } @else { | ||
| 380 | $name: nth($block, 1); | ||
| 381 | $type: nth($block, 2); | ||
| 382 | |||
| 383 | @if not map-get($iro-bem-namespaces, $type) { | ||
| 384 | @error '"#{$type}" is not a valid type.'; | ||
| 385 | } | ||
| 386 | |||
| 387 | @if not index($iro-bem-blocks, $name + '_' + $type) { | ||
| 388 | @error 'Block "#{$name}" does not exist.'; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | } | ||
| 392 | } | ||
| diff --git a/src/bem/_debug.scss b/src/bem/_debug.scss new file mode 100644 index 0000000..e69083c --- /dev/null +++ b/src/bem/_debug.scss | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | @if $iro-bem-debug { | ||
| 8 | @each $type, $color in $iro-bem-debug-colors { | ||
| 9 | $namespace: map-get($iro-bem-namespaces, $type); | ||
| 10 | |||
| 11 | [class^='#{$namespace}-'], | ||
| 12 | [class*=' #{$namespace}-'] { | ||
| 13 | outline: 5px solid $color; | ||
| 14 | } | ||
| 15 | } | ||
| 16 | } | ||
| diff --git a/src/bem/_element.scss b/src/bem/_element.scss new file mode 100644 index 0000000..b3d2fee --- /dev/null +++ b/src/bem/_element.scss | |||
| @@ -0,0 +1,622 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Generate a new BEM element. | ||
| 9 | /// | ||
| 10 | /// The element will be generated according to the BEM naming convention. | ||
| 11 | /// If the parent selector doesn't match the block selector, the element will be | ||
| 12 | /// nested inside the parent selector. This means, you may nest elements inside | ||
| 13 | /// other elements, modifiers or any kind of selector such as &:hover. | ||
| 14 | /// | ||
| 15 | /// @param {string} $name - First element name | ||
| 16 | /// @param {string} $names - More element names | ||
| 17 | /// | ||
| 18 | /// @content | ||
| 19 | /// | ||
| 20 | /// @throw If the element is not preceded by a block, element, modifier or suffix. | ||
| 21 | /// | ||
| 22 | /// @example scss - Element for a block | ||
| 23 | /// @include iro-bem-component('block') { | ||
| 24 | /// /* some block definitions */ | ||
| 25 | /// | ||
| 26 | /// @include iro-bem-element('elem') { | ||
| 27 | /// /* some element definitions */ | ||
| 28 | /// } | ||
| 29 | /// } | ||
| 30 | /// | ||
| 31 | /// // Generates: | ||
| 32 | /// | ||
| 33 | /// .c-block { | ||
| 34 | /// /* some block definitions */ | ||
| 35 | /// } | ||
| 36 | /// | ||
| 37 | /// .c-block__elem { | ||
| 38 | /// /* some element definitions */ | ||
| 39 | /// } | ||
| 40 | /// | ||
| 41 | /// @example scss - Element that is affected by the user hovering the block | ||
| 42 | /// @include iro-bem-component('block') { | ||
| 43 | /// /* some block definitions */ | ||
| 44 | /// | ||
| 45 | /// @include iro-bem-element('elem') { | ||
| 46 | /// background-color: #eee; | ||
| 47 | /// } | ||
| 48 | /// | ||
| 49 | /// &:hover { | ||
| 50 | /// @include iro-bem-element('elem') { | ||
| 51 | /// background-color: #000; | ||
| 52 | /// } | ||
| 53 | /// } | ||
| 54 | /// } | ||
| 55 | /// | ||
| 56 | /// // Generates: | ||
| 57 | /// | ||
| 58 | /// .c-block { | ||
| 59 | /// /* some block definitions */ | ||
| 60 | /// } | ||
| 61 | /// | ||
| 62 | /// .c-block__elem { | ||
| 63 | /// background-color: #eee; | ||
| 64 | /// } | ||
| 65 | /// | ||
| 66 | /// .c-block:hover .c-block__elem { | ||
| 67 | /// background-color: #000; | ||
| 68 | /// } | ||
| 69 | /// | ||
| 70 | /// @example scss - Multiple elements | ||
| 71 | /// @include iro-bem-component('block') { | ||
| 72 | /// /* some block definitions */ | ||
| 73 | /// | ||
| 74 | /// @include iro-bem-element('elem1', 'elem2') { | ||
| 75 | /// /* some element definitions */ | ||
| 76 | /// } | ||
| 77 | /// } | ||
| 78 | /// | ||
| 79 | /// // Generates: | ||
| 80 | /// | ||
| 81 | /// .c-block { | ||
| 82 | /// /* some block definitions */ | ||
| 83 | /// } | ||
| 84 | /// | ||
| 85 | /// .c-block__elem1, .c-block__elem2 { | ||
| 86 | /// /* some element definitions */ | ||
| 87 | /// } | ||
| 88 | /// | ||
| 89 | @mixin iro-bem-element($name, $names...) { | ||
| 90 | $result: iro-bem-element($name, $names...); | ||
| 91 | $selector: nth($result, 1); | ||
| 92 | $context: nth($result, 2); | ||
| 93 | |||
| 94 | @include iro-bem-validate( | ||
| 95 | 'element', | ||
| 96 | (name: $name, names: $names), | ||
| 97 | $selector, | ||
| 98 | $context | ||
| 99 | ); | ||
| 100 | |||
| 101 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 102 | @at-root #{$selector} { | ||
| 103 | @content; | ||
| 104 | } | ||
| 105 | @include iro-context-pop($iro-bem-context-id); | ||
| 106 | } | ||
| 107 | |||
| 108 | /// | ||
| 109 | /// Generate a new BEM element. Check the respective mixin documentation for more information. | ||
| 110 | /// | ||
| 111 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 112 | /// | ||
| 113 | /// @see {mixin} iro-bem-element | ||
| 114 | /// | ||
| 115 | @function iro-bem-element($name, $names...) { | ||
| 116 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 117 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); | ||
| 118 | |||
| 119 | $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element'); | ||
| 120 | |||
| 121 | $selector: (); | ||
| 122 | $parts-data: (); | ||
| 123 | |||
| 124 | @if nth($parent-context, 1) == 'element' { | ||
| 125 | @if $iro-bem-element-nesting-policy == 'disallow' { | ||
| 126 | @error 'Element nesting is forbidden.'; | ||
| 127 | } | ||
| 128 | |||
| 129 | @if $iro-bem-element-nesting-policy == 'append' { | ||
| 130 | $element-selector: map-get(nth($parent-context, 2), 'selector'); | ||
| 131 | |||
| 132 | @if not iro-selector-suffix-match(&, $element-selector) { | ||
| 133 | @error 'A nested element must be an immediate children of the parent element.'; | ||
| 134 | } | ||
| 135 | |||
| 136 | // | ||
| 137 | // Possible outcomes: | ||
| 138 | // - {e}__element | ||
| 139 | // - [manual selector] {e}__element | ||
| 140 | // | ||
| 141 | |||
| 142 | @each $name in join($name, $names) { | ||
| 143 | $sel: selector-append(&, $iro-bem-element-separator + $name); | ||
| 144 | $selector: join($selector, $sel, comma); | ||
| 145 | $parts-data: append($parts-data, ( | ||
| 146 | 'name': $name, | ||
| 147 | 'selector': $sel | ||
| 148 | )); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | $parent-context: iro-context-get($iro-bem-context-id, 'block'); | ||
| 153 | } | ||
| 154 | |||
| 155 | @if length($selector) == 0 { | ||
| 156 | $parent-selector: map-get(nth($parent-context, 2), 'selector'); | ||
| 157 | |||
| 158 | @if iro-selector-suffix-match(&, $parent-selector) { | ||
| 159 | // | ||
| 160 | // Possible outcomes: | ||
| 161 | // - {b}__element | ||
| 162 | // - [manual selector] {b}__element | ||
| 163 | // | ||
| 164 | |||
| 165 | @each $name in join($name, $names) { | ||
| 166 | $sel: selector-append(&, $iro-bem-element-separator + $name); | ||
| 167 | $selector: join($selector, $sel, comma); | ||
| 168 | $parts-data: append($parts-data, ( | ||
| 169 | 'name': $name, | ||
| 170 | 'selector': $sel | ||
| 171 | )); | ||
| 172 | } | ||
| 173 | } @else { | ||
| 174 | // | ||
| 175 | // Possible outcomes: | ||
| 176 | // - {b} [manual selector] {b}__element | ||
| 177 | // - {e,m,s} ([manual selector]) {b}__element | ||
| 178 | // | ||
| 179 | |||
| 180 | @if nth($parent-context, 1) != 'block' { | ||
| 181 | $parent-context: iro-context-get($iro-bem-context-id, 'block'); | ||
| 182 | } | ||
| 183 | |||
| 184 | $block-base-selector: map-get(nth($parent-context, 2), 'base-selector'); | ||
| 185 | |||
| 186 | @each $name in join($name, $names) { | ||
| 187 | $sel: selector-nest(&, selector-append($block-base-selector, $iro-bem-element-separator + $name)); | ||
| 188 | $selector: join($selector, $sel, comma); | ||
| 189 | $parts-data: append($parts-data, ( | ||
| 190 | 'name': $name, | ||
| 191 | 'selector': $sel | ||
| 192 | )); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | $context: 'element', ( | ||
| 198 | 'parts': $parts-data, | ||
| 199 | 'selector': $selector | ||
| 200 | ); | ||
| 201 | |||
| 202 | @return $selector $context; | ||
| 203 | } | ||
| 204 | |||
| 205 | /// | ||
| 206 | /// Generate a BEM element that is related to the current element. | ||
| 207 | /// | ||
| 208 | /// The generated element selector is appended to the current element selector. The $sign | ||
| 209 | /// determines the relationship. | ||
| 210 | /// | ||
| 211 | /// @param {string} $sign - Relationshop sign, either '+' or '~' | ||
| 212 | /// @param {string} $name - First element name | ||
| 213 | /// @param {string} $names - More element names | ||
| 214 | /// | ||
| 215 | /// @content | ||
| 216 | /// | ||
| 217 | /// @throw If the element is not preceded by an element. | ||
| 218 | /// | ||
| 219 | /// @example scss - A sibling element to a single element | ||
| 220 | /// @include iro-bem-component('block') { | ||
| 221 | /// @include iro-bem-element('elem') { | ||
| 222 | /// /* some element definitions */ | ||
| 223 | /// | ||
| 224 | /// @include iro-bem-related-element('~', 'sibling') { | ||
| 225 | /// /* some sibling element definitions */ | ||
| 226 | /// } | ||
| 227 | /// } | ||
| 228 | /// } | ||
| 229 | /// | ||
| 230 | /// // Generates: | ||
| 231 | /// | ||
| 232 | /// .c-block__elem { | ||
| 233 | /// /* some element definitions */ | ||
| 234 | /// } | ||
| 235 | /// | ||
| 236 | /// .c-block__elem ~ .c-block__sibling { | ||
| 237 | /// /* some sibling element definitions */ | ||
| 238 | /// } | ||
| 239 | /// | ||
| 240 | /// @example scss - A successor element to a single element | ||
| 241 | /// @include iro-bem-component('block') { | ||
| 242 | /// @include iro-bem-element('elem') { | ||
| 243 | /// /* some element definitions */ | ||
| 244 | /// | ||
| 245 | /// @include iro-bem-related-element('+', 'successor') { | ||
| 246 | /// /* some successor element definitions */ | ||
| 247 | /// } | ||
| 248 | /// } | ||
| 249 | /// } | ||
| 250 | /// | ||
| 251 | /// // Generates: | ||
| 252 | /// | ||
| 253 | /// .c-block__elem { | ||
| 254 | /// /* some element definitions */ | ||
| 255 | /// } | ||
| 256 | /// | ||
| 257 | /// .c-block__elem + .c-block__successor { | ||
| 258 | /// /* some successor element definitions */ | ||
| 259 | /// } | ||
| 260 | /// | ||
| 261 | /// @example scss - A successor element to multiple elements | ||
| 262 | /// @include iro-bem-component('block') { | ||
| 263 | /// @include iro-bem-element('elem1', 'elem2') { | ||
| 264 | /// /* some element definitions */ | ||
| 265 | /// | ||
| 266 | /// @include iro-bem-related-element('+', 'successor') { | ||
| 267 | /// /* some successor element definitions */ | ||
| 268 | /// } | ||
| 269 | /// } | ||
| 270 | /// } | ||
| 271 | /// | ||
| 272 | /// // Generates: | ||
| 273 | /// | ||
| 274 | /// .c-block__elem1, .c-block__elem2 { | ||
| 275 | /// /* some element definitions */ | ||
| 276 | /// } | ||
| 277 | /// | ||
| 278 | /// .c-block__elem1 + .c-block__successor, .c-block__elem2 + .c-block__successor { | ||
| 279 | /// /* some successor element definitions */ | ||
| 280 | /// } | ||
| 281 | /// | ||
| 282 | @mixin iro-bem-related-element($sign, $name, $names...) { | ||
| 283 | $result: iro-bem-related-element($sign, $name, $names...); | ||
| 284 | $selector: nth($result, 1); | ||
| 285 | $context: nth($result, 2); | ||
| 286 | |||
| 287 | @include iro-bem-validate( | ||
| 288 | 'related-element', | ||
| 289 | (sign: $sign, name: $name, names: $names), | ||
| 290 | $selector, | ||
| 291 | $context | ||
| 292 | ); | ||
| 293 | |||
| 294 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 295 | @at-root #{$selector} { | ||
| 296 | @content; | ||
| 297 | } | ||
| 298 | @include iro-context-pop($iro-bem-context-id); | ||
| 299 | } | ||
| 300 | |||
| 301 | /// | ||
| 302 | /// Generate a new BEM element that is related to the current element. | ||
| 303 | /// Check the respective mixin documentation for more information. | ||
| 304 | /// | ||
| 305 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 306 | /// | ||
| 307 | /// @see {mixin} iro-bem-related-element | ||
| 308 | /// | ||
| 309 | @function iro-bem-related-element($sign, $name, $names...) { | ||
| 310 | // | ||
| 311 | // Generating this selector is simple: Take the latest block context, use it | ||
| 312 | // to generate the element part, and insert it at the end of the current selector. | ||
| 313 | // Possible outcomes: | ||
| 314 | // - {e} ({m,s}) ([manual selector]) + {e} | ||
| 315 | // - {e} ({m,s}) ([manual selector]) ~ {e} | ||
| 316 | // | ||
| 317 | |||
| 318 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 319 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'element'); | ||
| 320 | |||
| 321 | @if $sign != '+' and $sign != '~' { | ||
| 322 | @error 'Invalid relationship sign #{inspect($sign)}.'; | ||
| 323 | } | ||
| 324 | |||
| 325 | $block-context: iro-context-get($iro-bem-context-id, 'block'); | ||
| 326 | $block-base-selector: map-get(nth($block-context, 2), 'base-selector'); | ||
| 327 | |||
| 328 | $selector: (); | ||
| 329 | $parts-data: (); | ||
| 330 | |||
| 331 | @each $name in join($name, $names) { | ||
| 332 | $sel: selector-nest(&, $sign, selector-append($block-base-selector, $iro-bem-element-separator + $name)); | ||
| 333 | $selector: join($selector, $sel, comma); | ||
| 334 | $parts-data: append($parts-data, ( | ||
| 335 | 'name': $name, | ||
| 336 | 'selector': $sel | ||
| 337 | )); | ||
| 338 | } | ||
| 339 | |||
| 340 | $context: 'element', ( | ||
| 341 | 'parts': $parts-data, | ||
| 342 | 'selector': $selector | ||
| 343 | ); | ||
| 344 | |||
| 345 | @return $selector $context; | ||
| 346 | } | ||
| 347 | |||
| 348 | /// | ||
| 349 | /// Generate a BEM element that is a sibling of the current element. | ||
| 350 | /// | ||
| 351 | /// It's a shorthand for iro-bem-related-element('~', $name). | ||
| 352 | /// | ||
| 353 | /// @param {string} $name - First element name | ||
| 354 | /// @param {list} $names - List of more element names | ||
| 355 | /// | ||
| 356 | /// @content | ||
| 357 | /// | ||
| 358 | @mixin iro-bem-sibling-element($name, $names...) { | ||
| 359 | @include iro-bem-related-element('~', $name, $names...) { | ||
| 360 | @content; | ||
| 361 | } | ||
| 362 | } | ||
| 363 | |||
| 364 | /// | ||
| 365 | /// Generate a new BEM element that is a sibling of the current element. | ||
| 366 | /// Check the respective mixin documentation for more information. | ||
| 367 | /// | ||
| 368 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 369 | /// | ||
| 370 | /// @see {mixin} iro-bem-sibling-element | ||
| 371 | /// | ||
| 372 | @function iro-bem-sibling-element($name, $names...) { | ||
| 373 | @return iro-bem-related-element('~', $name, $names...); | ||
| 374 | } | ||
| 375 | |||
| 376 | /// | ||
| 377 | /// Generate a BEM element that is the successor of the current element. | ||
| 378 | /// | ||
| 379 | /// It's a shorthand for iro-bem-related-element('+', $name). | ||
| 380 | /// | ||
| 381 | /// @param {string} $name - First element name | ||
| 382 | /// @param {string} $names - More element names | ||
| 383 | /// | ||
| 384 | /// @content | ||
| 385 | /// | ||
| 386 | @mixin iro-bem-next-element($name, $names...) { | ||
| 387 | @include iro-bem-related-element('+', $name, $names...) { | ||
| 388 | @content; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | |||
| 392 | /// | ||
| 393 | /// Generate a new BEM element that is the successor of the current element. | ||
| 394 | /// Check the respective mixin documentation for more information. | ||
| 395 | /// | ||
| 396 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 397 | /// | ||
| 398 | /// @see {mixin} iro-bem-next-element | ||
| 399 | /// | ||
| 400 | @function iro-bem-next-element($name, $names...) { | ||
| 401 | @return iro-bem-related-element('+', $name, $names...); | ||
| 402 | } | ||
| 403 | |||
| 404 | /// | ||
| 405 | /// Generate the current BEM element as a successor of itself. | ||
| 406 | /// | ||
| 407 | /// If this is applied to a single element, it behaves exactly the same as | ||
| 408 | /// iro-bem-related-element('+', name); | ||
| 409 | /// However, if it is applied to multiple elements, each twin element only will influence | ||
| 410 | /// their other twin, which is not replicable with iro-bem-related-element. | ||
| 411 | /// | ||
| 412 | /// @content | ||
| 413 | /// | ||
| 414 | /// @example scss - Two twin elements | ||
| 415 | /// @include iro-bem-component('block') { | ||
| 416 | /// @include iro-bem-element('elem') { | ||
| 417 | /// /* some element definitions */ | ||
| 418 | /// | ||
| 419 | /// @include iro-bem-next-twin-element { | ||
| 420 | /// /* some twin element definitions */ | ||
| 421 | /// } | ||
| 422 | /// } | ||
| 423 | /// } | ||
| 424 | /// | ||
| 425 | /// // Generates: | ||
| 426 | /// | ||
| 427 | /// .c-block__elem { | ||
| 428 | /// /* some element definitions */ | ||
| 429 | /// } | ||
| 430 | /// | ||
| 431 | /// .c-block__elem + .c-block__elem { | ||
| 432 | /// /* some twin element definitions */ | ||
| 433 | /// } | ||
| 434 | /// | ||
| 435 | /// @example scss - Multiple twin elements | ||
| 436 | /// @include iro-bem-component('block') { | ||
| 437 | /// @include iro-bem-element('elem1', 'elem2') { | ||
| 438 | /// /* some element definitions */ | ||
| 439 | /// | ||
| 440 | /// @include iro-bem-next-twin-element { | ||
| 441 | /// /* some twin element definitions */ | ||
| 442 | /// } | ||
| 443 | /// } | ||
| 444 | /// } | ||
| 445 | /// | ||
| 446 | /// // Generates: | ||
| 447 | /// | ||
| 448 | /// .c-block__elem1, .c-block__elem2 { | ||
| 449 | /// /* some element definitions */ | ||
| 450 | /// } | ||
| 451 | /// | ||
| 452 | /// .c-block__elem1 + .c-block__elem1, .c-block__elem2 + .c-block__elem2 { | ||
| 453 | /// /* some twin element definitions */ | ||
| 454 | /// } | ||
| 455 | /// | ||
| 456 | @mixin iro-bem-related-twin-element($sign) { | ||
| 457 | $result: iro-bem-related-twin-element($sign); | ||
| 458 | $selector: nth($result, 1); | ||
| 459 | $context: nth($result, 2); | ||
| 460 | |||
| 461 | @include iro-bem-validate( | ||
| 462 | 'next-twin-element', | ||
| 463 | (), | ||
| 464 | $selector, | ||
| 465 | $context | ||
| 466 | ); | ||
| 467 | |||
| 468 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 469 | @at-root #{$selector} { | ||
| 470 | @content; | ||
| 471 | } | ||
| 472 | @include iro-context-pop($iro-bem-context-id); | ||
| 473 | } | ||
| 474 | |||
| 475 | /// | ||
| 476 | /// Generate the current BEM element as a successor of itself. | ||
| 477 | /// Check the respective mixin documentation for more information. | ||
| 478 | /// | ||
| 479 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 480 | /// | ||
| 481 | /// @see {mixin} iro-bem-next-twin-element | ||
| 482 | /// | ||
| 483 | @function iro-bem-related-twin-element($sign) { | ||
| 484 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 485 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'element'); | ||
| 486 | |||
| 487 | $element-context: iro-context-get($iro-bem-context-id, 'element'); | ||
| 488 | $element-selector: map-get(nth($element-context, 2), 'selector'); | ||
| 489 | |||
| 490 | $block-context: iro-context-get($iro-bem-context-id, 'block'); | ||
| 491 | $block-base-selector: map-get(nth($block-context, 2), 'base-selector'); | ||
| 492 | |||
| 493 | $selector: (); | ||
| 494 | $parts-data: (); | ||
| 495 | |||
| 496 | // | ||
| 497 | // To determine the twin for each element, iterate the sub-selectors from the current selector | ||
| 498 | // and check if it contains the currently inspected element. This has to be done with string | ||
| 499 | // comparison since none of Sass selector functions is of use here. | ||
| 500 | // Finally, the current twin will be appended to the extracted sub-selector as a successor | ||
| 501 | // element. | ||
| 502 | // | ||
| 503 | @each $part-data in map-get(nth($element-context, 2), 'parts') { | ||
| 504 | $part-selector: map-get($part-data, 'selector'); | ||
| 505 | $part-name: map-get($part-data, 'name'); | ||
| 506 | |||
| 507 | $sel: (); | ||
| 508 | @if iro-selector-suffix-match(&, $element-selector) { | ||
| 509 | // | ||
| 510 | // This mixin is included in the selector the last element mixin created. | ||
| 511 | // Possible outcomes: | ||
| 512 | // - {e} + {e} | ||
| 513 | // - [manual selector] {e} + {e} | ||
| 514 | // | ||
| 515 | |||
| 516 | @each $s in & { | ||
| 517 | @each $ps in $part-selector { | ||
| 518 | @if nth($s, -1) == nth($ps, -1) { | ||
| 519 | $sel-ent: selector-nest($s, $sign, selector-append($block-base-selector, $iro-bem-element-separator + $part-name)); | ||
| 520 | $sel: join($sel, $sel-ent, comma); | ||
| 521 | } | ||
| 522 | } | ||
| 523 | } | ||
| 524 | } @else { | ||
| 525 | // | ||
| 526 | // This mixin is NOT included in the selector the last element mixin created. | ||
| 527 | // Possible outcomes: | ||
| 528 | // - {e} {m,s} + {e} | ||
| 529 | // - {e} [manual selector] + {e} | ||
| 530 | // - {e} {m,s} [manual selector] + {e} | ||
| 531 | // | ||
| 532 | |||
| 533 | @each $s in & { | ||
| 534 | @each $ps in $part-selector { | ||
| 535 | @if str-index(inspect($s), inspect($ps)) { | ||
| 536 | $char-index: str-length(inspect($ps)) + 1; | ||
| 537 | $match: index(' ' ':' ',', str-slice(inspect($s), $char-index, $char-index)) != null; | ||
| 538 | |||
| 539 | @if not $match { | ||
| 540 | @each $separator in $iro-bem-element-separator $iro-bem-modifier-separator $iro-bem-suffix-separator { | ||
| 541 | @if str-slice(inspect($s), $char-index, $char-index + str-length($separator) - 1) == $separator { | ||
| 542 | $match: true; | ||
| 543 | } | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | @if $match { | ||
| 548 | $sel-ent: selector-nest($s, '+', selector-append($block-base-selector, $iro-bem-element-separator + $part-name)); | ||
| 549 | $sel: join($sel, $sel-ent, comma); | ||
| 550 | } | ||
| 551 | } | ||
| 552 | } | ||
| 553 | } | ||
| 554 | } | ||
| 555 | @if length($sel) != length($part-selector) { | ||
| 556 | @error 'Could not generate twin element selector.'; | ||
| 557 | } | ||
| 558 | |||
| 559 | $selector: join($selector, $sel, comma); | ||
| 560 | $parts-data: append($parts-data, ( | ||
| 561 | 'name': $part-name, | ||
| 562 | 'selector': $sel | ||
| 563 | )); | ||
| 564 | } | ||
| 565 | |||
| 566 | $context: 'element', ( | ||
| 567 | 'parts': $parts-data, | ||
| 568 | 'selector': $selector | ||
| 569 | ); | ||
| 570 | |||
| 571 | @return $selector $context; | ||
| 572 | } | ||
| 573 | |||
| 574 | /// | ||
| 575 | /// Generate the current BEM element as a sibling of itself. | ||
| 576 | /// | ||
| 577 | /// It's a shorthand for iro-bem-related-twin-element('~'). | ||
| 578 | /// | ||
| 579 | /// @content | ||
| 580 | /// | ||
| 581 | @mixin iro-bem-sibling-twin-element { | ||
| 582 | @include iro-bem-related-twin-element('~') { | ||
| 583 | @content; | ||
| 584 | } | ||
| 585 | } | ||
| 586 | |||
| 587 | /// | ||
| 588 | /// Generate the current BEM element as a sibling of itself. | ||
| 589 | /// Check the respective mixin documentation for more information. | ||
| 590 | /// | ||
| 591 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 592 | /// | ||
| 593 | /// @see {mixin} iro-bem-sibling-twin-element | ||
| 594 | /// | ||
| 595 | @function iro-bem-sibling-twin-element() { | ||
| 596 | @return iro-bem-related-twin-element('~'); | ||
| 597 | } | ||
| 598 | |||
| 599 | /// | ||
| 600 | /// Generate the current BEM element as a next sibling of itself. | ||
| 601 | /// | ||
| 602 | /// It's a shorthand for iro-bem-related-twin-element('+', $name). | ||
| 603 | /// | ||
| 604 | /// @content | ||
| 605 | /// | ||
| 606 | @mixin iro-bem-next-twin-element { | ||
| 607 | @include iro-bem-related-twin-element('+') { | ||
| 608 | @content; | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | /// | ||
| 613 | /// Generate the current BEM element as a next sibling of itself. | ||
| 614 | /// Check the respective mixin documentation for more information. | ||
| 615 | /// | ||
| 616 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 617 | /// | ||
| 618 | /// @see {mixin} iro-bem-next-twin-element | ||
| 619 | /// | ||
| 620 | @function iro-bem-next-twin-element() { | ||
| 621 | @return iro-bem-related-twin-element('+'); | ||
| 622 | } | ||
| diff --git a/src/bem/_functions.scss b/src/bem/_functions.scss new file mode 100644 index 0000000..4bb95c4 --- /dev/null +++ b/src/bem/_functions.scss | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// @access private | ||
| 9 | /// | ||
| 10 | @function iro-bem-theme-selector($name, $names...) { | ||
| 11 | $namespace: map-get($iro-bem-namespaces, 'theme'); | ||
| 12 | $selector: null; | ||
| 13 | |||
| 14 | @each $name in join($name, $names) { | ||
| 15 | $sel: '.' + $namespace + '-' + $name; | ||
| 16 | |||
| 17 | @if $selector == null { | ||
| 18 | $selector: join(selector-parse($sel), selector-parse('[class*=\' t-\'] ' + $sel), comma); | ||
| 19 | $selector: join($selector, selector-parse('[class^=\'t-\'] ' + $sel), comma); | ||
| 20 | } @else { | ||
| 21 | $selector: selector-nest($selector, $sel); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | @return $selector; | ||
| 26 | } | ||
| diff --git a/src/bem/_modifier.scss b/src/bem/_modifier.scss new file mode 100644 index 0000000..e1f9507 --- /dev/null +++ b/src/bem/_modifier.scss | |||
| @@ -0,0 +1,246 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Generate a new BEM modifier. | ||
| 9 | /// | ||
| 10 | /// If the parent context is block or element, the modifier will modify said block or element according | ||
| 11 | /// to the BEM naming convention. | ||
| 12 | /// | ||
| 13 | /// If the parent context is a modifier or suffix, then the modifier will depend on said modifier or suffix. | ||
| 14 | /// Depending on $extend, the meaning of this dependency (and the resulting selector) varies: | ||
| 15 | /// If it's false (default), you signalize that the modifier also exists by itself, but it changes its | ||
| 16 | /// behavior when the parent modifier or suffix is set. | ||
| 17 | /// If it's true, you signalize that the modifier extends the parent modifier or suffix and can only be | ||
| 18 | /// used in conjunction with it. | ||
| 19 | /// | ||
| 20 | /// @param {string | list} $name - First element name or list with two items: 1. first element name, 2. bool indicating if the modifier is extending | ||
| 21 | /// @param {string | list} $names - More element names or lists with two items: 1. element name, 2. bool indicating if the modifier is extending | ||
| 22 | /// | ||
| 23 | /// @content | ||
| 24 | /// | ||
| 25 | /// @throw If the element is not preceded by a block, element, modifier or suffix. | ||
| 26 | /// | ||
| 27 | /// @example scss - Modifier that modifies a block or element | ||
| 28 | /// @include iro-bem-component('block') { | ||
| 29 | /// @include iro-bem-modifier('mod') { | ||
| 30 | /// background-color: #eee; | ||
| 31 | /// } | ||
| 32 | /// | ||
| 33 | /// @include iro-bem-element('elem') { | ||
| 34 | /// @include iro-bem-modifier('mod') { | ||
| 35 | /// background-color: #222; | ||
| 36 | /// } | ||
| 37 | /// } | ||
| 38 | /// } | ||
| 39 | /// | ||
| 40 | /// // Generates: | ||
| 41 | /// | ||
| 42 | /// .c-block--mod { | ||
| 43 | /// background-color: #eee; | ||
| 44 | /// } | ||
| 45 | /// | ||
| 46 | /// .c-block__elem--mod { | ||
| 47 | /// background-color: #222; | ||
| 48 | /// } | ||
| 49 | /// | ||
| 50 | /// @example scss - Modifier nested in another modifier, not extending | ||
| 51 | /// @include iro-bem-component('block') { | ||
| 52 | /// @include iro-bem-modifier('mod') { | ||
| 53 | /// background-color: #eee; | ||
| 54 | /// } | ||
| 55 | /// | ||
| 56 | /// @include iro-bem-modifier('dark') { | ||
| 57 | /// /* some definitions */ | ||
| 58 | /// | ||
| 59 | /// @include iro-bem-modifier('mod') { | ||
| 60 | /// background-color: #222; | ||
| 61 | /// } | ||
| 62 | /// } | ||
| 63 | /// } | ||
| 64 | /// | ||
| 65 | /// // Generates: | ||
| 66 | /// | ||
| 67 | /// .c-block--mod { | ||
| 68 | /// background-color: #eee; | ||
| 69 | /// } | ||
| 70 | /// | ||
| 71 | /// .c-block--dark { | ||
| 72 | /// /* some definitions */ | ||
| 73 | /// } | ||
| 74 | /// | ||
| 75 | /// .c-block--dark.c-block--mod { | ||
| 76 | /// background-color: #222; | ||
| 77 | /// } | ||
| 78 | /// | ||
| 79 | /// @example scss - Modifier nested in another modifier, extending | ||
| 80 | /// @include iro-bem-component('block') { | ||
| 81 | /// @include iro-bem-modifier('mod') { | ||
| 82 | /// background-color: #eee; | ||
| 83 | /// } | ||
| 84 | /// | ||
| 85 | /// @include iro-bem-modifier('dark') { | ||
| 86 | /// /* some definitions */ | ||
| 87 | /// | ||
| 88 | /// @include iro-bem-modifier('mod' true) { | ||
| 89 | /// background-color: #222; | ||
| 90 | /// } | ||
| 91 | /// } | ||
| 92 | /// } | ||
| 93 | /// | ||
| 94 | /// // Generates: | ||
| 95 | /// | ||
| 96 | /// .c-block--mod { | ||
| 97 | /// background-color: #eee; | ||
| 98 | /// } | ||
| 99 | /// | ||
| 100 | /// .c-block--dark { | ||
| 101 | /// /* some definitions */ | ||
| 102 | /// } | ||
| 103 | /// | ||
| 104 | /// .c-block--dark--mod { | ||
| 105 | /// background-color: #222; | ||
| 106 | /// } | ||
| 107 | /// | ||
| 108 | @mixin iro-bem-modifier($name, $names...) { | ||
| 109 | $result: iro-bem-modifier($name, $names...); | ||
| 110 | $selector: nth($result, 1); | ||
| 111 | $context: nth($result, 2); | ||
| 112 | |||
| 113 | @include iro-bem-validate( | ||
| 114 | 'modifier', | ||
| 115 | (name: $name, names: $names), | ||
| 116 | $selector, | ||
| 117 | $context | ||
| 118 | ); | ||
| 119 | |||
| 120 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 121 | @at-root #{$selector} { | ||
| 122 | @content; | ||
| 123 | } | ||
| 124 | @include iro-context-pop($iro-bem-context-id); | ||
| 125 | } | ||
| 126 | |||
| 127 | /// | ||
| 128 | /// Generate a new BEM modifier. Check the respective mixin documentation for more information. | ||
| 129 | /// | ||
| 130 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 131 | /// | ||
| 132 | /// @see {mixin} iro-bem-modifier | ||
| 133 | /// | ||
| 134 | @function iro-bem-modifier($name, $names...) { | ||
| 135 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 136 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); | ||
| 137 | |||
| 138 | $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element' 'modifier' 'suffix'); | ||
| 139 | $parent-selector: map-get(nth($parent-context, 2), 'selector'); | ||
| 140 | $selector: (); | ||
| 141 | $parts-data: (); | ||
| 142 | |||
| 143 | @if not iro-selector-suffix-match(&, $parent-selector) { | ||
| 144 | // | ||
| 145 | // The current selector doesn't match the parent selector. | ||
| 146 | // The user manually added a selector between parent context and this modifier call. | ||
| 147 | // This case is forbidden because any outcome semantically wouldn't make sense: | ||
| 148 | // - {b,e,m,s} [manual selector] {b,e,m,s}--modifier | ||
| 149 | // - {b,e,m,s}--modifier [manual selector] | ||
| 150 | // The first case would make the modifier behave like an element. | ||
| 151 | // The second case is unintuitive, the code would be more clear by nesting the manual | ||
| 152 | // selector in the modifier instead. | ||
| 153 | // | ||
| 154 | |||
| 155 | @error 'A modifier must be an immediate child of the parent context'; | ||
| 156 | } | ||
| 157 | |||
| 158 | @each $name in iro-list-prepend($names, $name) { | ||
| 159 | $extend: false; | ||
| 160 | @if type-of($name) == list { | ||
| 161 | $extend: nth($name, 2); | ||
| 162 | $name: nth($name, 1); | ||
| 163 | } | ||
| 164 | |||
| 165 | @if index('block' 'element', nth($parent-context, 1)) or $extend == true { | ||
| 166 | // | ||
| 167 | // Either the parent context is block or element, or a modifier or suffix | ||
| 168 | // is to be extended. The modifier part can simply be appended. | ||
| 169 | // Possible outcomes: | ||
| 170 | // - {b,e,m,s}--modifier | ||
| 171 | // | ||
| 172 | |||
| 173 | $sel: selector-append(&, $iro-bem-modifier-separator + $name); | ||
| 174 | $selector: join($selector, $sel, comma); | ||
| 175 | $parts-data: append($parts-data, ( | ||
| 176 | 'name': $name, | ||
| 177 | 'selector': $sel | ||
| 178 | )); | ||
| 179 | } @else { | ||
| 180 | // | ||
| 181 | // Parent context is modifier, suffix or state and $extend is false. | ||
| 182 | // | ||
| 183 | |||
| 184 | $be-context: iro-context-get($iro-bem-context-id, 'block' 'element'); | ||
| 185 | |||
| 186 | @if nth($be-context, 1) == 'element' { | ||
| 187 | // | ||
| 188 | // Latest context is element. Since element contexts can consist of multiple single | ||
| 189 | // elements, inspect all elements and append its selector with the suffix "--$name". | ||
| 190 | // This has to be done with string comparison since none of Sass selector functions | ||
| 191 | // is of use here. | ||
| 192 | // Possible outcomes: | ||
| 193 | // - {m,s}.{e}--modifier | ||
| 194 | // | ||
| 195 | |||
| 196 | $nsel: (); | ||
| 197 | |||
| 198 | @each $elem-part-data in map-get(nth($be-context, 2), 'parts') { | ||
| 199 | $elem-part-selector: map-get($elem-part-data, 'selector'); | ||
| 200 | |||
| 201 | $sel: (); | ||
| 202 | @each $s in & { | ||
| 203 | @each $ps in $elem-part-selector { | ||
| 204 | @if str-index(inspect($s), inspect($ps) + $iro-bem-modifier-separator) or str-index(inspect($s), inspect($ps) + $iro-bem-suffix-separator) { | ||
| 205 | $sel: join($sel, selector-unify($s, selector-append($ps, $iro-bem-modifier-separator + $name)), comma); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | @if length($sel) == 0 { | ||
| 210 | @error 'Could not generate modifier selector.'; | ||
| 211 | } | ||
| 212 | |||
| 213 | $nsel: join($nsel, $sel, comma); | ||
| 214 | } | ||
| 215 | |||
| 216 | $selector: join($selector, $nsel, comma); | ||
| 217 | $parts-data: append($parts-data, ( | ||
| 218 | 'name': $name, | ||
| 219 | 'selector': $nsel | ||
| 220 | )); | ||
| 221 | } @else { | ||
| 222 | // | ||
| 223 | // Latest context is block. Just append the modifier part. | ||
| 224 | // Possible outcomes: | ||
| 225 | // - {m,s}.{b}--modifier | ||
| 226 | // | ||
| 227 | |||
| 228 | $block-base-selector: map-get(nth($be-context, 2), 'base-selector'); | ||
| 229 | |||
| 230 | $sel: selector-append(&, $block-base-selector, $iro-bem-modifier-separator + $name); | ||
| 231 | $selector: join($selector, $sel, comma); | ||
| 232 | $parts-data: append($parts-data, ( | ||
| 233 | 'name': $name, | ||
| 234 | 'selector': $sel | ||
| 235 | )); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | $context: 'modifier', ( | ||
| 241 | 'parts': $parts-data, | ||
| 242 | 'selector': $selector | ||
| 243 | ); | ||
| 244 | |||
| 245 | @return $selector $context; | ||
| 246 | } | ||
| diff --git a/src/bem/_multi.scss b/src/bem/_multi.scss new file mode 100644 index 0000000..9e47ce4 --- /dev/null +++ b/src/bem/_multi.scss | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Generate multiple entities (BEM or not) at once. | ||
| 9 | /// | ||
| 10 | /// NOTE: This mixin does not generate perfectly optimized selectors in order to keep track of contexts. | ||
| 11 | /// | ||
| 12 | /// @param {string | list} $first - First selector. Either a string for a manual selector, or a list with the first items standing for a BEM selector function (optionally suffixed by a colon) and other items being passed as arguments to said function. | ||
| 13 | /// @param {string | list} $others - Other selectors. Either a string for a manual selector, or a list with the first items standing for a BEM selector function (optionally suffixed by a colon) and other items being passed as arguments to said function. | ||
| 14 | /// | ||
| 15 | /// @content | ||
| 16 | /// | ||
| 17 | /// @example scss - Creating multiple elements, a modifier and an anchor | ||
| 18 | /// @include iro-bem-object('buttonstrip') { | ||
| 19 | /// display: none; | ||
| 20 | /// | ||
| 21 | /// @include iro-bem-multi('modifier' 'mod', 'element' 'button' 'separator', '> a') { | ||
| 22 | /// display: block; | ||
| 23 | /// } | ||
| 24 | /// } | ||
| 25 | /// | ||
| 26 | /// // Generates: | ||
| 27 | /// | ||
| 28 | /// .o-buttonstrip { | ||
| 29 | /// display: none; | ||
| 30 | /// } | ||
| 31 | /// | ||
| 32 | /// .o-buttonstrip--mod { | ||
| 33 | /// display: block; | ||
| 34 | /// } | ||
| 35 | /// | ||
| 36 | /// .o-buttonstrip__button, { | ||
| 37 | /// .o-buttonstrip__separator { | ||
| 38 | /// display: block; | ||
| 39 | /// } | ||
| 40 | /// | ||
| 41 | /// .o-buttonstrip > a { | ||
| 42 | /// display: block; | ||
| 43 | /// } | ||
| 44 | /// | ||
| 45 | /// @example scss - Creating multiple elements, a modifier and an anchor - optional colons included | ||
| 46 | /// @include iro-bem-object('buttonstrip') { | ||
| 47 | /// display: none; | ||
| 48 | /// | ||
| 49 | /// @include iro-bem-multi('modifier:' 'mod', 'element:' 'button' 'separator', '> a') { | ||
| 50 | /// display: block; | ||
| 51 | /// } | ||
| 52 | /// } | ||
| 53 | /// | ||
| 54 | /// // Generates: | ||
| 55 | /// | ||
| 56 | /// .o-buttonstrip { | ||
| 57 | /// display: none; | ||
| 58 | /// } | ||
| 59 | /// | ||
| 60 | /// .o-buttonstrip--mod { | ||
| 61 | /// display: block; | ||
| 62 | /// } | ||
| 63 | /// | ||
| 64 | /// .o-buttonstrip__button, { | ||
| 65 | /// .o-buttonstrip__separator { | ||
| 66 | /// display: block; | ||
| 67 | /// } | ||
| 68 | /// | ||
| 69 | /// .o-buttonstrip > a { | ||
| 70 | /// display: block; | ||
| 71 | /// } | ||
| 72 | /// | ||
| 73 | @mixin iro-bem-multi($first, $others...) { | ||
| 74 | @include iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 75 | |||
| 76 | @each $entity in iro-list-prepend($others, $first) { | ||
| 77 | $is-manual-selector: false; | ||
| 78 | |||
| 79 | @if type-of($entity) == string and not function-exists('iro-bem-' + $entity) { | ||
| 80 | $is-manual-selector: true; | ||
| 81 | } | ||
| 82 | |||
| 83 | @if $is-manual-selector { | ||
| 84 | $sel: if(&, selector-nest(&, $entity), selector-parse($entity)); | ||
| 85 | |||
| 86 | @at-root #{$sel} { | ||
| 87 | @content; | ||
| 88 | } | ||
| 89 | } @else { | ||
| 90 | $entity-func-id: null; | ||
| 91 | |||
| 92 | @if type-of($entity) == list { | ||
| 93 | $entity-func-id: nth($entity, 1); | ||
| 94 | $entity: iro-list-slice($entity, 2); | ||
| 95 | } @else { | ||
| 96 | $entity-func-id: $entity; | ||
| 97 | $entity: (); | ||
| 98 | } | ||
| 99 | |||
| 100 | @if str-slice($entity-func-id, str-length($entity-func-id)) == ':' { | ||
| 101 | $entity-func-id: str-slice($entity-func-id, 1, str-length($entity-func-id) - 1); | ||
| 102 | } | ||
| 103 | |||
| 104 | $sel-func: null; | ||
| 105 | |||
| 106 | @if function-exists('iro-bem-' + $entity-func-id) { | ||
| 107 | $sel-func: get-function('iro-bem-' + $entity-func-id); | ||
| 108 | } @else if function-exists($entity-func-id) { | ||
| 109 | $sel-func: get-function($entity-func-id); | ||
| 110 | } | ||
| 111 | |||
| 112 | @if $sel-func == null { | ||
| 113 | @error 'Function "#{inspect($entity-func-id)}" was not found.'; | ||
| 114 | } | ||
| 115 | |||
| 116 | $entity-result: call($sel-func, $entity...); | ||
| 117 | $entity-result-selector: nth($entity-result, 1); | ||
| 118 | $entity-result-context: nth($entity-result, 2); | ||
| 119 | |||
| 120 | @if $entity-result-context != null { | ||
| 121 | @include iro-context-push($iro-bem-context-id, $entity-result-context...); | ||
| 122 | } | ||
| 123 | @at-root #{$entity-result-selector} { | ||
| 124 | @content; | ||
| 125 | } | ||
| 126 | @if $entity-result-context != null { | ||
| 127 | @include iro-context-pop($iro-bem-context-id); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| diff --git a/src/bem/_state.scss b/src/bem/_state.scss new file mode 100644 index 0000000..4a85bbb --- /dev/null +++ b/src/bem/_state.scss | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Create a new state rule. | ||
| 9 | /// | ||
| 10 | /// @param {string} $state - First state name | ||
| 11 | /// @param {list} $states - List of more state names | ||
| 12 | /// | ||
| 13 | /// @content | ||
| 14 | /// | ||
| 15 | /// @example scss - Using single is-state | ||
| 16 | /// @include iro-bem-object('menu') { | ||
| 17 | /// display: none; | ||
| 18 | /// | ||
| 19 | /// @include iro-bem-state('is', open') { | ||
| 20 | /// display: block; | ||
| 21 | /// } | ||
| 22 | /// } | ||
| 23 | /// | ||
| 24 | /// // Generates: | ||
| 25 | /// | ||
| 26 | /// .o-menu { | ||
| 27 | /// display: none; | ||
| 28 | /// } | ||
| 29 | /// | ||
| 30 | /// .o-menu.is-open { | ||
| 31 | /// display: block; | ||
| 32 | /// } | ||
| 33 | /// | ||
| 34 | /// @example scss - Using multiple is-states | ||
| 35 | /// @include iro-bem-object('menu') { | ||
| 36 | /// display: none; | ||
| 37 | /// | ||
| 38 | /// @include iro-bem-state('is', open', 'visible') { | ||
| 39 | /// display: block; | ||
| 40 | /// } | ||
| 41 | /// } | ||
| 42 | /// | ||
| 43 | /// // Generates: | ||
| 44 | /// | ||
| 45 | /// .o-menu { | ||
| 46 | /// display: none; | ||
| 47 | /// } | ||
| 48 | /// | ||
| 49 | /// .o-menu.is-open, | ||
| 50 | /// .o-menu.is-visible { | ||
| 51 | /// display: block; | ||
| 52 | /// } | ||
| 53 | /// | ||
| 54 | @mixin iro-bem-state($prefix, $state, $states...) { | ||
| 55 | $result: iro-bem-state($prefix, $state, $states...); | ||
| 56 | $selector: nth($result, 1); | ||
| 57 | $context: nth($result, 2); | ||
| 58 | |||
| 59 | @include iro-bem-validate( | ||
| 60 | 'state', | ||
| 61 | (prefix: $prefix, state: $state, states: $states), | ||
| 62 | $selector, | ||
| 63 | $context | ||
| 64 | ); | ||
| 65 | |||
| 66 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 67 | @at-root #{$selector} { | ||
| 68 | @content; | ||
| 69 | } | ||
| 70 | @include iro-context-pop($iro-bem-context-id); | ||
| 71 | } | ||
| 72 | |||
| 73 | /// | ||
| 74 | /// Generate a new state. | ||
| 75 | /// | ||
| 76 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 77 | /// | ||
| 78 | /// @see {mixin} iro-bem-has | ||
| 79 | /// | ||
| 80 | @function iro-bem-state($prefix, $state, $states...) { | ||
| 81 | $selector: (); | ||
| 82 | $parts-data: (); | ||
| 83 | |||
| 84 | @each $state in join($state, $states) { | ||
| 85 | $sel: selector-parse('.#{$prefix}-#{$state}'); | ||
| 86 | @if & { | ||
| 87 | $sel: selector-append(&, $sel); | ||
| 88 | } | ||
| 89 | $selector: join($selector, $sel, comma); | ||
| 90 | $parts-data: append($parts-data, ( | ||
| 91 | 'name': $state, | ||
| 92 | 'selector': $sel | ||
| 93 | )); | ||
| 94 | } | ||
| 95 | |||
| 96 | $context: 'state', ( | ||
| 97 | 'parts': $parts-data, | ||
| 98 | 'selector': $selector | ||
| 99 | ); | ||
| 100 | |||
| 101 | @return $selector $context; | ||
| 102 | } | ||
| 103 | |||
| 104 | /// | ||
| 105 | /// Create a new has-state modifier. | ||
| 106 | /// | ||
| 107 | /// It's a shorthand for iro-bem-state('is', $state, $states...). | ||
| 108 | /// | ||
| 109 | @mixin iro-bem-is($state, $states...) { | ||
| 110 | @include iro-bem-state('is', $state, $states...) { | ||
| 111 | @content; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /// | ||
| 116 | /// Generate a new is-state modifier. Check the respective mixin documentation for more information. | ||
| 117 | /// | ||
| 118 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 119 | /// | ||
| 120 | /// @see {mixin} iro-bem-is | ||
| 121 | /// | ||
| 122 | @function iro-bem-is($state, $states...) { | ||
| 123 | @return iro-bem-state('is', $state, $states...); | ||
| 124 | } | ||
| 125 | |||
| 126 | /// | ||
| 127 | /// Create a new has-state modifier. | ||
| 128 | /// | ||
| 129 | /// It's a shorthand for iro-bem-state('has', $state, $states...). | ||
| 130 | /// | ||
| 131 | @mixin iro-bem-has($state, $states...) { | ||
| 132 | @include iro-bem-state('has', $state, $states...) { | ||
| 133 | @content; | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | /// | ||
| 138 | /// Generate a new has-state modifier. Check the respective mixin documentation for more information. | ||
| 139 | /// | ||
| 140 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 141 | /// | ||
| 142 | /// @see {mixin} iro-bem-has | ||
| 143 | /// | ||
| 144 | @function iro-bem-has($state, $states...) { | ||
| 145 | @return iro-bem-state('has', $state, $states...); | ||
| 146 | } | ||
| diff --git a/src/bem/_suffix.scss b/src/bem/_suffix.scss new file mode 100644 index 0000000..b103c9f --- /dev/null +++ b/src/bem/_suffix.scss | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Generate a new suffix. | ||
| 9 | /// | ||
| 10 | /// @param {string} $name - Suffix name | ||
| 11 | /// | ||
| 12 | /// @content | ||
| 13 | /// | ||
| 14 | /// @throw If the element is not preceded by a block or modifier. | ||
| 15 | /// | ||
| 16 | /// @example scss - Using a suffix | ||
| 17 | /// @include iro-bem-utility('hidden') { | ||
| 18 | /// display: none; | ||
| 19 | /// | ||
| 20 | /// @media (max-width: 320px) { | ||
| 21 | /// @include iro-bem-suffix('phone') { | ||
| 22 | /// display: none; | ||
| 23 | /// } | ||
| 24 | /// } | ||
| 25 | /// | ||
| 26 | /// @media (max-width: 768px) { | ||
| 27 | /// @include iro-bem-suffix('tablet') { | ||
| 28 | /// display: none; | ||
| 29 | /// } | ||
| 30 | /// } | ||
| 31 | /// } | ||
| 32 | /// | ||
| 33 | /// // Generates: | ||
| 34 | /// | ||
| 35 | /// .u-hidden { | ||
| 36 | /// display: none; | ||
| 37 | /// } | ||
| 38 | /// | ||
| 39 | /// @media (max-width: 320px) { | ||
| 40 | /// .u-hidden@phone { | ||
| 41 | /// display: none; | ||
| 42 | /// } | ||
| 43 | /// } | ||
| 44 | /// | ||
| 45 | /// @media (max-width: 768px) { | ||
| 46 | /// .u-hidden@tablet { | ||
| 47 | /// display: none; | ||
| 48 | /// } | ||
| 49 | /// } | ||
| 50 | /// | ||
| 51 | @mixin iro-bem-suffix($name) { | ||
| 52 | $result: iro-bem-suffix($name); | ||
| 53 | $selector: nth($result, 1); | ||
| 54 | $context: nth($result, 2); | ||
| 55 | |||
| 56 | @include iro-bem-validate( | ||
| 57 | 'suffix', | ||
| 58 | (name: $name), | ||
| 59 | $selector, | ||
| 60 | $context | ||
| 61 | ); | ||
| 62 | |||
| 63 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 64 | @at-root #{$selector} { | ||
| 65 | @content; | ||
| 66 | } | ||
| 67 | @include iro-context-pop($iro-bem-context-id); | ||
| 68 | } | ||
| 69 | |||
| 70 | /// | ||
| 71 | /// Generate a new suffix. Check the respective mixin documentation for more information. | ||
| 72 | /// | ||
| 73 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 74 | /// | ||
| 75 | /// @see {mixin} iro-bem-suffix | ||
| 76 | /// | ||
| 77 | @function iro-bem-suffix($name) { | ||
| 78 | // | ||
| 79 | // Suffixes can be used on block, element and modifier. | ||
| 80 | // | ||
| 81 | |||
| 82 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
| 83 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); | ||
| 84 | $noop: iro-context-assert-stack-must-not-contain($iro-bem-context-id, 'suffix'); | ||
| 85 | |||
| 86 | $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element' 'modifier'); | ||
| 87 | $parent-selector: map-get(nth($parent-context, 2), 'selector'); | ||
| 88 | |||
| 89 | @if not iro-selector-suffix-match(&, $parent-selector) { | ||
| 90 | // | ||
| 91 | // The current selector doesn't match the parent selector. | ||
| 92 | // The user manually added a selector between parent context and this suffix call. | ||
| 93 | // This case is forbidden because any outcome semantically wouldn't make sense: | ||
| 94 | // - {b,e,m} [manual selector] {b,e,m}@suffix | ||
| 95 | // - {b,e,m}@suffix [manual selector] | ||
| 96 | // The first case would make the modifier behave like an element. | ||
| 97 | // The second case is unintuitive, the code would be more clear by nesting the manual | ||
| 98 | // selector in the suffix instead. | ||
| 99 | // | ||
| 100 | |||
| 101 | @error 'A suffix must be an immediate child of the parent context'; | ||
| 102 | } | ||
| 103 | |||
| 104 | // | ||
| 105 | // The suffix part can simply be appended. | ||
| 106 | // Possible outcomes: | ||
| 107 | // - {b,e,m}@suffix | ||
| 108 | // | ||
| 109 | |||
| 110 | $selector: selector-append(&, $iro-bem-suffix-separator + $name); | ||
| 111 | |||
| 112 | $context: 'suffix', ( | ||
| 113 | 'name': $name, | ||
| 114 | 'selector': $selector | ||
| 115 | ); | ||
| 116 | |||
| 117 | @return $selector $context; | ||
| 118 | } | ||
| diff --git a/src/bem/_theme.scss b/src/bem/_theme.scss new file mode 100644 index 0000000..b4bcc76 --- /dev/null +++ b/src/bem/_theme.scss | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Declare new rules for the current block for when this theme is active. | ||
| 9 | /// | ||
| 10 | /// @param {string} $name - First theme block name | ||
| 11 | /// @param {string} $names - More theme block names | ||
| 12 | /// | ||
| 13 | /// @content | ||
| 14 | /// | ||
| 15 | @mixin iro-bem-at-theme($name, $names...) { | ||
| 16 | $result: iro-bem-at-theme($name, $names...); | ||
| 17 | $selector: nth($result, 1); | ||
| 18 | $context: nth($result, 2); | ||
| 19 | |||
| 20 | @include iro-bem-validate( | ||
| 21 | 'at-theme', | ||
| 22 | (name: $name, names: $names), | ||
| 23 | $selector, | ||
| 24 | $context | ||
| 25 | ); | ||
| 26 | |||
| 27 | @include iro-context-push($iro-bem-context-id, $context...); | ||
| 28 | @at-root #{$selector} { | ||
| 29 | @content; | ||
| 30 | } | ||
| 31 | @include iro-context-pop($iro-bem-context-id); | ||
| 32 | } | ||
| 33 | |||
| 34 | /// | ||
| 35 | /// Declare new rules for the current block for when this theme is active. | ||
| 36 | /// Check the respective mixin documentation for more information. | ||
| 37 | /// | ||
| 38 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
| 39 | /// | ||
| 40 | /// @see {mixin} iro-bem-at-theme | ||
| 41 | /// | ||
| 42 | @function iro-bem-at-theme($name, $names...) { | ||
| 43 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); | ||
| 44 | |||
| 45 | $parent-context: iro-context-get($iro-bem-context-id, 'block'); | ||
| 46 | $parent-selector: map-get(nth($parent-context, 2), 'selector'); | ||
| 47 | |||
| 48 | @if not iro-selector-suffix-match(&, $parent-selector) { | ||
| 49 | @error 'An at-theme rule must be an immediate child of a block'; | ||
| 50 | } | ||
| 51 | |||
| 52 | $selector: iro-bem-theme-selector($name, $names...); | ||
| 53 | $selector: selector-nest($selector, &); | ||
| 54 | |||
| 55 | $context: 'at-theme', ( | ||
| 56 | 'name': join($name, $names), | ||
| 57 | 'selector': $selector | ||
| 58 | ); | ||
| 59 | |||
| 60 | @return $selector $context; | ||
| 61 | } | ||
| diff --git a/src/bem/_validators.scss b/src/bem/_validators.scss new file mode 100644 index 0000000..eb09a60 --- /dev/null +++ b/src/bem/_validators.scss | |||
| @@ -0,0 +1,176 @@ | |||
| 1 | //// | ||
| 2 | /// Validators are custom functions that will be called before a BEM entity is created. | ||
| 3 | /// They check if the current mixin usage is valid or not and thus they are a flexible way to | ||
| 4 | /// let you implement your own rules. | ||
| 5 | /// | ||
| 6 | /// Validator functions receive the following information: | ||
| 7 | /// - BEM entity type | ||
| 8 | /// - Arguments passed to the mixin | ||
| 9 | /// - The generated selector | ||
| 10 | /// - The generated context, if any | ||
| 11 | /// | ||
| 12 | /// Additionally, the context stack used by the BEM system can be examined. | ||
| 13 | /// | ||
| 14 | /// @group BEM | ||
| 15 | /// | ||
| 16 | /// @access public | ||
| 17 | //// | ||
| 18 | |||
| 19 | /// | ||
| 20 | /// A list of validator functions. | ||
| 21 | /// | ||
| 22 | /// @type list | ||
| 23 | /// | ||
| 24 | /// @access private | ||
| 25 | /// | ||
| 26 | $iro-bem-validators: (); | ||
| 27 | |||
| 28 | /// | ||
| 29 | /// Register one or multiple validator functions. | ||
| 30 | /// | ||
| 31 | /// A validator function is a function that accepts 4 arguments: | ||
| 32 | /// 1. BEM entity type (string) | ||
| 33 | /// 2. Arguments passed to the mixin (map) | ||
| 34 | /// 3. The generated selector (selector) | ||
| 35 | /// 4. The generated context (list, may be null) | ||
| 36 | /// | ||
| 37 | /// The function must return a list with two items: | ||
| 38 | /// 1. `true` if the mixin usage is valid, otherwise `false`, | ||
| 39 | /// 2. a string with a rejection reason (empty if the usage is valid). | ||
| 40 | /// | ||
| 41 | /// @param {string} $func-name - First function name. | ||
| 42 | /// @param {string} $func-names - Other function names. | ||
| 43 | /// | ||
| 44 | @mixin iro-bem-add-validator($func-name, $func-names...) { | ||
| 45 | $noop: iro-bem-add-validator($func-name, $func-names...); | ||
| 46 | } | ||
| 47 | |||
| 48 | /// | ||
| 49 | /// Register one or multiple validator functions. Check the respective mixin documentation for more information. | ||
| 50 | /// | ||
| 51 | /// @see {mixin} iro-bem-add-validator | ||
| 52 | /// | ||
| 53 | @function iro-bem-add-validator($func-name, $func-names...) { | ||
| 54 | @each $fn-name in join($func-name, $func-names) { | ||
| 55 | $fn: get-function($fn-name); | ||
| 56 | $iro-bem-validators: map-merge($iro-bem-validators, ($fn-name: $fn)) !global; | ||
| 57 | } | ||
| 58 | @return null; | ||
| 59 | } | ||
| 60 | |||
| 61 | /// | ||
| 62 | /// Unregister one or multiple validator functions. | ||
| 63 | /// | ||
| 64 | /// @param {string} $func-name - First function name. | ||
| 65 | /// @param {string} $func-names - Other function names. | ||
| 66 | /// | ||
| 67 | @mixin iro-bem-remove-validator($func-name, $func-names...) { | ||
| 68 | $noop: iro-bem-remove-validator($func-name, $func-names...); | ||
| 69 | } | ||
| 70 | |||
| 71 | /// | ||
| 72 | /// Unregister one or multiple validator functions. Check the respective mixin documentation for more information. | ||
| 73 | /// | ||
| 74 | /// @see {mixin} iro-bem-remove-validator | ||
| 75 | /// | ||
| 76 | @function iro-bem-remove-validator($func-name, $func-names...) { | ||
| 77 | $iro-bem-validators: map-remove($iro-bem-validators, $func-name, $func-names...) !global; | ||
| 78 | @return null; | ||
| 79 | } | ||
| 80 | |||
| 81 | /// | ||
| 82 | /// @access private | ||
| 83 | /// | ||
| 84 | @mixin iro-bem-validate($type, $args, $selector, $context) { | ||
| 85 | @each $id, $fn in $iro-bem-validators { | ||
| 86 | $result: call($fn, $type, $args, $selector, $context); | ||
| 87 | @if not nth($result, 1) { | ||
| 88 | @error 'A BEM validator rejected this mixin usage due to the following reason: #{nth($result, 2)}'; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | // | ||
| 94 | // --------------------------------------------------------------------------------------------------------- | ||
| 95 | // Built-in validators | ||
| 96 | // --------------------------------------------------------------------------------------------------------- | ||
| 97 | // | ||
| 98 | |||
| 99 | /// | ||
| 100 | /// A validator that makes sure blocks are declared in the right order, determined by the | ||
| 101 | /// namespace used. | ||
| 102 | /// | ||
| 103 | @function iro-bem-validator--enforce-namespace-order($type, $args, $selector, $context) { | ||
| 104 | @if not global-variable-exists(iro-bem-namespace-order) { | ||
| 105 | $iro-bem-namespace-order: map-keys($iro-bem-namespaces) !global; | ||
| 106 | } | ||
| 107 | @if not global-variable-exists(iro-bem-cur-namespace-index) { | ||
| 108 | $iro-bem-cur-namespace-index: 1 !global; | ||
| 109 | } | ||
| 110 | |||
| 111 | @if $type == 'block' { | ||
| 112 | $block-type: map-get($args, 'type'); | ||
| 113 | |||
| 114 | @if $block-type != null { | ||
| 115 | $ns-index: index($iro-bem-namespace-order, $block-type); | ||
| 116 | |||
| 117 | @if $ns-index != null { | ||
| 118 | @if $ns-index < $iro-bem-cur-namespace-index { | ||
| 119 | @return false 'Namespace "#{$block-type}" comes before current namespace #{nth($iro-bem-namespace-order, $iro-bem-cur-namespace-index)}'; | ||
| 120 | } | ||
| 121 | |||
| 122 | $iro-bem-cur-namespace-index: $ns-index !global; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | @return true ''; | ||
| 128 | } | ||
| 129 | |||
| 130 | /// | ||
| 131 | /// A validator that makes all BEM entities immutable. | ||
| 132 | /// | ||
| 133 | @function iro-bem-validator--immutable-entities($type, $args, $selector, $context) { | ||
| 134 | @if not global-variable-exists(iro-bem-generated-selectors) { | ||
| 135 | $iro-bem-generated-selectors: () !global; | ||
| 136 | } | ||
| 137 | |||
| 138 | $block-name: null; | ||
| 139 | $block-type: null; | ||
| 140 | $block-id: null; | ||
| 141 | |||
| 142 | @if $type == 'block' { | ||
| 143 | $block-name: map-get($args, 'name'); | ||
| 144 | $block-type: map-get($args, 'type'); | ||
| 145 | } @else { | ||
| 146 | $block-context: iro-context-get($iro-bem-context-id, 'block'); | ||
| 147 | $block-name: map-get(nth($block-context, 2), 'name'); | ||
| 148 | $block-type: map-get(nth($block-context, 2), 'type'); | ||
| 149 | } | ||
| 150 | |||
| 151 | @if $block-type != null { | ||
| 152 | $block-id: $block-name + '_' + $block-type; | ||
| 153 | } @else { | ||
| 154 | $block-id: $block-name; | ||
| 155 | } | ||
| 156 | |||
| 157 | @if $type == 'block' { | ||
| 158 | @if map-has-key($iro-bem-generated-selectors, $block-id) { | ||
| 159 | @return false 'Entity "#{$type}" with arguments [ #{iro-map-print($args)} ] was already defined.'; | ||
| 160 | } | ||
| 161 | |||
| 162 | $iro-bem-generated-selectors: map-merge($iro-bem-generated-selectors, ($block-id: ())) !global; | ||
| 163 | } @else { | ||
| 164 | $selectors: map-get($iro-bem-generated-selectors, $block-id); | ||
| 165 | |||
| 166 | @if index($selectors, $selector) { | ||
| 167 | @return false 'Entity "#{$type}" with arguments [ #{iro-map-print($args)} ] was already defined.'; | ||
| 168 | } | ||
| 169 | |||
| 170 | $selectors: append($selectors, $selector); | ||
| 171 | |||
| 172 | $iro-bem-generated-selectors: map-merge($iro-bem-generated-selectors, ($block-id: $selectors)) !global; | ||
| 173 | } | ||
| 174 | |||
| 175 | @return true ''; | ||
| 176 | } | ||
| diff --git a/src/bem/_vars.scss b/src/bem/_vars.scss new file mode 100644 index 0000000..5942d4f --- /dev/null +++ b/src/bem/_vars.scss | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | //// | ||
| 2 | /// @group BEM | ||
| 3 | /// | ||
| 4 | /// @access public | ||
| 5 | //// | ||
| 6 | |||
| 7 | /// | ||
| 8 | /// Separating character sequence for elements. | ||
| 9 | /// | ||
| 10 | /// @type string | ||
| 11 | /// | ||
| 12 | $iro-bem-element-separator: '__' !default; | ||
| 13 | |||
| 14 | /// | ||
| 15 | /// Separating character sequence for modifiers. | ||
| 16 | /// | ||
| 17 | /// @type string | ||
| 18 | /// | ||
| 19 | $iro-bem-modifier-separator: '--' !default; | ||
| 20 | |||
| 21 | /// | ||
| 22 | /// Separating character sequence for BEMIT suffixes. | ||
| 23 | /// | ||
| 24 | /// @type string | ||
| 25 | /// | ||
| 26 | $iro-bem-suffix-separator: '\\@' !default; | ||
| 27 | |||
| 28 | /// | ||
| 29 | /// Prefixes for all BEMIT namespaces. | ||
| 30 | /// | ||
| 31 | /// @prop {string} utility ['u'] - Utility namespace | ||
| 32 | /// @prop {string} object ['o'] - Object namespace | ||
| 33 | /// @prop {string} component ['c'] - Component namespace | ||
| 34 | /// @prop {string} layout ['l'] - Layout namespace | ||
| 35 | /// @prop {string} scope ['s'] - Scope namespace | ||
| 36 | /// @prop {string} theme ['t'] - Theme namespace | ||
| 37 | /// @prop {string} js ['js'] - JS namespace | ||
| 38 | /// @prop {string} qa ['qa'] - QA namespace | ||
| 39 | /// @prop {string} hack ['_'] - Hack namespace | ||
| 40 | /// | ||
| 41 | /// @type map | ||
| 42 | /// | ||
| 43 | $iro-bem-namespaces: ( | ||
| 44 | object: 'o', | ||
| 45 | component: 'c', | ||
| 46 | layout: 'l', | ||
| 47 | scope: 's', | ||
| 48 | theme: 't', | ||
| 49 | utility: 'u', | ||
| 50 | js: 'js', | ||
| 51 | qa: 'qa', | ||
| 52 | hack: '_' | ||
| 53 | ) !default; | ||
| 54 | |||
| 55 | /// | ||
| 56 | /// A list of all generated blocks. | ||
| 57 | /// | ||
| 58 | /// @type list | ||
| 59 | /// | ||
| 60 | /// @access private | ||
| 61 | /// | ||
| 62 | $iro-bem-blocks: (); | ||
| 63 | |||
| 64 | /// | ||
| 65 | /// Maximum nesting depth of BEM mixins. The large default value means there is no effective limit. | ||
| 66 | /// | ||
| 67 | /// @type number | ||
| 68 | /// | ||
| 69 | $iro-bem-max-depth: 99 !default; | ||
| 70 | |||
| 71 | /// | ||
| 72 | /// Indicates how nested elements should be handled. | ||
| 73 | /// | ||
| 74 | /// 'allow' means elements will be nested, i.e. the result will be {e} {b}__element. | ||
| 75 | /// 'disallow' means an error will be emitted. | ||
| 76 | /// 'append' means the element name will be appended to the parent element, i.e. the result will be {e}__element. | ||
| 77 | /// Any other value is treated as 'allow'. | ||
| 78 | /// | ||
| 79 | /// @type string | ||
| 80 | /// | ||
| 81 | $iro-bem-element-nesting-policy: 'allow' !default; | ||
| 82 | |||
| 83 | /// | ||
| 84 | /// Context ID used for all BEM-related mixins. | ||
| 85 | /// | ||
| 86 | /// @type string | ||
| 87 | /// | ||
| 88 | $iro-bem-context-id: 'bem' !default; | ||
| 89 | |||
| 90 | /// | ||
| 91 | /// Debug mode. | ||
| 92 | /// | ||
| 93 | /// @type bool | ||
| 94 | /// | ||
| 95 | $iro-bem-debug: false !default; | ||
| 96 | |||
| 97 | /// | ||
| 98 | /// Colors assigned to namespaces. | ||
| 99 | /// | ||
| 100 | /// @type map | ||
| 101 | /// | ||
| 102 | $iro-bem-debug-colors: ( | ||
| 103 | object: #ffa500, | ||
| 104 | component: #00f, | ||
| 105 | layout: #ff0, | ||
| 106 | utility: #008000, | ||
| 107 | hack: #f00 | ||
| 108 | ) !default; | ||
| diff --git a/src/harmony-shortcodes.scss b/src/harmony-shortcodes.scss new file mode 100644 index 0000000..3307a7e --- /dev/null +++ b/src/harmony-shortcodes.scss | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | //// | ||
| 2 | /// Shorter version of the harmony-related functions. Useful to reduce clutter. | ||
| 3 | /// | ||
| 4 | /// @group Harmony shortcodes | ||
| 5 | /// | ||
| 6 | /// @access public | ||
| 7 | //// | ||
| 8 | |||
| 9 | /// | ||
| 10 | /// @alias iro-harmony-modular-scale | ||
| 11 | /// | ||
| 12 | @function modular-scale($times, $base, $ratio) { | ||
| 13 | @include iro-harmony-modular-scale($times, $base, $ratio); | ||
| 14 | } | ||
| 15 | |||
| 16 | /// | ||
| 17 | /// @alias modular-scale | ||
| 18 | /// | ||
| 19 | @function ms($times, $base, $ratio) { | ||
| 20 | @include modular-scale($times, $base, $ratio); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// | ||
| 24 | /// @alias iro-responsive-modular-scale | ||
| 25 | /// | ||
| 26 | @mixin responsive-modular-scale($prop, $times, $responsive-map) { | ||
| 27 | @include iro-responsive-modular-scale($prop, $times, $responsive-map); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// | ||
| 31 | /// @alias responsive-modular-scale | ||
| 32 | /// | ||
| 33 | @mixin responsive-ms($prop, $times, $responsive-map) { | ||
| 34 | @include responsive-modular-scale($prop, $times, $responsive-map); | ||
| 35 | } | ||
| diff --git a/src/main.scss b/src/main.scss new file mode 100644 index 0000000..5b1b3d1 --- /dev/null +++ b/src/main.scss | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | @import 'functions'; | ||
| 2 | @import 'math'; | ||
| 3 | @import 'vars'; | ||
| 4 | @import 'contexts'; | ||
| 5 | @import 'bem'; | ||
| 6 | @import 'props'; | ||
| 7 | @import 'harmony'; | ||
| 8 | @import 'responsive'; | ||
| 9 | @import 'easing'; | ||
| 10 | @import 'gradients'; | ||
| diff --git a/src/prep.scss b/src/prep.scss new file mode 100644 index 0000000..616165d --- /dev/null +++ b/src/prep.scss | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | @import 'functions'; | ||
| 2 | @import 'math'; | ||
| diff --git a/src/responsive-shortcodes.scss b/src/responsive-shortcodes.scss new file mode 100644 index 0000000..e43dfc0 --- /dev/null +++ b/src/responsive-shortcodes.scss | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | //// | ||
| 2 | /// Shorter version of the responsive-related mixins. Useful to reduce clutter. | ||
| 3 | /// | ||
| 4 | /// @group Responsive shortcodes | ||
| 5 | /// | ||
| 6 | /// @access public | ||
| 7 | //// | ||
| 8 | |||
| 9 | /// | ||
| 10 | /// @alias iro-responsive-property | ||
| 11 | /// | ||
| 12 | @mixin responsive($props, $responsive-map, $fluid: true, $vertical: false) { | ||
| 13 | @include iro-responsive-property($props, $responsive-map, $fluid, $vertical); | ||
| 14 | } | ||
