// // Copyright 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 // // go/keep-sorted start @use 'sass:list'; // go/keep-sorted end // go/keep-sorted start @use '../../tokens'; // go/keep-sorted end @mixin theme($tokens) { $supported-tokens: tokens.$md-comp-linear-progress-supported-tokens; @each $token, $value in $tokens { @if list.index($supported-tokens, $token) == null { @error 'Token `#{$token}` is not a supported token.'; } @if $value { --md-linear-progress-#{$token}: #{$value}; } } } // note, transition settings match MDC // see https://github.com/material-components/material-components-web/blob/main/packages/mdc-linear-progress/_linear-progress.scss#L79 $_determinate-duration: 250ms; $_determinate-easing: cubic-bezier(0.4, 0, 0.6, 1); // see https://github.com/material-components/material-components-web/blob/main/packages/mdc-linear-progress/_linear-progress.scss#L218 $_indeterminate-duration: 2s; @mixin styles() { $tokens: tokens.md-comp-linear-progress-values(); :host { @each $token, $value in $tokens { --_#{$token}: #{$value}; } border-radius: var(--_track-shape); display: flex; position: relative; // note, this matches the `meter` element and is just done so // there's a default width. min-width: 80px; height: var(--_track-height); content-visibility: auto; contain: strict; } .progress, .dots, .inactive-track, .bar, .bar-inner { position: absolute; } .progress { // Animations need to be in LTR. We support RTL by flipping the indicator // with scale(-1). direction: ltr; inset: 0; border-radius: inherit; overflow: hidden; display: flex; align-items: center; } .bar { animation: none; // position is offset for indeterminate animation, so we lock the inline size here. width: 100%; height: var(--_active-indicator-height); transform-origin: left center; transition: transform $_determinate-duration $_determinate-easing; } .secondary-bar { display: none; } .bar-inner { inset: 0; animation: none; background: var(--_active-indicator-color); } .inactive-track { background: var(--_track-color); inset: 0; transition: transform $_determinate-duration $_determinate-easing; transform-origin: left center; } .dots { inset: 0; animation: linear infinite $_determinate-duration; animation-name: buffering; // The color of the buffer dots, which are masked out of this background // color. background-color: var(--_track-color); background-repeat: repeat-x; // SVG is optimized for data URI (https://codepen.io/tigt/post/optimizing-svgs-in-data-uris) // This svg creates a black circle on a transparent background which is used // to mask out the animated buffering dots. This technique allows for dot // color customization via the background-color property, and mask-image // displays when forced-colors are active. $svg: "data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 5 2' preserveAspectRatio='xMinYMin slice'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/svg%3E"; // required for full support with Chrome, Edge, and Opera. -webkit-mask-image: url($svg); mask-image: url($svg); z-index: -1; // Place behind tracks for Safari } // dots are hidden when indeterminate or when there is no visible buffer to // prevent infinite invisible animation. .dots[hidden] { display: none; } // indeterminate .indeterminate .bar { transition: none; } // note, the numbers here come directly from the mdc implementation. // see https://github.com/material-components/material-components-web/blob/main/packages/mdc-linear-progress/_linear-progress.scss#L208. .indeterminate .primary-bar { inset-inline-start: -145.167%; } .indeterminate .secondary-bar { inset-inline-start: -54.8889%; // this is display none by default. display: block; } .indeterminate .primary-bar { animation: linear infinite $_indeterminate-duration; animation-name: primary-indeterminate-translate; } .indeterminate .primary-bar > .bar-inner { animation: linear infinite $_indeterminate-duration primary-indeterminate-scale; } .indeterminate.four-color .primary-bar > .bar-inner { animation-name: primary-indeterminate-scale, four-color; animation-duration: $_indeterminate-duration, calc($_indeterminate-duration * 2); } .indeterminate .secondary-bar { animation: linear infinite $_indeterminate-duration; animation-name: secondary-indeterminate-translate; } .indeterminate .secondary-bar > .bar-inner { animation: linear infinite $_indeterminate-duration secondary-indeterminate-scale; } .indeterminate.four-color .secondary-bar > .bar-inner { animation-name: secondary-indeterminate-scale, four-color; animation-duration: $_indeterminate-duration, calc($_indeterminate-duration * 2); } :host(:dir(rtl)) { transform: scale(-1); } @keyframes primary-indeterminate-scale { 0% { transform: scaleX(0.08); } 36.65% { animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1); transform: scaleX(0.08); } 69.15% { animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1); transform: scaleX(0.661479); } 100% { transform: scaleX(0.08); } } @keyframes secondary-indeterminate-scale { 0% { animation-timing-function: cubic-bezier( 0.205028, 0.057051, 0.57661, 0.453971 ); transform: scaleX(0.08); } 19.15% { animation-timing-function: cubic-bezier( 0.152313, 0.196432, 0.648374, 1.00432 ); transform: scaleX(0.457104); } 44.15% { animation-timing-function: cubic-bezier( 0.257759, -0.003163, 0.211762, 1.38179 ); transform: scaleX(0.72796); } 100% { transform: scaleX(0.08); } } @keyframes buffering { 0% { $_dot-size: calc(var(--_track-height) / 2); // the amount to animate is aligned with the default track background $_dot-background-width: calc($_dot-size * 5); transform: translateX(#{$_dot-background-width}); } } // note, the numbers here come directly from the mdc implementation. // see https://github.com/material-components/material-components-web/blob/main/packages/mdc-linear-progress/_linear-progress.scss#L208. // keyframes @keyframes primary-indeterminate-translate { 0% { transform: translateX(0px); } 20% { animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819); transform: translateX(0px); } 59.15% { animation-timing-function: cubic-bezier( 0.302435, 0.381352, 0.55, 0.956352 ); transform: translateX(83.6714%); } 100% { transform: translateX(200.611%); } } @keyframes secondary-indeterminate-translate { 0% { animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685); transform: translateX(0px); } 25% { animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712); transform: translateX(37.6519%); } 48.35% { animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026); transform: translateX(84.3862%); } 100% { transform: translateX(160.278%); } } @keyframes four-color { 0% { background: var(--_four-color-active-indicator-one-color); } 15% { background: var(--_four-color-active-indicator-one-color); } 25% { background: var(--_four-color-active-indicator-two-color); } 40% { background: var(--_four-color-active-indicator-two-color); } 50% { background: var(--_four-color-active-indicator-three-color); } 65% { background: var(--_four-color-active-indicator-three-color); } 75% { background: var(--_four-color-active-indicator-four-color); } 90% { background: var(--_four-color-active-indicator-four-color); } 100% { background: var(--_four-color-active-indicator-one-color); } } @media (forced-colors: active) { :host { outline: 1px solid CanvasText; } .bar-inner, .dots { background-color: CanvasText; } // TODO(b/296262544): fix dots not being CanvasText. This need a refactor, // since background gradiants are not displayed in HCM. } }