Outlook_Addin_LLM/node_modules/@material/theme/_shadow-dom.scss

492 lines
16 KiB
SCSS

//
// Copyright 2020 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.
//
@use 'sass:selector';
@use 'sass:string';
@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
/// Global variable used to conditionally emit CSS selector fallback
/// declarations in addition to CSS custom property overrides for IE11 support.
/// Use `enable-css-selector-fallback-declarations()` mixin to configure this
/// flag.
///
/// @example
///
/// @include shadow-dom.enable-css-selector-fallback-declarations();
/// @include foo-bar-theme.theme($theme);
///
/// CSS output =>
///
/// --foo-bar: red;
///
/// // Fallback declarations for IE11 support
/// .mdc-foo-bar__baz {
/// color: red;
/// }
$css-selector-fallback-declarations: false;
/// Enables CSS selector fallback declarations for IE11 support by setting
/// global variable `$css-selector-fallback-declarations` to true. Call this
/// mixin before theme mixin call.
/// @param {Boolean} $enable Set to `true` to emit CSS selector fallback
/// declarations.
/// @example
/// @include shadow-dom.enable-css-selector-fallback-declarations()
/// @include foo-bar-theme.theme($theme);
@mixin enable-css-selector-fallback-declarations($enable) {
$css-selector-fallback-declarations: $enable !global;
}
$_host: ':host';
$_host-parens: ':host(';
$_end-parens: ')';
/// @deprecated - Use selector-ext.append-strict() instead:
///
/// @example - scss
/// :host([outlined]), :host, :host button {
/// @include selector-ext.append-strict(&, ':hover') {
/// --my-custom-prop: blue;
/// }
/// }
///
/// @example - css
/// :host([outlined]:hover), :host(:hover), :host button:hover {
/// --my-custom-prop: blue;
/// }
///
/// @example - scss
/// :host([outlined]), :host, :host button {
/// @at-root {
/// #{selector-ext.append-strict(&, ':hover')},
/// & {
/// --my-custom-prop: blue;
/// }
/// }
/// }
///
/// @example - css
/// :host([outlined]:hover), :host(:hover), :host button:hover,
/// :host([outlined]), :host, :host button {
/// --my-custom-prop: blue;
/// }
///
/// Given one or more selectors, this mixin will fix any invalid `:host` parent
/// nesting by adding parentheses or inserting the nested selector into the
/// parent `:host()` selector's parentheses. The content block provided to
/// this mixin
/// will be projected under the new selectors.
///
/// @example
/// :host([outlined]), :host, :host button {
/// @include host-aware(selector.append(&, ':hover'), &)) {
/// --my-custom-prop: blue;
/// }
/// }
///
/// will output (but with selectors on a single line):
/// :host([outlined]:hover), // Appended :hover argument
/// :host(:hover),
/// :host button:hover,
/// :host([outlined]), // Ampersand argument
/// :host,
/// :host button, {
/// --my-custom-prop: blue;
/// };
///
/// @param {List} $selector-args - One or more selectors to be fixed for invalid
/// :host syntax.
@mixin host-aware($selector-args...) {
@each $selector in $selector-args {
@if not _is-sass-selector($selector) {
@error 'mdc-theme: host-aware() expected a sass:selector value type but received #{$selector}';
}
}
@if not _share-common-parent($selector-args...) {
@error 'mdc-theme: host-aware() requires all selectors to use the parent selector (&)';
}
$selectors: _flatten-selectors($selector-args...);
$processed-selectors: ();
@each $selector in $selectors {
$first-selector: list.nth($selector, 1);
@if _host-selector-needs-to-be-fixed($first-selector) {
$selector: list.set-nth(
$selector,
1,
_fix-host-selector($first-selector)
);
$processed-selectors: list.append(
$processed-selectors,
$selector,
$separator: comma
);
} @else {
// Either not in :host, or there are more selectors following the :host
// and nothing needs to be modified. The content can be placed within the
// original selector
$processed-selectors: list.append(
$processed-selectors,
$selector,
$separator: comma
);
}
}
@if list.length($processed-selectors) > 0 {
@at-root {
#{$processed-selectors} {
@content;
}
}
}
}
/// Determines whether a selector needs to be processed.
/// Selectors that need to be processed would include anything of the format
/// `^:host(\(.*\))?.+` e.g. `:host([outlined]):hover` or `:host:hover` but not
/// `:host` or `:host([outlined])`
///
/// @param {String} $selector - Selector string to be processed
/// @return {Boolean} Whether or not the given selector string needs to be fixed
/// for an invalid :host selector
@function _host-selector-needs-to-be-fixed($selector) {
$host-index: string.index($selector, $_host);
$begins-with-host: $host-index == 1;
@if not $begins-with-host {
@return false;
}
$_host-parens-index: _get-last-end-parens-index($selector);
$has-parens: $_host-parens-index != null;
@if $has-parens {
// e.g. :host(.inside).after -> needs to be fixed
// :host(.inside) -> does not need to be fixed
$end-parens-index: string.index($selector, $_end-parens);
$content-after-parens: string.slice($selector, $end-parens-index + 1);
$has-content-after-parens: string.length($selector) > $end-parens-index;
@return $has-content-after-parens;
} @else {
// e.g. :host.after -> needs to be fixed
// :host -> does not need to be fixed
$has-content-after-host: $selector != $_host;
@return $has-content-after-host;
}
}
/// Flattens a list of selectors
///
/// @param {List} $selector-args - A list of selectors to flatten
/// @return {List} Flattened selectors
@function _flatten-selectors($selector-args...) {
$selectors: ();
@each $selector-list in $selector-args {
$selectors: list.join($selectors, $selector-list);
}
@return $selectors;
}
/// Fixes an invalid `:host` selector of the format `^:host(\(.*\))?.+` to
/// `:host(.+)`
/// @example
/// @debug _fix-host-selector(':host:hover') // :host(:hover)
/// @debug _fix-host-selector(':host([outlined]):hover) // :host([outlined]:hover)
///
/// @param {String} $selector - Selector string to be fixed that follows the
/// following format: `^:host(\(.*\))?.+`
/// @return {String} Fixed host selector.
@function _fix-host-selector($selector) {
$_host-parens-index: string.index($selector, $_host-parens);
$has-parens: $_host-parens-index != null;
$new-host-inside: '';
@if $has-parens {
// e.g. :host(.inside).after -> :host(.inside.after)
$end-parens-index: _get-last-end-parens-index($selector);
$inside-host-parens: string.slice(
$selector,
string.length($_host-parens) + 1,
$end-parens-index - 1
);
$after-host-parens: string.slice($selector, $end-parens-index + 1);
$new-host-inside: $inside-host-parens + $after-host-parens;
} @else {
// e.g. :host.after -> :host(.after)
$new-host-inside: string.slice($selector, string.length($_host) + 1);
}
@return ':host(#{$new-host-inside})';
}
/// Returns the index of the final occurrence of the end-parenthesis in the
/// given string or null if there is none.
///
/// @param {String} $string - The string to be searched
/// @return {null|Number}
@function _get-last-end-parens-index($string) {
$index: string.length($string);
@while $index > 0 {
$char: string.slice($string, $index, $index);
@if $char == $_end-parens {
@return $index;
}
$index: $index - 1;
}
@return null;
}
/// Returns true if the provided List of Sass selectors share a common parent
/// selector. This function ensures that the parent selector (`&`) is used with
/// `host-aware()`.
///
/// @example
/// _share-common-parent(
/// ('.foo:hover'), ('.foo' '.bar'), ('.baz' '.foo')
/// ); // true
///
/// _share-common-parent(
/// ('.foo:hover'), ('.foo' '.bar'), ('.baz' '.bar')
/// ); // false
///
/// The purpose of this function is to make sure that a group of selectors do
/// not violate Sass nesting rules. Due to the dynamic nature of `host-aware()`,
/// it's possible to provide invalid selector combinations.
///
/// @example
/// // Valid native nesting
/// :host {
/// &:hover,
/// .foo,
/// .bar & {
/// color: blue;
/// }
/// }
/// // Valid host-aware() nesting
/// :host {
/// @include host-aware(
/// selector.append(&, ':hover'),
/// selector.nest(&, '.foo'),
/// selector.nest('.bar', &),
/// ) {
/// color: blue;
/// }
/// }
/// // Output
/// :host(:hover),
/// :host .foo,
/// .bar :host {
/// color: blue;
/// }
///
/// // Invalid use of host-aware()
/// :host {
/// @include host-aware(
/// selector.append(&, ':hover'),
/// selector.parse('.foo') // Does not share a common parent via `&`
/// ) {
/// color: blue;
/// }
/// }
/// // Invalid output: no way to write this natively without using @at-root
/// :host(:hover),
/// .foo {
/// color: blue;
/// }
///
/// @param {Arglist} $selector-lists - An argument list of Sass selectors.
/// @return true if the selectors share a common parent selector, or false
/// if not.
@function _share-common-parent($selector-lists...) {
// To validate, this function will extract the simple selectors from each
// complex selector and compare them to each other. Every complex selector
// should share at least one common simple parent selector.
//
// We do this by keeping track of each simple selector and if they're present
// within a complex selector. At the end of checking all the selectors, at
// least one of simple selectors should have been seen for each one of the
// complex selectors.
//
// Each selector list index needs to track its own selector count Map. This is
// because each comma-separated list has its own root parent selector that
// we're looking for:
// .foo,
// .bar {
// &:hover,
// .baz & { ... }
// }
// ('.foo:hover', '.bar:hover'), ('.baz' '.foo', '.baz' '.bar')
//
// In the first index of each selector list, we're looking for the parent
// ".foo". In the second index we're looking for the parent ".bar".
$selector-counts-by-index: ();
$expected-counts-by-index: ();
@each $selector-list in $selector-lists {
@each $complex-selector in $selector-list {
$selector-list-index: list.index($selector-list, $complex-selector);
$selector-count-map: map.get(
$selector-counts-by-index,
$selector-list-index
);
@if not $selector-count-map {
$selector-count-map: ();
}
$expected-count: map.get($expected-counts-by-index, $selector-list-index);
@if not $expected-count {
$expected-count: 0;
}
$simple-selectors-set: ();
@each $selector in $complex-selector {
@each $simple-selector in selector.simple-selectors($selector) {
// Don't use list.join() because there may be duplicate selectors
// within the complex selector. We want to treat $simple-selectors-set
// like a Set where there are no duplicate values so that we don't
// mess up our count by counting one simple selector too many times
// for a single complex selector.
@if not list.index($simple-selectors-set, $simple-selector) {
$simple-selectors-set: list.append(
$simple-selectors-set,
$simple-selector
);
}
}
}
// Now that we have a "Set" of simple selectors for this complex
// selector, we can go through each one and update the selector count Map.
@each $simple-selector in $simple-selectors-set {
$count: map.get($selector-count-map, $simple-selector);
@if $count {
$count: $count + 1;
} @else {
$count: 1;
}
$selector-count-map: map.merge(
$selector-count-map,
(
$simple-selector: $count,
)
);
}
$selector-counts-by-index: map.merge(
$selector-counts-by-index,
(
$selector-list-index: $selector-count-map,
)
);
$expected-counts-by-index: map.merge(
$expected-counts-by-index,
(
$selector-list-index: $expected-count + 1,
)
);
}
}
@each $index, $selector-count-map in $selector-counts-by-index {
// If one of the selectors was seen the expected number of times, then we
// can reasonably assume that each selector shares a common parent.
// Verify for each index if there are multiple parents.
$found-parent: false;
@each $selector, $count in $selector-count-map {
$expected-count: map.get($expected-counts-by-index, $index);
@if $count == $expected-count {
$found-parent: true;
}
}
@if not $found-parent {
@return false;
}
}
// A common parent was found for each selector, or there were no selectors
// provided and we did not enter any for loops.
@return true;
}
/// Returns true if the value is a Sass selector type.
///
/// Selector types are a 2D List: a comma-separated list (the selector list)
/// that contains space-separated lists (the complex selectors) that contain
/// unquoted strings (the compound selectors).
/// @link https://sass-lang.com/documentation/modules/selector
///
/// @example
/// .foo, .bar button:hover { }
/// $type: ((unquote('.foo')), (unquote('.bar') unquote('button:hover')),);
///
/// @param {*} $selector-list - A value to check.
/// @return {Boolean} true if the value is a Sass selector, or false if not.
@function _is-sass-selector($selector-list) {
// For the purposes of these utility functions, we don't care if the lists
// have the correct separated or if the strings are unquoted. All that
// matters is that the type is a 2D array and the values are strings to
// ensure "close enough" that the selector was generated by Sass.
//
// This function is primarily a safe-guard against an accidental string
// slipping in and forgetting to use a selector.append() which would cause a
// hard-to-debug problem.
@if meta.type-of($selector-list) != 'list' {
@return false;
}
// First level is the selector list: what's separated by commas
// e.g. ".foo, .bar"
@each $complex-selector in $selector-list {
// Second level is the complex selector: what's separated by spaces
// e.g. ".foo .bar"
@if meta.type-of($complex-selector) != 'list' {
@return false;
}
// Third level is the compound selector: the actual string
// e.g. ".foo"
@each $selector in $complex-selector {
@if meta.type-of($selector) != 'string' {
@return false;
}
}
}
@return true;
}