You are here

StyleBase.php in GridStack 8.2

File

src/Plugin/gridstack/stylizer/StyleBase.php
View source
<?php

namespace Drupal\gridstack\Plugin\gridstack\stylizer;

use Drupal\Component\Utility\Html;
use Drupal\gridstack\GridStackStylizerPluginBase;
use Drupal\gridstack\GridStackDefault;

/**
 * Provides the base styles.
 *
 * @todo move all backend/ form stuffs into GridStackAdminStylizer.
 */
abstract class StyleBase extends GridStackStylizerPluginBase {

  /**
   * Provides both CSS grid and js-driven attributes configurable via UI.
   *
   * Can be applied to either $attributes or $content_attributes depending on
   * whether using CSS framework, or original js-driven or native Grid layouts.
   * Must be applied to $content_attributes for UI draggable to work.
   * Do not unset anything here so that nested grids (.row) can still access it.
   *
   * The CSS BG rule is good at .box or .box__content for native grid, yet
   * .box__content for js-driven and CSS framework due to absolute and
   * overlapping containers respectively. The CSS framework was done at
   * self::modifyNestedItem. For logic consistency, js-driven native grid uses
   * .box__content too.
   */
  public function attributes(array &$attributes, array $settings) {
    if (!empty($settings['attributes']) && is_string($settings['attributes'])) {
      $this
        ->parseAttributes($attributes, $settings['attributes']);
    }
    foreach ([
      'row',
      'wrapper',
    ] as $key) {
      if (!empty($settings[$key . '_classes']) && is_string($settings[$key . '_classes'])) {
        $this
          ->parseClasses($attributes, $settings[$key . '_classes']);
      }
    }

    // These options affect layouts, provides the contexts.
    $this
      ->extraAttributes($attributes, $settings);
  }

  /**
   * Returns selectors and sub-selectors with context related to box levels.
   */
  public function getSelector(array $settings, $key = '', array $rules = []) {
    $sub = '';
    $selector = $settings['_selector'];
    $index = isset($settings['delta']) ? $settings['delta'] + 1 : 0;
    $overlay = $this
      ->getStyle('overlay', $settings);
    $rid = empty($settings['rid']) ? '' : $settings['rid'];
    $suffix = str_replace([
      'gridstack_',
      ':root',
    ], '', $rid);
    $suffix = str_replace('_', '-', $suffix);
    switch ($settings['_level']) {
      case GridStackDefault::LEVEL_ROOT:
        $context = GridStackDefault::ROOT;
        $suffix = 0;
        break;
      case GridStackDefault::LEVEL_ROOT_ITEM:
        $context = $index;
        $selector = $selector . ' .box--' . $index;
        $sub = '.box__content';
        break;
      case GridStackDefault::LEVEL_NESTED:
        $context = GridStackDefault::NESTED . $index;
        $selector = $selector . ' .gridstack--' . $index;
        break;
      case GridStackDefault::LEVEL_NESTED_ITEM:
        $context = $settings['nested_id'];
        $selector = $selector . ' .box--' . $context;
        $sub = '.box__content';
        break;
      default:
        break;
    }
    $main_selector = $sub ? $selector . ' ' . $sub : $selector;

    // @todo $bg_selector = $overlay ? $selector . ' .b-gs--' . $suffix . ' .media__overlay' : $main_selector;
    $bg_selector = $overlay ? $selector . ' .b-gs .media__overlay' : $main_selector;
    $rules = $rules ? array_unique($rules) : [];
    $data = [
      'index' => $index,
      'level' => $settings['_level'],
      'context' => $context,
      'selector' => $main_selector,
      'bg_selector' => $bg_selector,
      'overlay' => $overlay,
      'rid' => $rid,
      'suffix' => $suffix,
      'sub' => $sub,
      'rules' => $rules,
    ];
    if ($key) {
      return isset($data[$key]) ? $data[$key] : FALSE;
    }
    return $data;
  }

  /**
   * Modifies inline style to not nullify others.
   */
  public function inlineStyle(array &$attributes, $css) {
    $attributes['style'] = (isset($attributes['style']) ? $attributes['style'] : '') . $css;
  }

  /**
   * Builds inline styles if so required with multiple instances on a page.
   */
  public function parseStyles(array $styles, $stringify = FALSE) {
    $build = [];
    foreach ($styles as $id => $groups) {
      foreach ($groups as $rules) {
        foreach ($rules as $selector => $rule) {
          if ($stringify) {
            $build[$id][] = $this
              ->cssRule($selector, $rule, TRUE);
          }
          else {
            $build[$id][$selector] = $rule;
          }
        }
      }
    }
    return $build;
  }

  /**
   * Returns an animation.
   */
  public function getAnimation(array $settings, $key = 'animation') {
    return $this
      ->getStyle($key, $settings, 'animations');
  }

  /**
   * Returns a style.
   */
  public function getStyle($key, array $settings, $group = 'extras') {
    $values = empty($settings['styles'][$group]) ? [] : $settings['styles'][$group];
    if ($key == 'all') {
      return is_array($values) ? array_filter($values) : $values;
    }
    $output = empty($values[$key]) ? FALSE : $values[$key];

    // Chances are optionset is updated, but Layout Builder settings not.
    if ($key == 'ete' && $output && isset($settings['region']['_fw'])) {
      $output = $settings['region']['_fw'];
    }
    return $output;
  }

  /**
   * Returns the CSS rule with a selector and sub selector if available.
   */
  protected function cssRule($selector, $rule, $stringify = FALSE) {
    return $stringify ? $selector . '{' . $rule . '}' : [
      $selector => $rule,
    ];
  }

  /**
   * Checks if it has colors.
   */
  protected function hasColors(array $settings = []) {
    return isset($settings['styles']['colors']) && array_filter($settings['styles']['colors']);
  }

  /**
   * Returns available colors.
   */
  protected function getColors(array $settings = []) {
    return $this
      ->hasColors($settings) ? $settings['styles']['colors'] : [];
  }

  /**
   * Checks for valid color excluding black (#000000) by design.
   */
  protected function getColor($key, array $settings = []) {
    $colors = $this
      ->getColors($settings);
    return !empty($colors[$key]) && $colors[$key] != '#000000' ? $colors[$key] : FALSE;
  }

  /**
   * Modifies any attributes relevant to use backgrounds.
   */
  protected function extraAttributes(array &$attributes, array $settings) {
    if ($extras = $this
      ->getStyle('all', $settings, 'extras')) {
      foreach (array_keys($extras) as $key) {
        $attributes['class'][] = 'is-gs-' . $key;
      }
    }
  }

  /**
   * Parses the string attribute: role|navigation,data-something|some value.
   *
   * No need to whitelist as this already requires admin priviledges.
   * With admin privileges, the site is already taken over before playing
   * around with attributes. However provides few basic sanitizations to
   * satisfy curious playful editors.
   *
   * @nottodo: Token support.
   */
  protected function parseAttributes(array &$attributes, $string = '') {
    foreach (explode(',', $string) as $data) {
      if (strpos($data, '|') !== FALSE) {
        list($key, $value) = array_pad(array_map('trim', explode('|', $data, 2)), 2, NULL);
        $key = mb_substr($key, 0, 2) === 'on' ? 'data-' . $key : $key;
        $attributes[$key] = Html::cleanCssIdentifier(strip_tags($value));
      }
    }
  }

  /**
   * Parses the given string classes.
   */
  protected function parseClasses(array &$attributes, $string = '') {
    $classes = array_map('\\Drupal\\Component\\Utility\\Html::cleanCssIdentifier', explode(' ', $string));
    $attributes['class'] = empty($attributes['class']) ? array_unique($classes) : array_unique(array_merge($attributes['class'], $classes));
  }

  /**
   * Returns the variant class.
   */
  public function getVariantClass($variant) {

    // Unless prefixed, Html::getClass removes number, 03|13 Twain -> 3-twain.
    return Html::getClass('is-gs-variant-' . str_replace([
      '_',
    ], '-', $variant));
  }

  /**
   * Return the style element.
   */
  protected function styleElement($key, $value, array $settings) {
    $context = $settings['_scope'];
    $range = in_array($key, GridStackDefault::rangeElements());
    $css = str_replace([
      '_',
      ':',
    ], '-', $key);
    $type = $key == 'rgba' ? 'hidden' : 'color';
    $type = $range ? 'range' : $type;
    $height = strpos($key, 'height') !== FALSE;
    $property = $key == 'bg' ? 'background-color' : 'color';
    $property = $range || $key == 'rgba' ? $key : $property;
    $title = $key == 'alpha' ? 'BG transparency' : $key;
    $title = $key == 'opacity' ? 'Image opacity' : $title;
    $title = $range ? $title : mb_strtoupper($title);
    $selector = $key !== 'text' && in_array($key, GridStackDefault::textElements()) ? $key : '';
    $selector = $key == 'opacity' ? '.b-gs' : $selector;
    $selector = $height ? '.is-gs-ete' : $selector;
    $group_type = $type == 'hidden' ? 'color' : $type;
    $group_css = $type;
    if (in_array($key, [
      'alpha',
      'opacity',
    ])) {
      $group_css = 'color';
    }
    $element = [
      '#type' => $type,
      '#title' => $this
        ->t('@title', [
        '@title' => $title,
      ]),
      '#default_value' => $range && empty($value) ? 1 : $value,
      '#attributes' => [
        'class' => [
          'form-' . $group_type . '--gs',
          'form-' . $group_type . '--gs-' . $css,
          'form-' . $group_type . '--gs-' . $context,
          'form-' . $group_type . '--gs-' . $group_css,
        ],
        'data-gs-color-picker' => $property,
        'data-gs-color-region' => $context,
        'data-gs-target-selector' => $selector,
      ],
    ];
    if ($height) {
      $element['#attributes']['class'][] = 'form-range--gs-height';
    }
    return $element;
  }

}

Classes

Namesort descending Description
StyleBase Provides the base styles.