// Kf - Sass animation library (v.1.0.0) // Kf is a Sass library that generates animation CSS keyframes from Sass maps. // // Documentation: http://kf-sass.com // Github: http://github.com/mil/kf-sass // "BUY MILES A BEER LICENSE" (Revision 26): // Miles Alan wrote this library. As long as you retain the // full text of this license in your project or derivative work, you can do // whatever you want with this thing. Also, if someday we meet and you want // to buy me a beer as a token of gratitude, then you should. @function _cast-list($input) { @return if(type-of($input) != list, ($input,), $input); } @function _change-list-seperator($list, $seperator: comma) { @return join($list, (), $seperator); } @function _compare-strings($a, $b) { $ord: "&:.abcdefghijklmnopqrstuvwxyz0123456789-_"; @for $i from 1 through max(str-length($a), str-length($b)) { $av: str-slice($a, $i, $i); $bv: str-slice($b, $i, $i); @if $av == "" or $av == null { @return -1; } @if $bv == "" or $bv == null { @return 1; } $ap: str-index($ord, to-lower-case($av)); $bp: str-index($ord, to-lower-case($bv)); @if $ap == null and $bp == null { @return 0; } @if $ap == null { @return -1; } @if $bp == null { @return 1; } @if $ap > $bp { @return 1; } @else if $ap < $bp { @return -1 } } @return 0; } @function _convert-s-to-ms-map-keys($map) { @each $k, $v in $map { @if unit($k) == s { $map: map-remove($map, $k); $map: map-merge($map, (0ms + $k: $v)); } } @return $map; } @function _deep-map-merge($map-one, $map-two) { @if type-of($map-one) != map or type-of($map-two) != map { @return $map-two; } @each $k, $v in $map-two { $map-one: map-merge($map-one, ($k: _deep-map-merge(map-get($map-one, $k), $v))); } @return $map-one; } @function _filter-function( $list, $function_name, $args... ) { $return_list: (); @if length($list) > 0 { @for $index from 1 through length($list) { $call-result: null; $item: nth($list, $index); $call-result: call(get-function($function_name), $item, $args...); @if $call-result { $return_list: append($return_list, $item); } } } @return $return_list; } $valid-prop-names: (align-content align-items alignment-baseline align-self all animation animation-delay animation-direction animation-duration animation-fill-mode animation-iteration-count animation-name animation-play-state animation-timing-function appearance azimuth background background-attachment background-blend-mode background-clip background-color background-image background-image-transform background-origin background-position background-repeat background-size baseline-shift block-size block-step block-step-align block-step-insert block-step-round block-step-size bookmark-label bookmark-level bookmark-state border border-block border-block-color border-block-end border-block-end-color border-block-end-style border-block-end-width border-block-start border-block-start-color border-block-start-style border-block-start-width border-block-style border-block-width border-bottom border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-style border-bottom-width border-boundary border-collapse border-color border-image border-image-outset border-image-repeat border-image-slice border-image-source border-image-transform border-image-width border-inline border-inline-color border-inline-end border-inline-end-color border-inline-end-style border-inline-end-width border-inline-start border-inline-start-color border-inline-start-style border-inline-start-width border-inline-style border-inline-width border-left border-left-color border-left-style border-left-width border-radius border-right border-right-color border-right-style border-right-width border-spacing border-style border-top border-top-color border-top-left-radius border-top-right-radius border-top-style border-top-width border-width bottom box-decoration-break box-shadow box-sizing box-snap break-after break-before break-inside caption-side caret caret-color caret-shape chains clear clip clip-path clip-rule color color-adjust color-interpolation-filters column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width columns column-span column-width contain content continue counter-increment counter-reset counter-set cue cue-after cue-before cursor direction display dominant-baseline elevation empty-cells fill fill-break fill-color fill-image fill-opacity fill-origin fill-position fill-repeat fill-rule fill-size filter flex flex-basis flex-direction flex-flow flex-grow flex-shrink flex-wrap float float-defer float-offset float-reference flood-color flood-opacity flow flow-from flow-into font font-family font-feature-settings font-kerning font-language-override font-max-size font-min-size font-optical-sizing font-palette font-size font-size-adjust font-stretch font-style font-synthesis font-variant font-variant-alternates font-variant-caps font-variant-east-asian font-variant-emoji font-variant-ligatures font-variant-numeric font-variant-position font-variation-settings font-weight footnote-display footnote-policy gap glyph-orientation-vertical grid grid-area grid-auto-columns grid-auto-flow grid-auto-rows grid-column grid-column-end grid-column-start grid-row grid-row-end grid-row-start grid-template grid-template-areas grid-template-columns grid-template-rows hanging-punctuation height hyphenate-character hyphenate-limit-chars hyphenate-limit-last hyphenate-limit-lines hyphenate-limit-zone hyphens image-orientation image-resolution initial-letter initial-letter-align initial-letter-wrap inline-size inset inset-block inset-block-end inset-block-start inset-inline inset-inline-end inset-inline-start isolation justify-content justify-items justify-self left letter-spacing lighting-color line-break line-grid line-height line-height-step line-snap list-style list-style-image list-style-position list-style-type margin margin-block margin-block-end margin-block-start margin-bottom margin-inline margin-inline-end margin-inline-start margin-left margin-right margin-top marker marker-end marker-knockout-left marker-knockout-right marker-mid marker-pattern marker-segment marker-side marker-start marquee-direction marquee-loop marquee-speed marquee-style mask mask-border mask-border-mode mask-border-outset mask-border-repeat mask-border-slice mask-border-source mask-border-width mask-clip mask-composite mask-image mask-mode mask-origin mask-position mask-repeat mask-size mask-type max-block-size max-height max-inline-size max-lines max-width min-block-size min-height min-inline-size min-width mix-blend-mode nav-down nav-left nav-right nav-up object-fit object-position offset offset-after offset-anchor offset-before offset-distance offset-end offset-path offset-position offset-rotate offset-start opacity order orphans outline outline-color outline-offset outline-style outline-width overflow overflow-style overflow-wrap overflow-x overflow-y padding padding-block padding-block-end padding-block-start padding-bottom padding-inline padding-inline-end padding-inline-start padding-left padding-right padding-top page page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range place-content place-items place-self play-during position presentation-level quotes region-fragment resize rest rest-after rest-before richness right rotation rotation-point row-gap ruby-align ruby-merge ruby-position running scrollbar-gutter scroll-behavior scroll-margin scroll-margin-block scroll-margin-block-end scroll-margin-block-start scroll-margin-bottom scroll-margin-inline scroll-margin-inline-end scroll-margin-inline-start scroll-margin-left scroll-margin-right scroll-margin-top scroll-padding scroll-padding-block scroll-padding-block-end scroll-padding-block-start scroll-padding-bottom scroll-padding-inline scroll-padding-inline-end scroll-padding-inline-start scroll-padding-left scroll-padding-right scroll-padding-top scroll-snap-align scroll-snap-stop scroll-snap-type shape-image-threshold shape-inside shape-margin shape-outside size speak speak-as speak-header speak-numeral speak-punctuation speech-rate stress string-set stroke stroke-align stroke-alignment stroke-break stroke-color stroke-dashadjust stroke-dasharray stroke-dash-corner stroke-dashcorner stroke-dash-justify stroke-dashoffset stroke-image stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-origin stroke-position stroke-repeat stroke-size stroke-width table-layout tab-size text-align text-align-all text-align-last text-combine-upright text-decoration text-decoration-color text-decoration-line text-decoration-skip text-decoration-skip-ink text-decoration-style text-decoration-width text-emphasis text-emphasis-color text-emphasis-position text-emphasis-skip text-emphasis-style text-indent text-justify text-orientation text-overflow text-shadow text-space-collapse text-space-trim text-spacing text-transform text-underline-offset text-underline-position text-wrap top transform transform-box transform-origin transition transition-delay transition-duration transition-property transition-timing-function unicode-bidi user-select vertical-align visibility voice-balance voice-duration voice-family voice-pitch voice-range voice-rate voice-stress voice-volume volume white-space widows width will-change word-break word-spacing word-wrap wrap-after wrap-before wrap-flow wrap-inside wrap-through writing-mode z-index); @function _is-a-css-property-name($str) { @if type-of($str) != string { @return false; } @if str-slice($str, 1, 1) == "-" { @return true; } @return type-of(index($valid-prop-names, $str)) == number; } @function _is-a-map($input) { @return type-of($input) == map; } @function _is-a-number($value) { @return type-of($value) == 'number'; } @function _is-a-string($value) { @return type-of($value) == 'string'; } @function _is-context-within-selector($selector) { @if $selector == null { @return true; } @if & == null { @return false; } $selector-list: nth(selector-parse($selector), 1); $context-list: nth(selector-parse(&), 1); @for $i from 1 through length($selector) { @if nth($selector, $i) != nth($context-list, $i) { @return false; } } @return true; } @function _limit-map-keys($map, $list-of-keys) { $r: _new-map(); @each $l in $list-of-keys { @if map-has-key($map, $l) { $r: map-merge( $r, ($l: map-get($map, $l)) ); } } @return $r; } @function _list-difference($a, $b) { $difference: (); @each $ai in $a { @if index($b, $ai) == null { $difference: append($difference, $ai); } } @return $difference; } @function _list-flatten($list) { $return-list: (); @each $item in $list { @if type-of($item) == list { $return-list: join($return-list, _list-flatten($item)); } @else { $return-list: append($return-list, $item); } } @return $return-list; } @function _list-sum($items) { $sum: 0; @each $i in $items { $sum: $sum + $i; } @return $sum; } @function _list-uniq($items) { $uniq: (); @each $item in $items { $is-missing: index($uniq, $item) == null; @if $is-missing { $uniq: append($uniq, $item); } } @return $uniq; } @function _map-function( $list, $function_name, $args... ) { $return_list: (); @for $index from 1 through length($list) { $call-result: call(get-function($function_name), nth($list, $index), $args...); $return_list: append($return_list, $call-result); } @return $return_list; }@function _map-has-key-with-value($map, $key, $value) { @return map-get($map, $key) == $value; } @function _new-map() { $m: (a: b); $m: map-remove($m, a); @return $m; } @function _partition-list($list, $function) { $a: (); $b: (); @each $item in $list { $call-result: call(get-function($function), $item); @if $call-result { $a: append($a, $item); } @else { $b: append($b, $item); } } @return ($a, $b); } @function _partition-map-by-key($map, $function) { $partitioned-map-keys: _partition-list( map-keys($map), $function ); $a: _limit-map-keys($map, nth($partitioned-map-keys, 1)); $b: _limit-map-keys($map, nth($partitioned-map-keys, 2)); @return ( $a, $b ); } @function _sort-list($list) { // Insertion sort implementation @if length($list) <= 1 { @return $list; } $final: (); $final: append($final, nth($list, 1)); @for $i from 2 through length($list) { $compare-to-item: nth($list, $i); $final-half-one: (); $final-half-two: (); @each $final-item in $final { $in-front: _sort-list-cmp($compare-to-item, $final-item); @if $in-front { $final-half-one: append($final-half-one, $final-item); } @else { $final-half-two: append($final-half-two, $final-item); } } $final-half-one: append($final-half-one, $compare-to-item); $final: join($final-half-one, $final-half-two); } @return $final; } @function _sort-list-cmp($a, $b) { $in-front: false; @if type-of($a) == number and type-of($b) == number { $in-front: $b < $a; } @else if type-of($a) == string and type-of($b) == string { $in-front: _compare-strings($b, $a) < 0; } @else if type-of($a) == type-of($b) { $in-front: _compare-strings(inspect($b), inspect($a)) < 0; } @else { $in-front: _compare-strings(type-of($b), type-of($a)) < 0; } @return $in-front; } @function _sort-map-keys-deep($map) { @if type-of($map) != map { @return $map; } $return-map: _new-map(); $keys: _sort-list(map-keys($map)); @each $k in $keys { $return-map: _deep-map-merge($return-map, ( $k: _sort-map-keys-deep(map-get($map, $k)) )); } @return $return-map; } @function _to-percentage($numerator, $divisor) { @return percentage($numerator / $divisor); } @function _calculate_animation_length($animation-map) { @if map-has-key($animation-map, _kf-duration) { @return map-get($animation-map, _kf-duration); } @if map-has-key($animation-map, _kf-sleep) { @return map-get($animation-map, _kf-sleep); } $animation-map: _determine-map-type($animation-map); $map-type: map-get($animation-map, _kf-map-type); $seperated: _kf-properties-extractor($animation-map); $m: nth($seperated, 1); // _kf-map-type can be one of (tepv, etpv, or eptv) // So this is the most efficient way to extract just the timing-level keys $timings: (); $timings-level-in-animation-map: str-index($map-type, t); @if $timings-level-in-animation-map == 1 { $timings: map-keys($m); } @else if $timings-level-in-animation-map == 2 { $timings: _map-function(map-values($m), map-keys); } @else if $timings-level-in-animation-map == 3 { $timings: _map-function( _list-flatten( _map-function(map-values($m), map-values) ), map-keys ); } @return max(_list-flatten($timings)...); }@function _get-value-type($v) { @if type-of($v) == list { $v: nth($v, 1); } @if type-of($v) == number and (unit($v) == s or unit($v) == ms) { @return t; } @if _is-a-css-property-name($v) { @return p; } @return e; } @function _determine-map-type($map) { @if map-get($map, _kf-map-type) != null { @return $map; } $level-1-map-keys: map-keys($map); $level-2-map-keys: map-keys(map-get($map, nth($level-1-map-keys, 1))); $level-3-map-keys: map-keys(map-get(map-get($map, nth($level-1-map-keys, 1)), nth($level-2-map-keys, 1))); $level-1-value-type: _get-value-type(nth($level-1-map-keys, 1)); $level-2-value-type: _get-value-type(nth($level-2-map-keys, 1)); $level-3-value-type: _get-value-type(nth($level-3-map-keys, 1)); $_kf-map-type: $level-1-value-type + $level-2-value-type + $level-3-value-type + "v"; @return map-merge($map, (_kf-map-type: $_kf-map-type)); } @function _expand-compound-selectors($eptv-map) { $extracted_map: (); $old-map: (); @each $element, $ptv in $eptv-map { @if type-of($element) == list { @each $e in $element { $extracted-map: _deep-map-merge($extracted-map, ($e: $ptv)); } } @else { $old-map: map-merge($old-map, ( $element: $ptv) ); } } @return if( type-of($extracted-map) == map, _deep-map-merge($old-map, $extracted-map), $old-map ); } @function _is-greater-than-three-level-map($map) { @each $v in _filter-function(map-values($map), _is-a-map) { @each $v2 in _filter-function(map-values($v), _is-a-map) { @if type-of($v2) == map { @return true; } } } @return false; } @function _expand-implicit-selector($map) { @if _is-greater-than-three-level-map($map) { @return $map; } @else { @return ('&': $map); } } @function _expand-super-selectors($eptv-map) { // Expand all super-selectors animations to apply to their sub-selectors // E.g. so if an animation applies for 'div' - it will also apply for div:nth-child(1) $return-map: (); $i: 0; @each $e, $ptv in $eptv-map { $i: $i + 1; $j: 0; @if $e != '&' and $e != '&:before' { @each $e2, $ptv2 in $eptv-map { $j: $j + 1; @if $i != $j and $e2 != '&' and $e2 != '&:before' and is-superselector($e, $e2) { $return-map: _deep-map-merge($return-map, ($e2: $ptv)); } } } $return-map: _deep-map-merge($return-map, ($e: $ptv)); } @return $return-map; }@function _fill-from-start-and-to-end($eptv-map, $total-ms) { $use-first-value: true; $ret-map: $eptv-map; $start-timing: 0ms; $end-timing: 0ms + $total-ms; @each $elem, $ptv in $ret-map { @each $p, $tv in $ptv { $timing-keys: _sort-list(map-keys($tv)); @each $set in ( (at-timing: $start-timing, from-end: false), (at-timing: $end-timing, from-end: true) ) { $at-timing: map-get($set, at-timing); $from-end: map-get($set, from-end); @if map-get($tv, $at-timing) == null { $use-key: nth($timing-keys, if($from-end, length($timing-keys), 1)); $use-value: map-get($tv, $use-key); $merge-map: ($elem: ($p: ($at-timing: $use-value))); $ret-map: _deep-map-merge($ret-map, $merge-map); } } } } @return $ret-map; } @function _convert-animation-into-transition-list($animation-map) { $animation-map: _normalize-map-hierarchy($animation-map, eptv); $seperated: _kf-properties-extractor($animation-map); $transition-list: (); $m: nth($seperated, 1); @each $e, $ptv in $m { @each $p, $tv in $ptv { $last-t: null; $last-v: null; $i: 0; @each $t, $v in $tv { $duration: 0; $from: initial; @if $last-t != null { $duration: $t - $last-t; } @if $last-v != null { $from: $last-v; } // E.g. don't put initial state value, only worried about *transitions* @if $i > 0 { $transition-list: append($transition-list, ( property: $p, selector: $e, start: $last-t, duration: $duration, from: $from, to: $v )); } $i: $i + 1; $last-t: $t; $last-v: $v; } } } @return _filter-function($transition-list, _is-valid-transition); } @function _is-valid-transition($item) { $duration: map-get($item, duration); $from: map-get($item, from); $to: map-get($item, to); @return $from != $to and $duration > 0; } @function _get-start-timings-from-transition-list($transition-list) { @return _list-uniq(_map-function($transition-list, map-get, start)); } @function _map-transition-list-into-content-property($transition-list) { $message: ""; @each $item in $transition-list { $selector: map-get($item, selector); $start: map-get($item, start); $duration: map-get($item, duration); $end: $start + $duration; $from: map-get($item, from); $to: map-get($item, to); $property: map-get($item, property); @if $from != $to and $duration > 0 { $append-line: "#{$selector} [#{$property}]: #{$start}::#{$from} → #{$end}::#{$to} (#{$duration})"; @if $message == "" { $message: "#{$append-line}" } @else { $message: "#{$message}\a#{$append-line}" } } } @return $message; } @function _generate-debugger-animation($for-selector, $animation-map) { $animation-map: _sort-map-keys-deep($animation-map); $transition-list: _convert-animation-into-transition-list($animation-map); $start-timings: _sort-list(_get-start-timings-from-transition-list($transition-list)); $tv-set: (); $so-far-transition-stack: (); @each $timing in $start-timings { $transitions-with-start-timing: _filter-function( $transition-list, _map-has-key-with-value, start, $timing ); $so-far-transition-stack: join($so-far-transition-stack, $transitions-with-start-timing); $tv-set: map-merge($tv-set, ( $timing: _map-transition-list-into-content-property($so-far-transition-stack) )); } $duration: _calculate-animation-length($animation-map); @return kf-ease(step-end, ( _kf-duration: $duration, $for-selector: ('content': $tv-set) )); } @function _get-globals() { $default-globals: ( isolate: null, infinite-loop: true, default-ease: linear, debug: false, visual-debug: false, enabled: true ); $kf-globals: if(variable-exists('kf-globals') and _is-a-map($kf-globals), $kf-globals, ()); @return map-merge($default-globals, $kf-globals); } @function _is-not-kf-property($v) { @return not ( type-of($v) == string and str-slice($v, 1, 4) == _kf- ); } @function _kf-properties-extractor($map) { @return _partition-map-by-key( $map, _is-not-kf-property ); } @function _get-value-and-atf($v) { @if type-of($v) == list and list-separator($v) == comma { @return (value: nth($v, 1), animation-timing-function: nth($v, 2)); } @else { @return (value: $v); } } @function _get-value-and-atf-with-lookahead($v, $lookahead-for-atf-v) { @return ( value: map-get(_get-value-and-atf($v), value), animation-timing-function: map-get( _get-value-and-atf($lookahead-for-atf-v), animation-timing-function ) ); } @function _convert-absolute-timings-to-percentages($times, $total-length) { $times: if(type-of($times) != 'list', ($times,), $times); @return _change-list-seperator(_map-function($times, _to-percentage, $total-length), comma); } @function _make-animation-sets($animation-sets, $animation-length) { $return-list-of-maps: (); @each $property, $timings-to-value in $animation-sets { $n: 0; @each $times, $value in $timings-to-value { $n: $n + 1; $map-percentage-and-property: ( percent-list: _convert-absolute-timings-to-percentages($times, $animation-length), property: $property, ); $map-value-and-atf: _get-value-and-atf-with-lookahead( $value, nth(map-values($timings-to-value), if($n < length($timings-to-value), $n + 1, 1)) ); $push-map: map-merge($map-percentage-and-property, $map-value-and-atf); $return-list-of-maps: append($return-list-of-maps, $push-map); } } @return $return-list-of-maps; } @function _normalize-map-hierarchy($animation-map, $map-type: eptv) { @if map-get($animation-map, _kf-sleep) != null { @return $animation-map; } $seperated: _kf-properties-extractor($animation-map); $map: nth($seperated, 1); $prop-map: nth($seperated, 2); $map: _expand-implicit-selector($map); $already-set-kf-map-type: map-get($prop-map, _kf-map-type); @if map-get($prop-map, _kf-map-type) == null { $map: _determine-map-type($map); } @else { $map: map-merge($map, (_kf-map-type: $already-set-kf-map-type)); } $map: _reorder-map-hierarchy($map, $map-type); $map: _expand-compound-selectors($map); @return map-merge(map-merge($map, $prop-map), (_kf-map-type: $map-type)); } @function _reorder-map-hierarchy($map, $eptv-ordering) { @if map-get($map, _kf-sleep) { @return $map; } $map-type: map-get($map, _kf-map-type); @if $map-type == $eptv-ordering { @return $map; } $eptv-list: (); @for $i from 1 through str-length($eptv-ordering) { $eptv-list: append($eptv-list, str-slice($eptv-ordering, $i, $i)); } // Re-order $return-map: (); @each $a, $bcd in map-remove($map, _kf-map-type) { @each $b, $cd in $bcd { @each $c, $d in $cd { $vals: ($a, $b, $c, $d); $rord: (); @each $letter in $eptv-list { $index: str-index($map-type, $letter); $rord: append($rord, (nth($vals, $index))); } $return-map: _deep-map-merge( $return-map, (nth($rord, 1): (nth($rord, 2): (nth($rord, 3): nth($rord,4)))) ); } } } @return map-merge($return-map, (_kf-map-type: $eptv-ordering)); } @function _selector-property-keyify($eptv-map) { // Produces map like: (s-p: last-value, s-p2: last-value, s2-p: last-value ) $r: (); @each $e, $ptv in $eptv-map { @each $p, $tv in $ptv { @if type-of($tv) == map { $last-t: nth(map-keys($tv), -1); $last-v: map-get($tv, $last-t); $r: map-merge($r, ("#{$e}-#{$p}": (last-t: $last-t, last-v: $last-v, e: $e, p: $p))); } } } @return $r; }@mixin _at-root-keyframes($l) { @at-root { @keyframes #{$l} { @content; } } } @mixin _debugger-styles() { $p: 10px; font-size: 12px; content: ""; display: block; background: rgba(#e8e8e8, 0.8); border: 1px solid #888888; position: fixed; bottom: $p; right: $p; padding: $p / 2; z-index: 99999999; white-space: pre-wrap; } @function kf($kf-map, $loop: null) { $globals: _get-globals(); // nop @if not map-get($globals, enabled) { @return (eptv-map: ()); } // apply pipeline fn $pipeline-fn: map-get($globals, pipeline-fn); @if $pipeline-fn != null { $kf-map: call($pipeline-fn, $kf-map); } $eptv-map: _normalize_map-hierarchy($kf-map); $total-ms: _calculate-animation-length($eptv-map); $seperated: _kf-properties-extractor($eptv-map); $eptv-map-seperated: nth($seperated, 1); $eptv-map-seperated: _expand-super-selectors($eptv-map-seperated); $eptv-map-seperated: _fill-from-start-and-to-end($eptv-map-seperated, $total-ms); @return ( animation-handle: kf-#{unique-id()}, total-ms: $total-ms, eptv-map: $eptv-map-seperated, infinite-loop: if($loop != null, $loop, map-get($globals, infinite-loop)), generate-debugger-animation: map-get($kf-map, _kf-debug) ); } @mixin kf($kf-map, $loop: null) { $kf-props: kf($kf-map, $loop); $animation-handle: map-get($kf-props, animation-handle); $total-ms: map-get($kf-props, total-ms); $eptv-map: map-get($kf-props, eptv-map); $infinite-loop: map-get($kf-props, infinite-loop); $debugger-enabled: map-get($kf-props, generate-debugger-animation); @if $debugger-enabled != null { #{$debugger-enabled} { @include _debugger-styles(); } } $i: 0; @each $selector, $animation-set in $eptv-map { $i: $i + 1; $selector-animation-name-handle: #{$animation-handle}-#{$i}; @include _at-root-keyframes($selector-animation-name-handle) { $kfs: _make-animation-sets($animation-set, $total-ms); @each $set in $kfs { #{map-get($set, percent-list)} { #{map-get($set, property)}: map-get($set, value); @if map-get($set, animation-timing-function) != null { animation-timing-function: map-get($set, animation-timing-function); } } } } #{$selector} { animation-fill-mode: forwards; animation-iteration-count: if($infinite-loop, infinite, 1); animation-name: $selector-animation-name-handle; animation-duration: $total-ms; } } } @function kf-chain($chained-maps...) { $chained-maps: _map-function($chained-maps, _normalize-map-hierarchy, tepv); $new-duration: _list-sum(_map-function($chained-maps, _calculate-animation-length)); $return-map: (); $last-timing: 0ms; $i: 0; @each $m in $chained-maps { $i: $i + 1; @if map-has-key($m, _kf-sleep) { $last-timing: $last-timing + map-get($m, _kf-sleep); @if length($chained-maps) == $i { $return-map: map-merge($return-map, (_kf-duration: $last-timing)); } } @else { $new-map: (); $seperated: _kf-properties-extractor($m); $anim-map: nth($seperated, 1); @each $t, $epv in $anim-map { $timing: $last-timing + $t; $new-map: map-merge($new-map, ($timing: $epv)) } $return-map: map-merge($return-map, $new-map); $last-timing: $last-timing + _calculate-animation-length(map-merge($m, (_kf-map-type: tepv))) + 1ms; } } @return map-merge($return-map, (_kf-map-type: tepv, _kf-duration: $new-duration)); } @mixin kf-chain($m...) { @include kf(kf-chain($m...)); }@function kf-debug($args...) { $animation-map: nth($args, 1); $called-from-mixin: nth($args, 2); @if $called-from-mixin != called-from-mixin { @warn "Unsupported feature warning:"; @warn "_kf-debug is currently only support when used as a top-level mixin"; @warn "_kf-debug *may* work when used nested as a function but this is not officially support"; } $debug-selector: "&:before"; $animation-map: kf-parallel( kf-ease(step-end, _generate-debugger-animation($debug-selector, $animation-map)), $animation-map ); @return map-merge($animation-map, (_kf-debug: $debug-selector )); } @mixin kf-debug($animation-map) { @include kf( kf-debug($animation-map, called-from-mixin) ); } @function kf-ease($easing, $animation-map) { $animation-map: _normalize-map-hierarchy($animation-map); $seperated: _kf-properties-extractor($animation-map); $anim-map: nth($seperated, 1); $prop-map: nth($seperated, 2); $return-map: (); @each $s, $ptv in $anim-map { $ptv-m: (); @each $p, $tv in $ptv { $tv-m: (); @each $t, $v in $tv { $new-v: $v; @if type-of($v) != list { $new-v: ($v, $easing); } $tv-m: map-merge($tv-m, ($t: $new-v)); } $ptv-m: map-merge($ptv-m, ($p: $tv-m)); } $return-map: map-merge($return-map, ($s: $ptv-m)); } @return map-merge($return-map, $prop-map); } @mixin kf-ease($m...) { @include kf(kf-ease($m...)); } @function kf-lag($duration, $animation-map) { @return kf-chain( $animation-map, kf-sleep($duration) ); } @mixin kf-lag($m...) { @include kf(kf-lag($m...)); } @function kf-lead($duration, $animation-map) { @return kf-chain( kf-sleep($duration), $animation-map, ); } @mixin kf-lead($m...) { @include kf(kf-lead($m...)); } @function kf-loop($args...) { @if length($args) == 1 { $animation-map: nth($args, 1); $animation-map: _normalize-map-hierarchy($animation-map); @return map-merge($animation-map, (_kf-infinite-loop: true)); } @else if length($args) == 2 { $times: nth($args, 1); $animation-map: nth($args, 2); $animation-map: _normalize-map-hierarchy($animation-map); $chain: (); @for $i from 1 through $times { $chain: append($chain, $animation-map); } @return kf-chain($chain...); } @else { @debug "Cannot use kf-loop must be used with 1-2 parameters"; @return (); } } @mixin kf-loop($m...) { @include kf(kf-loop($m...)); } @function kf-mirror($map) { $map: _normalize-map-hierarchy($map); @return kf-chain($map, kf-reverse($map)); } @mixin kf-mirror($m...) { @include kf(kf-mirror($m...)); } @function kf-parallel($m...) { $m: _map-function($m, _normalize-map-hierarchy); $seperated-list: _map-function($m, _kf-properties-extractor); $r: (_kf-map-type: eptv); @each $n in $seperated-list { $r: _deep-map-merge($r, nth($n, 1)); $kf-duration: map-get(nth($n, 2), _kf-duration); @if $kf-duration != null { $old-duration: map-get($r, _kf-duration); $new-duration: if($old-duration != null, max($old-duration, $kf-duration), $kf-duration); $r: map-merge($r, (_kf-duration: $new-duration)); } } @return $r; } @mixin kf-parallel($m...) { @include kf(kf-parallel($m...)); } @function kf-reverse($animation-map) { $animation-map: _normalize-map-hierarchy($animation-map, tepv); $length: _calculate-animation-length($animation-map); $seperated: _kf-properties-extractor($animation-map); $anim-map: nth($seperated, 1); $prop-map: nth($seperated, 2); $return-map: (); @each $m, $epv in $anim-map { $m: abs($m + -$length); $return-map: map-merge($return-map, ($m: $epv)); } @return map-merge($return-map, $prop-map); } @mixin kf-reverse($m...) { @include kf(kf-reverse($m...)); } @function kf-sleep($duration) { @return (_kf-sleep: $duration); } @mixin kf-sleep($duration) { @include kf-sleep($duration); } @function kf-sleep($duration) { @return (_kf-sleep: $duration); } @mixin kf-sleep($duration) { @include kf-sleep($duration); } @function kf-stagger($stagger-duration, $list-of-animations...) { $i: 0; $return-animation: null; @each $animation in $list-of-animations { @if $i == 0 { $return-animation: $animation; } @else { $old-duration: _calculate_animation_length($return-animation); $return-animation: kf-parallel( $return-animation, kf-lead($old-duration + $stagger-duration, $animation) ); } $i: $i + 1; } @return $return-animation; } @mixin kf-stagger($m...) { @include kf(kf-stagger($m...)); } @function kf-stretch($multiplier-or-duration, $animation-map) { $animation-map: _normalize-map-hierarchy($animation-map, tepv); $multiplier: 1; $unit: unit($multiplier-or-duration); $current-duration: _calculate_animation_length($animation-map); @if $unit == "" { $multiplier: $multiplier-or-duration; } @else if $unit == "ms" or $unit == "s" { $multiplier: $multiplier-or-duration / $current-duration; } @else { @warn Cannot use kf-stretch on unit '#{$unit}'; } $new-total-duration: $current-duration * $multiplier; $seperated: _kf-properties-extractor($animation-map); $anim-map: nth($seperated, 1); $prop-map: nth($seperated, 2); $return-map: (); @each $t, $epv in $anim-map { $return-map: map-merge($return-map, (($t * $multiplier): $epv)); } $prop-map: map-merge($prop-map, (_kf-duration: $new-total-duration)); @return map-merge( $return-map, $prop-map ); } @mixin kf-stretch($m...) { @include kf(kf-stretch($m...)); } @function kf-timeline($kf-timeline-map) { @warn "Unsupported feature warning:"; @warn "kf-timeline is not fully completed - please do not use!"; $kf-parallel-list: (); @each $timing, $place-map in $kf-timeline-map { $sleep-amount: $timing; $kf-chain-map: kf-chain(kf-sleep($sleep-amount), $place-map); $kf-parallel-list: append($kf-parallel-list, $kf-chain-map); } @return kf-parallel($kf-parallel-list...); } @mixin kf-timeline($kf-timeline-map) { @include kf(kf-timeline($kf-timeline-map)); }