// // Copyright 2016 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // @import "@material/animation/functions"; @import "@material/animation/variables"; @import "@material/base/mixins"; @import "@material/feature-targeting/functions"; @import "@material/feature-targeting/mixins"; @import "@material/theme/mixins"; @import "./functions"; @import "./keyframes"; @import "./variables"; @mixin mdc-ripple-core-styles($query: mdc-feature-all()) { // postcss-bem-linter: define ripple-surface $feat-structure: mdc-feature-create-target($query, structure); .mdc-ripple-surface { @include mdc-ripple-surface($query: $query); @include mdc-states($query: $query); @include mdc-ripple-radius-bounded($query: $query); @include mdc-feature-targets($feat-structure) { position: relative; outline: none; overflow: hidden; } &[data-mdc-ripple-is-unbounded] { @include mdc-ripple-radius-unbounded($query: $query); @include mdc-feature-targets($feat-structure) { overflow: visible; } } &--primary { @include mdc-states(primary, $query: $query); } &--accent { @include mdc-states(secondary, $query: $query); } } // postcss-bem-linter: end } @mixin mdc-ripple-common($query: mdc-feature-all()) { $feat-animation: mdc-feature-create-target($query, animation); $feat-structure: mdc-feature-create-target($query, structure); // Ensure that styles needed by any component using MDC Ripple are emitted, but only once. // (Every component using MDC Ripple imports these mixins, but doesn't necessarily import // mdc-ripple.scss.) @include mdc-feature-targets($feat-animation) { @include mdc-base-emit-once("mdc-ripple/common/animation") { @include mdc-ripple-keyframes_; } } @include mdc-feature-targets($feat-structure) { @include mdc-base-emit-once("mdc-ripple/common/structure") { // Styles used to detect buggy behavior of CSS custom properties in Edge. // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11495448/ // This is included in _mixins.scss rather than mdc-ripple.scss so that it will be // present for other components which rely on ripple as well as mdc-ripple itself. .mdc-ripple-surface--test-edge-var-bug { --mdc-ripple-surface-test-edge-var: 1px solid #000; visibility: hidden; &::before { border: var(--mdc-ripple-surface-test-edge-var); } } } } } @mixin mdc-ripple-surface($query: mdc-feature-all(), $ripple-target: "&") { $feat-animation: mdc-feature-create-target($query, animation); $feat-structure: mdc-feature-create-target($query, structure); @include mdc-feature-targets($feat-structure) { --mdc-ripple-fg-size: 0; --mdc-ripple-left: 0; --mdc-ripple-top: 0; --mdc-ripple-fg-scale: 1; --mdc-ripple-fg-translate-end: 0; --mdc-ripple-fg-translate-start: 0; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); // !!DO NOT REMOVE!! mdc-ripple-will-change-replacer } #{$ripple-target}::before, #{$ripple-target}::after { @include mdc-feature-targets($feat-structure) { position: absolute; border-radius: 50%; opacity: 0; pointer-events: none; content: ""; } } #{$ripple-target}::before { @include mdc-feature-targets($feat-animation) { // Also transition background-color to avoid unnatural color flashes when toggling activated/selected state transition: opacity $mdc-states-wash-duration linear, background-color $mdc-states-wash-duration linear; } @include mdc-feature-targets($feat-structure) { z-index: 1; // Ensure that the ripple wash for hover/focus states is displayed on top of positioned child elements } } // Common styles for upgraded surfaces (some of these depend on custom properties set via JS or other mixins) &.mdc-ripple-upgraded { #{$ripple-target}::before { @include mdc-feature-targets($feat-structure) { transform: scale(var(--mdc-ripple-fg-scale, 1)); } } #{$ripple-target}::after { @include mdc-feature-targets($feat-structure) { top: 0; /* @noflip */ left: 0; transform: scale(0); transform-origin: center center; } } } &.mdc-ripple-upgraded--unbounded { #{$ripple-target}::after { @include mdc-feature-targets($feat-structure) { top: var(--mdc-ripple-top, 0); /* @noflip */ left: var(--mdc-ripple-left, 0); } } } &.mdc-ripple-upgraded--foreground-activation { #{$ripple-target}::after { @include mdc-feature-targets($feat-animation) { animation: mdc-ripple-fg-radius-in $mdc-ripple-translate-duration forwards, mdc-ripple-fg-opacity-in $mdc-ripple-fade-in-duration forwards; } } } &.mdc-ripple-upgraded--foreground-deactivation { #{$ripple-target}::after { @include mdc-feature-targets($feat-animation) { animation: mdc-ripple-fg-opacity-out $mdc-ripple-fade-out-duration; } @include mdc-feature-targets($feat-structure) { // Retain transform from mdc-ripple-fg-radius-in activation transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1)); } } } } @mixin mdc-states-base-color( $color, $query: mdc-feature-all(), $ripple-target: "&") { $feat-color: mdc-feature-create-target($query, color); #{$ripple-target}::before, #{$ripple-target}::after { @include mdc-feature-targets($feat-color) { @if alpha(mdc-theme-prop-value($color)) > 0 { @include mdc-theme-prop(background-color, $color, $edgeOptOut: true); } @else { // If a color with 0 alpha is specified, don't render the ripple pseudo-elements at all. // This avoids unnecessary transitions and overflow. content: none; } } } } /// /// Customizes ripple opacities in `hover`, `focus`, or `press` states /// @param {map} $opacity-map - map specifying custom opacity of zero or more states /// @param {bool} $has-nested-focusable-element - whether the component contains a focusable element in the root /// @mixin mdc-states-opacities($opacity-map: (), $has-nested-focusable-element: false, $query: mdc-feature-all()) { // Ensure sufficient specificity to override base state opacities @if map-has-key($opacity-map, hover) { @include mdc-states-hover-opacity(map-get($opacity-map, hover), $query: $query); } @if map-has-key($opacity-map, focus) { @include mdc-states-focus-opacity(map-get($opacity-map, focus), $has-nested-focusable-element, $query: $query); } @if map-has-key($opacity-map, press) { @include mdc-states-press-opacity(map-get($opacity-map, press), $query: $query); } } @mixin mdc-states-hover-opacity( $opacity, $query: mdc-feature-all(), $ripple-target: "&") { $feat-color: mdc-feature-create-target($query, color); // Background wash styles, for both CSS-only and upgraded stateful surfaces &:hover { #{$ripple-target}::before { // Opacity falls under color because the chosen opacity is color-dependent in typical usage @include mdc-feature-targets($feat-color) { opacity: $opacity; } } } } @mixin mdc-states-focus-opacity( $opacity, $has-nested-focusable-element: false, $query: mdc-feature-all(), $ripple-target: "&") { // Focus overrides hover by reusing the ::before pseudo-element. // :focus-within generally works on non-MS browsers and matches when a *child* of the element has focus. // It is useful for cases where a component has a focusable element within the root node, e.g. text field, // but undesirable in general in case of nested stateful components. // We use a modifier class for JS-enabled surfaces to support all use cases in all browsers. @if $has-nested-focusable-element { // JS-enabled selectors. &.mdc-ripple-upgraded--background-focused, &.mdc-ripple-upgraded:focus-within, // CSS-only selectors. &:not(.mdc-ripple-upgraded):focus, &:not(.mdc-ripple-upgraded):focus-within { #{$ripple-target}::before { @include mdc-states-focus-opacity-properties_( $opacity: $opacity, $query: $query); } } } @else { // JS-enabled selectors. &.mdc-ripple-upgraded--background-focused, // CSS-only selectors. &:not(.mdc-ripple-upgraded):focus { #{$ripple-target}::before { @include mdc-states-focus-opacity-properties_( $opacity: $opacity, $query: $query); } } } } @mixin mdc-states-focus-opacity-properties_($opacity, $query) { $feat-animation: mdc-feature-create-target($query, animation); // Opacity falls under color because the chosen opacity is color-dependent in typical usage $feat-color: mdc-feature-create-target($query, color); // Note that this duration is only effective on focus, not blur @include mdc-feature-targets($feat-animation) { transition-duration: 75ms; } @include mdc-feature-targets($feat-color) { opacity: $opacity; } } @mixin mdc-states-press-opacity($opacity, $query: mdc-feature-all(), $ripple-target: "&") { $feat-animation: mdc-feature-create-target($query, animation); $feat-color: mdc-feature-create-target($query, color); // Styles for non-upgraded (CSS-only) stateful surfaces &:not(.mdc-ripple-upgraded) { // Apply press additively by using the ::after pseudo-element #{$ripple-target}::after { @include mdc-feature-targets($feat-animation) { transition: opacity $mdc-ripple-fade-out-duration linear; } } &:active { #{$ripple-target}::after { @include mdc-feature-targets($feat-animation) { transition-duration: $mdc-ripple-fade-in-duration; } // Opacity falls under color because the chosen opacity is color-dependent in typical usage @include mdc-feature-targets($feat-color) { opacity: $opacity; } } } } &.mdc-ripple-upgraded { @include mdc-feature-targets($feat-color) { --mdc-ripple-fg-opacity: #{$opacity}; } } } // Simple mixin for base states which automatically selects opacity values based on whether the ink color is // light or dark. @mixin mdc-states( $color: mdc-theme-prop-value(on-surface), $has-nested-focusable-element: false, $query: mdc-feature-all(), $ripple-target: "&", ) { @include mdc-states-interactions_( $color: $color, $has-nested-focusable-element: $has-nested-focusable-element, $query: $query, $ripple-target: $ripple-target); } // Simple mixin for activated states which automatically selects opacity values based on whether the ink color is // light or dark. @mixin mdc-states-activated( $color, $has-nested-focusable-element: false, $query: mdc-feature-all(), $ripple-target: "&") { $feat-color: mdc-feature-create-target($query, color); $activated-opacity: mdc-states-opacity($color, activated); &--activated { // Stylelint seems to think that '&' qualifies as a type selector here? // stylelint-disable-next-line selector-max-type #{$ripple-target}::before { // Opacity falls under color because the chosen opacity is color-dependent. @include mdc-feature-targets($feat-color) { opacity: $activated-opacity; } } @include mdc-states-interactions_( $color: $color, $has-nested-focusable-element: $has-nested-focusable-element, $opacity-modifier: $activated-opacity, $query: $query, $ripple-target: $ripple-target); } } // Simple mixin for selected states which automatically selects opacity values based on whether the ink color is // light or dark. @mixin mdc-states-selected( $color, $has-nested-focusable-element: false, $query: mdc-feature-all(), $ripple-target: "&") { $feat-color: mdc-feature-create-target($query, color); $selected-opacity: mdc-states-opacity($color, selected); &--selected { // stylelint-disable-next-line selector-max-type #{$ripple-target}::before { // Opacity falls under color because the chosen opacity is color-dependent. @include mdc-feature-targets($feat-color) { opacity: $selected-opacity; } } @include mdc-states-interactions_( $color: $color, $has-nested-focusable-element: $has-nested-focusable-element, $opacity-modifier: $selected-opacity, $query: $query, $ripple-target: $ripple-target); } } @mixin mdc-ripple-radius-bounded( $radius: 100%, $query: mdc-feature-all(), $ripple-target: "&") { $feat-struture: mdc-feature-create-target($query, structure); #{$ripple-target}::before, #{$ripple-target}::after { @include mdc-feature-targets($feat-struture) { top: calc(50% - #{$radius}); /* @noflip */ left: calc(50% - #{$radius}); width: $radius * 2; height: $radius * 2; } } &.mdc-ripple-upgraded { #{$ripple-target}::after { @include mdc-feature-targets($feat-struture) { width: var(--mdc-ripple-fg-size, $radius); height: var(--mdc-ripple-fg-size, $radius); } } } } @mixin mdc-ripple-radius-unbounded( $radius: 100%, $query: mdc-feature-all(), $ripple-target: "&") { $feat-struture: mdc-feature-create-target($query, structure); #{$ripple-target}::before, #{$ripple-target}::after { @include mdc-feature-targets($feat-struture) { top: calc(50% - #{$radius / 2}); /* @noflip */ left: calc(50% - #{$radius / 2}); width: $radius; height: $radius; } } &.mdc-ripple-upgraded { #{$ripple-target}::before, #{$ripple-target}::after { @include mdc-feature-targets($feat-struture) { top: var(--mdc-ripple-top, calc(50% - #{$radius / 2})); /* @noflip */ left: var(--mdc-ripple-left, calc(50% - #{$radius / 2})); width: var(--mdc-ripple-fg-size, $radius); height: var(--mdc-ripple-fg-size, $radius); } } #{$ripple-target}::after { @include mdc-feature-targets($feat-struture) { width: var(--mdc-ripple-fg-size, $radius); height: var(--mdc-ripple-fg-size, $radius); } } } } @mixin mdc-states-interactions_( $color, $has-nested-focusable-element, $opacity-modifier: 0, $query: mdc-feature-all(), $ripple-target: "&", ) { @include mdc-ripple-target-selector($ripple-target) { @include mdc-states-base-color($color, $query); } @include mdc-states-hover-opacity( $opacity: mdc-states-opacity($color, hover) + $opacity-modifier, $query: $query, $ripple-target: $ripple-target); @include mdc-states-focus-opacity( $opacity: mdc-states-opacity($color, focus) + $opacity-modifier, $has-nested-focusable-element: $has-nested-focusable-element, $query: $query, $ripple-target: $ripple-target, ); @include mdc-states-press-opacity( $opacity: mdc-states-opacity($color, press) + $opacity-modifier, $query: $query, $ripple-target: $ripple-target); } // Wraps content in the `ripple-target` selector if it exists. @mixin mdc-ripple-target-selector($ripple-target: "&") { @if $ripple-target == "&" { @content; } @else { #{$ripple-target} { @content; } } } // Common styles for a ripple target element. // Used for components which have an inner ripple target element. @mixin mdc-ripple-target-common($query: mdc-feature-all()) { $feat-structure: mdc-feature-create-target($query, structure); @include mdc-feature-targets($feat-structure) { position: absolute; top: 0; left: 0; width: 100%; height: 100%; // Necessary for clicks on other inner elements (e.g. close icon in chip) // to go through. pointer-events: none; } }