// // This file defines the public Sass API to ag-Grid's styles. // https://www.ag-grid.com/javascript-data-grid/theming-v32-customisation-sass/ // @use 'sass:map'; @use 'sass:list'; @use 'sass:meta'; @use 'sass:string'; @use 'sass:color'; @use 'sass:math'; @use './css-content'; @use './icon-font-codes' as *; @forward './shared'; // Emit styles for the grid. This mixin validates the parameters passed to it, // converts the parameters to CSS variables, and combines all the necessary CSS // files for the grid and selected theme. @mixin grid-styles($global-params: ()) { $no-native-widgets: map.get($global-params, 'suppress-native-widget-styling'); $global-params: map.remove($global-params, 'suppress-native-widget-styling'); $themes: -get-themes($global-params); @if $no-native-widgets { @include -load-css-file('ag-grid-no-native-widgets.css'); } @else { @include -load-css-file('ag-grid.css'); } $global-extended-theme-name: -clean-theme-name(map.get($global-params, 'extend-theme')); @each $theme-name, $theme-params in $themes { @include -validate-params($theme-params, $theme-name); @include -load-theme-css-file($theme-name); $extended-theme-name: -get-extended-theme-name($theme-params, $default: $global-extended-theme-name); @if $extended-theme-name and not -is-provided-theme($extended-theme-name) { @error "Invalid extend-theme: \"#{$extended-theme-name}\": only provided themes can be extended, use one of #{meta.inspect(map.keys($-theme-css-files))}"; } @if $extended-theme-name and -is-provided-theme($theme-name) { @error "Can't use a provided theme (#{$theme-name}) and also extend a provided theme (#{$extended-theme-name}). If you want to extend #{$extended-theme-name}, provide your own custom theme name."; } @if $extended-theme-name { @include -load-theme-css-file($extended-theme-name); } $font: map.get($theme-params, '--ag-icon-font-family'); $font: map.get($-theme-fonts, $theme-name) !default; $font: map.get($-theme-fonts, $extended-theme-name) !default; @if $font { @include -load-css-file('#{$font}Font.css', $ignore-missing: true); } } @each $theme-name, $theme-params in $themes { $extended-theme-name: -get-extended-theme-name($theme-params, $default: $global-extended-theme-name); @if $extended-theme-name and $extended-theme-name != $theme-name { // alias all the styles in the extended theme to the new theme name .ag-theme-#{$theme-name} { @extend .ag-theme-#{$extended-theme-name}; } } // Emit CSS variable declarations for a set of params, converting (foo: bar) to `--ag-foo: bar` .ag-theme-#{$theme-name} { $color-blend-theme-name: $extended-theme-name; $color-blend-theme-name: $theme-name !default; $blend-function-name: '-theme-#{$color-blend-theme-name}-color-blends'; @if meta.function-exists($blend-function-name) { $theme-color-blends: meta.get-function($blend-function-name); $theme-params: meta.call($theme-color-blends, $theme-params); } @if (not -is-quartz($theme-name) and not -is-quartz($extended-theme-name)) { $theme-params: -base-color-blends($theme-params); } @each $name, $value in $theme-params { // use meta.inspect to preserve the quote style of strings @if map.has-key($-variable-param-types, $name) { #{$name}: #{meta.inspect($value)}; } } } } } // // PRIVATE IMPLEMENTATION // $-loaded-css-files: (); // Fulfils the same role as meta.load-css, which we can't use due to this issue: // https://github.com/sass/dart-sass/issues/1627 // // NOTE the above bug was fixed in Sass 1.52.4 in June 2022, this workaround should // be kept as long as we want to support earlier Sass versions @mixin -load-css-file($file, $ignore-missing: false) { @if not map.get($-loaded-css-files, $file) { $-loaded-css-files: map.set($-loaded-css-files, $file, true) !global; @include css-content.output-css-file($file, $ignore-missing: $ignore-missing); } } @mixin -load-theme-css-file($theme-name, $ignore-missing: false) { $css-file: map.get($-theme-css-files, $theme-name); @if $css-file { @include -load-css-file($css-file); } } @function -clean-theme-name($name) { @if $name == null { @return null; } $name: string.to-lower-case($name); $remove-prefix: 'ag-theme-'; @if string.index($name, $remove-prefix) == 1 { $name: string.slice($name, string.length($remove-prefix) + 1); } @return $name; } @function -is-quartz($theme) { @return $theme and string.index(-clean-theme-name($theme), 'quartz') == 1; } @function -clean-variable-name($name) { @if $name == null { @return null; } $name: string.to-lower-case($name); $proposed-variable-name: '--ag-#{$name}'; @if map.has-key($-variable-param-types, $proposed-variable-name) { $name: $proposed-variable-name; } $remove-prefix: 'ag-theme-'; @if string.index($name, $remove-prefix) == 1 { $name: string.slice($name, string.length($remove-prefix) + 1); } @return $name; } @function -get-extended-theme-name($theme-params, $default) { $extended-theme-name: -clean-theme-name(map.get($theme-params, 'extend-theme')); @if $extended-theme-name { @return $extended-theme-name; } @return $default; } $-theme-variables: ( 'quartz': ( active-color: 'color', ), 'quartz-dark': ( active-color: 'color', ), 'alpine': ( alpine-active-color: 'color', ), 'alpine-dark': ( alpine-active-color: 'color', ), 'balham': ( balham-active-color: 'color', ), 'balham-dark': ( balham-active-color: 'color', ), 'material': ( material-primary-color: 'color', material-accent-color: 'color', ), ); $-theme-css-files: ( 'quartz': 'ag-theme-quartz-no-font.css', 'quartz-dark': 'ag-theme-quartz-no-font.css', 'alpine': 'ag-theme-alpine-no-font.css', 'alpine-dark': 'ag-theme-alpine-no-font.css', 'balham': 'ag-theme-balham-no-font.css', 'balham-dark': 'ag-theme-balham-no-font.css', 'material': 'ag-theme-material-no-font.css', 'material-dark': 'ag-theme-material-no-font.css', ); $-theme-fonts: ( 'quartz': 'agGridQuartz', 'quartz-dark': 'agGridQuartz', 'alpine': 'agGridAlpine', 'alpine-dark': 'agGridAlpine', 'balham': 'agGridBalham', 'balham-dark': 'agGridBalham', 'material': 'agGridMaterial', 'material-dark': 'agGridMaterial', ); @function -is-provided-theme($theme-name) { @return map.has-key($-theme-variables, $theme-name); } @function -get-themes($params) { $themes: map.get($params, 'themes'); $themes: () !default; @if meta.type-of($themes) == string { $themes: ($themes); // comma makes this a single element list } // treat ("alpine", "balham") as (alpine: (), "balham": ()) @if meta.type-of($themes) == list { $themes-list: $themes; $themes: (); @each $theme in $themes-list { $themes: map.set($themes, $theme, ()); } } @if map.has-key($params, 'theme') { $theme: map.get($params, 'theme'); @if meta.type-of($theme) == list { @error "Expected theme to be a string, got a list (if you intend to use multiple themes, use the plural `themes` parameter)"; } @else if meta.type-of($theme) != string { @error "Expected theme to be a string, got #{meta.inspect($theme)})"; } $themes: map.set($themes, $theme, ()); } @if list.length($themes) == 0 { $themes: ( quartz: (), ); } $params: map.remove($params, 'theme', 'themes'); $cleaned: (); @each $name, $extra-params in $themes { $theme-params: internal-preprocess-params(map.merge($params, $extra-params)); $cleaned: map.set($cleaned, -clean-theme-name($name), $theme-params); } @return $cleaned; } // Return a version of $params with colour blending applied @function -base-color-blends($params) { // Simple defaults. We need to define the defaults for any parameters // that are used in blending below $params: -param-default($params, --ag-foreground-color, #000); $params: -param-default($params, --ag-background-color, #fff); $params: -param-default($params, --ag-range-selection-border-color, --ag-foreground-color); // Blended defaults. $params: -param-default($params, --ag-disabled-foreground-color, --ag-foreground-color, $alpha: 0.5); $params: -param-default($params, --ag-modal-overlay-background-color, --ag-background-color, $alpha: 0.66); $params: -param-default( $params, --ag-range-selection-background-color, --ag-range-selection-border-color, $alpha: 0.2 ); $params: -param-default( $params, --ag-range-selection-background-color-2, --ag-range-selection-background-color, $self-overlay: 2 ); $params: -param-default( $params, --ag-range-selection-background-color-3, --ag-range-selection-background-color, $self-overlay: 3 ); $params: -param-default( $params, --ag-range-selection-background-color-4, --ag-range-selection-background-color, $self-overlay: 4 ); $params: -param-default($params, --ag-border-color, --ag-foreground-color, $alpha: 0.25); $params: -param-default($params, --ag-header-column-separator-color, --ag-border-color, $alpha: 0.5); $params: -param-default($params, --ag-header-column-resize-handle-color, --ag-border-color, $alpha: 0.5); $params: -param-default($params, --ag-input-disabled-border-color, --ag-input-border-color, $alpha: 0.3); @return $params; } @function -theme-alpine-color-blends($params) { // Simple defaults. We need to define the defaults for any parameters // that are used in blending either below or in the base color blends $params: -param-default($params, --ag-background-color, #fff); $params: -param-default($params, --ag-foreground-color, #181d1f); $params: -param-default($params, --ag-subheader-background-color, #fff); $params: -param-default($params, --ag-alpine-active-color, #2196f3); $params: -param-default($params, --ag-range-selection-border-color, --ag-alpine-active-color); // Blended defaults $params: -param-default( $params, --ag-subheader-toolbar-background-color, --ag-subheader-background-color, $alpha: 0.5 ); $params: -param-default($params, --ag-selected-row-background-color, --ag-alpine-active-color, $alpha: 0.1); $params: -param-default($params, --ag-row-hover-color, --ag-alpine-active-color, $alpha: 0.1); $params: -param-default($params, --ag-column-hover-color, --ag-alpine-active-color, $alpha: 0.1); $params: -param-default($params, --ag-chip-background-color, --ag-foreground-color, $alpha: 0.07); $params: -param-default($params, --ag-input-disabled-background-color, --ag-border-color, $alpha: 0.15); $params: -param-default($params, --ag-input-disabled-border-color, --ag-border-color, $alpha: 0.3); $params: -param-default($params, --ag-disabled-foreground-color, --ag-foreground-color, $alpha: 0.5); $params: -param-default($params, --ag-input-focus-border-color, --ag-alpine-active-color, $alpha: 0.4); @return $params; } @function -theme-alpine-dark-color-blends($params) { $params: -param-default($params, --ag-background-color, #181d1f); $params: -param-default($params, --ag-foreground-color, #fff); $params: -param-default($params, --ag-subheader-background-color, #000); @return -theme-alpine-color-blends($params); } @function -theme-balham-color-blends($params) { // Simple defaults. We need to define the defaults for any parameters // that are used in blending either below or in the base color blends $params: -param-default($params, --ag-background-color, #fff); $params: -param-default($params, --ag-foreground-color, #000); $params: -param-default($params, --ag-border-color, #bdc3c7); $params: -param-default($params, --ag-subheader-background-color, #e2e9eb); $params: -param-default($params, --ag-balham-active-color, #0091ea); $params: -param-default($params, --ag-range-selection-border-color, --ag-balham-active-color); // Blended defaults $params: -param-default($params, --ag-secondary-foreground-color, --ag-foreground-color, $alpha: 0.54); $params: -param-default($params, --ag-disabled-foreground-color, --ag-foreground-color, $alpha: 0.38); $params: -param-default( $params, --ag-subheader-toolbar-background-color, --ag-subheader-background-color, $alpha: 0.5 ); $params: -param-default($params, --ag-row-border-color, --ag-border-color, $alpha: 0.58); $params: -param-default($params, --ag-chip-background-color, --ag-foreground-color, $alpha: 0.1); $params: -param-default($params, --ag-selected-row-background-color, --ag-balham-active-color, $alpha: 0.28); $params: -param-default($params, --ag-header-column-separator-color, --ag-border-color, $alpha: 0.5); @return $params; } @function -theme-balham-dark-color-blends($params) { $params: -param-default($params, --ag-background-color, #181d1f); $params: -param-default($params, --ag-foreground-color, #fff); $params: -param-default($params, --ag-border-color, #424242); $params: -param-default($params, --ag-subheader-background-color, #000); $params: -param-default($params, --ag-balham-active-color, #00b0ff); $params: -param-default($params, --ag-disabled-foreground-color, --ag-foreground-color, $alpha: 0.38); $params: -param-default($params, --ag-header-foreground-color, --ag-foreground-color, $alpha: 0.64); @return -theme-balham-color-blends($params); } @function -theme-material-color-blends($params) { // Simple defaults. We need to define the defaults for any parameters // that are used in blending either below or in the base color blends $params: -param-default($params, --ag-background-color, #fff); $params: -param-default($params, --ag-foreground-color, rgba(0, 0, 0, 0.87)); $params: -param-default($params, --ag-subheader-background-color, #eee); $params: -param-default($params, --ag-material-primary-color, #3f51b5); $params: -param-default($params, --ag-range-selection-border-color, --ag-material-primary-color); $params: -param-default($params, --ag-range-selection-background-color, rgba(122, 134, 203, 0.1)); $params: -param-default($params, --ag-border-color, #e2e2e2); // Blended defaults $params: -param-default($params, --ag-secondary-foreground-color, --ag-foreground-color, $alpha: 0.54); $params: -param-default($params, --ag-disabled-foreground-color, --ag-foreground-color, $alpha: 0.38); $params: -param-default( $params, --ag-subheader-toolbar-background-color, --ag-subheader-background-color, $alpha: 0.5 ); @return $params; } // Apply a default value to a parameter // $params: -param-default($params, x, #f08) - default x to a specific color value // $params: -param-default($params, x, y) - default x to the value of y (if y is set) // $params: -param-default($params, x, y, $alpha: 0.5) - default x to the value of y made 50% transparent @function -param-default($params, $target, $source, $alpha: null, $self-overlay: null) { @if string.index($target, '--ag-') != 1 { @error "Internal error: $target to -param-default should start --ag-"; } $value: null; @if meta.type-of($source) == 'color' { $value: $source; } @else { @if string.index($source, '--ag-') != 1 { @error "Internal error: $source to -param-default should start --ag-"; } $value: map.get($params, $source); } @if map.has-key($params, $target) or $value == null { @return $params; } @if $alpha != null { $value: color.change($value, $alpha: color.alpha($value) * $alpha); } @if $self-overlay != null { // this formula produces the same opacity value as overlaying the color on top // of itself $self-overlay times $value: color.change($value, $alpha: 1-(math.pow(1 - color.alpha($value), $self-overlay))); } @return map.set($params, $target, $value); } $-param-type-descriptions: ( 'color': 'a CSS color (e.g. `red` or `#fff`)', 'length': 'a CSS length (e.g. `0`, `4px` or `50%`)', 'border-style': 'a CSS border style (e.g. `dotted` or `solid`)', 'duration': 'a number with time duration units (e.g. `3s` or `250ms`)', 'border-style-and-size': 'either `none`, or a CSS border-style and size (e.g. `solid 1px`), or a boolean (true -> `solid 1px` and false -> `none`)', 'border-style-and-color': 'a CSS border-style and color (e.g. `solid red`)', 'display': 'A CSS display value (`block` to show, `none` to hide - `true` and `false` are also accepted)', 'any': 'any value', ); // params that are copied to --ag-param-name variables $-variable-param-types: ( --ag-foreground-color: 'color', --ag-data-color: 'color', --ag-secondary-foreground-color: 'color', --ag-header-foreground-color: 'color', --ag-disabled-foreground-color: 'color', --ag-background-color: 'color', --ag-header-background-color: 'color', --ag-tooltip-background-color: 'color', --ag-subheader-background-color: 'color', --ag-subheader-toolbar-background-color: 'color', --ag-control-panel-background-color: 'color', --ag-side-button-selected-background-color: 'color', --ag-selected-row-background-color: 'color', --ag-odd-row-background-color: 'color', --ag-modal-overlay-background-color: 'color', --ag-row-hover-color: 'color', --ag-column-hover-color: 'color', --ag-range-selection-border-color: 'color', --ag-range-selection-border-style: 'border-style', --ag-range-selection-background-color: 'color', --ag-range-selection-background-color-2: 'color', --ag-range-selection-background-color-3: 'color', --ag-range-selection-background-color-4: 'color', --ag-range-selection-highlight-color: 'color', --ag-selected-tab-underline-color: 'color', --ag-selected-tab-underline-width: 'length', --ag-selected-tab-underline-transition-speed: 'duration', --ag-range-selection-chart-category-background-color: 'color', --ag-range-selection-chart-background-color: 'color', --ag-header-cell-hover-background-color: 'color', --ag-header-cell-moving-background-color: 'color', --ag-value-change-value-highlight-background-color: 'color', --ag-value-change-delta-up-color: 'color', --ag-value-change-delta-down-color: 'color', --ag-chip-background-color: 'color', --ag-borders: 'border-style-and-size', --ag-border-color: 'color', --ag-borders-critical: 'border-style-and-size', --ag-borders-secondary: 'border-style-and-size', --ag-secondary-border-color: 'color', --ag-row-loading-skeleton-effect-color: 'color', --ag-row-border-style: 'border-style', --ag-row-border-width: 'length', --ag-row-border-color: 'color', --ag-cell-horizontal-border: 'border-style-and-color', --ag-borders-input: 'border-style-and-size', --ag-input-border-color: 'color', --ag-borders-input-invalid: 'border-style-and-size', --ag-input-border-color-invalid: 'color', --ag-borders-side-button: 'border-style-and-size', --ag-border-radius: 'length', --ag-header-column-separator-display: 'display', --ag-header-column-separator-height: 'length', --ag-header-column-separator-width: 'length', --ag-header-column-separator-color: 'color', --ag-header-column-resize-handle-display: 'display', --ag-header-column-resize-handle-height: 'length', --ag-header-column-resize-handle-width: 'length', --ag-header-column-resize-handle-color: 'color', --ag-invalid-color: 'color', --ag-input-disabled-border-color: 'color', --ag-input-disabled-background-color: 'color', --ag-checkbox-background-color: 'color', --ag-checkbox-border-radius: 'length', --ag-checkbox-checked-color: 'color', --ag-checkbox-unchecked-color: 'color', --ag-checkbox-indeterminate-color: 'color', --ag-toggle-button-off-border-color: 'color', --ag-toggle-button-off-background-color: 'color', --ag-toggle-button-on-border-color: 'color', --ag-toggle-button-on-background-color: 'color', --ag-toggle-button-switch-background-color: 'color', --ag-toggle-button-switch-border-color: 'color', --ag-toggle-button-border-width: 'length', --ag-toggle-button-height: 'length', --ag-toggle-button-width: 'length', --ag-input-focus-box-shadow: 'any', --ag-input-focus-border-color: 'color', --ag-minichart-selected-chart-color: 'color', --ag-minichart-selected-page-color: 'color', --ag-grid-size: 'length', --ag-icon-size: 'length', --ag-widget-container-horizontal-padding: 'length', --ag-widget-container-vertical-padding: 'length', --ag-widget-horizontal-spacing: 'length', --ag-widget-vertical-spacing: 'length', --ag-cell-horizontal-padding: 'length', --ag-cell-widget-spacing: 'length', --ag-row-height: 'length', --ag-header-height: 'length', --ag-list-item-height: 'length', --ag-column-select-indent-size: 'length', --ag-set-filter-indent-size: 'length', --ag-advanced-filter-builder-indent-size: 'length', --ag-row-group-indent-size: 'length', --ag-filter-tool-panel-group-indent: 'length', --ag-tab-min-width: 'length', --ag-menu-min-width: 'length', --ag-side-bar-panel-width: 'length', --ag-font-family: 'any', --ag-font-size: 'length', --ag-card-radius: 'length', --ag-card-shadow: 'any', --ag-popup-shadow: 'any', --ag-active-color: 'color', --ag-alpine-active-color: 'color', --ag-balham-active-color: 'color', --ag-material-primary-color: 'color', --ag-material-accent-color: 'color', --ag-icon-font-family: 'any', ); @each $icon-name in map.keys($icon-font-codes) { $-variable-param-types: map.set($-variable-param-types, '--ag-icon-font-code-#{$icon-name}', 'any'); } // params that are not copied to CSS variables $-non-variable-param-types: ( suppress-native-widget-styling: 'bool', extend-theme: 'string', ); // Apply Sass API sugar over CSS variable API @function internal-preprocess-params($params) { $processed: (); @each $name, $value in $params { $name: -clean-variable-name($name); $type: map.get($-variable-param-types, $name); // Allow `null` for colours @if $type == 'color' and $value == null { $value: transparent; } // Allow booleans for borders params @if string.index($name, 'borders') and $value == true { $value: solid 1px; } @if string.index($name, 'borders') and $value == false { $value: none; } // Allow booleans for display params @if $type == 'display' and not $value { $value: none; } @if $type == 'display' and $value == true { $value: block; } $processed: map.set($processed, $name, $value); } @return $processed; } @mixin -validate-params($params, $theme: null) { $error: internal-get-params-error($params, $theme); @if $error { @error $error; } } @function internal-get-params-error($params, $theme: null) { $theme-variables: (); @if $theme and map.has-key($-theme-variables, $theme) { $theme-variables: map.get($-theme-variables, $theme); } $param-types: map.merge($-variable-param-types, $theme-variables); $errors: (); @each $name, $value in $params { @if $name == 'suppress-native-widget-styling' { $errors: list.append( $errors, 'suppress-native-widget-styling can not be specified at the theme level, only at the top level' ); } @if -is-runtime-expression($value) { // You can pass variable expressions as values and we'll just trust // that it's an appropriate value because we can't check at compile time } @else { $expected-type: map.get($param-types, $name); @if $expected-type { $is-valid: -validate-value($value, $expected-type); @if not $is-valid { $expected: map.get($-param-type-descriptions, $expected-type); $expected: $expected-type !default; $errors: list.append( $errors, 'Invalid parameter `#{$name}: #{meta.inspect($value)}` (expected #{$expected})' ); } } @else if not map.has-key($-non-variable-param-types, $name) { $errors: list.append($errors, "Unrecognised parameter '#{$name}'"); } } } @if list.length($errors) > 0 { $message: ''; @each $error in $errors { @if $message == '' { $message: $error; } @else { $message: '#{$message}; #{$error}'; } } @if list.length($errors) > 1 { $message: '#{list.length($errors)} errors in #{$theme} parameters: #{$message}'; } @else { $message: 'Error in #{$theme} parameters: #{$message}'; } @return $message; } @return null; } @function -validate-value($value, $expected-type) { $validator-name: '-validate-#{$expected-type}'; $validator: meta.get-function($validator-name); @if not $validator { @error "Internal error: no validator function #{$validator-name}"; } @return meta.call($validator, $value); } @function -is-runtime-expression($value) { $value: string.to-lower-case(meta.inspect($value)); @return string.index($value, 'var(') != null or string.index($value, 'calc(') != null; } @function -validate-color($value) { @return meta.type-of($value) == 'color'; } @function -validate-length($value) { @return meta.type-of($value) == 'number' and (unit($value) != '' or $value == 0); } $border-styles: (none, hidden, dotted, dashed, solid, double, groove, ridge, inset, outset, initial); @function -validate-border-style($value) { $value-preserve-quotes: meta.inspect($value); @return list.index($border-styles, $value-preserve-quotes) != null; } @function -validate-duration($value) { @return meta.type-of($value) == 'number' and (unit($value) == 's' or unit($value) == 'ms' or $value == 0); } @function -validate-display($value) { @return $value == block or $value == none; } @function -validate-border-style-and-size($value) { @return -validate-two-in-any-order($value, 'border-style', 'length'); } @function -validate-border-style-and-color($value) { @return -validate-two-in-any-order($value, 'border-style', 'color'); } @function -validate-bool($value) { @return meta.type-of($value) == 'bool'; } @function -validate-string($value) { @return meta.type-of($value) == 'string'; } @function -validate-any($value) { @return true; } @function -validate-two-in-any-order($value, $type-a, $type-b) { @if meta.type-of($value) == 'string' and $value == 'none' { @return true; } @if meta.type-of($value) != 'list' or list.length($value) != 2 { @return false; } $value-1: list.nth($value, 1); $value-2: list.nth($value, 2); @return ( (-validate-value($value-1, $type-a) and -validate-value($value-2, $type-b)) or (-validate-value($value-2, $type-a) and -validate-value($value-1, $type-b)) ); } // check that we defined all the validator functions @each $variable-name, $type in $-variable-param-types { @if not map.has-key($-param-type-descriptions, $type) { @error "Internal error: missing type description for #{$type} (used on #{$variable-name})"; } @if not meta.function-exists('-validate-#{$type}') { @error "Internal error: missing validator function for #{$type} (used on #{$variable-name})"; } }