Source: lib/kss_modifier.js

'use strict';

/**
 * The `kss/lib/kss_modifier` module is normally accessed via the
 * [`KssModifier()`]{@link module:kss.KssModifier} class of the `kss` module:
 * ```
 * const KssModifier = require('kss').KssModifier;
 * ```
 * @private
 * @module kss/lib/kss_modifier
 */

/**
 * A KssModifier object represents a single modifier of a `KssSection`.
 *
 * This class is normally accessed via the [`kss`]{@link module:kss} module:
 * ```
 * const KssModifier = require('kss').KssModifier;
 * ```
 *
 * @alias module:kss.KssModifier
 */
class KssModifier {

  /**
   * Creates a KssModifier object and stores the given data.
   *
   * If passed an object, it will add `section`, `name`, `description`, and
   * `className` properties.
   *
   * @param {Object} [data] An object of data.
   */
  constructor(data) {
    data = data || {};

    this.meta = {
      section: null
    };

    this.data = {
      name: '',
      description: '',
      className: ''
    };

    // Loop through the given properties.
    for (let name in data) {
      // If the property is defined in this.data or this.meta, add it via our
      // API.
      if (data.hasOwnProperty(name) && (this.data.hasOwnProperty(name) || this.meta.hasOwnProperty(name))) {
        this[name](data[name]);
      }
    }
  }

  /**
   * Gets or sets the `KssSection` object this `KssModifier` is associated with.
   *
   * If the `section` value is provided, the `KssSection` for this modifier is
   * set. Otherwise, the `KssSection` of the modifier is returned.
   *
   * @param {KssSection} [section] Optional. The `KssSection` that owns the
   *   `KssModifier`.
   * @returns {KssSection|KssModifier} If section is given, the current
   *   `KssModifier` object is returned to allow chaining of methods. Otherwise,
   *   the `KssSection` object the modifier belongs to is returned.
   */
  section(section) {
    if (typeof section === 'undefined') {
      return this.meta.section;
    }

    this.meta.section = section;
    // Allow chaining.
    return this;
  }

  /**
   * Gets or sets the name of the `KssModifier`, e.g. `:hover`, `.primary`, etc.
   *
   * If the `name` value is provided, the name of this `KssModifier` is set.
   * Otherwise, the name of the `KssModifier` is returned.
   *
   * @param {string} [name] Optional. The name of the `KssModifier`.
   * @returns {string|KssModifier} If name is given, the current `KssModifier`
   *   object is returned to allow chaining of methods. Otherwise, the name of
   *   the `KssModifier` is returned.
   */
  name(name) {
    if (typeof name === 'undefined') {
      return this.data.name;
    }

    this.data.name = name;
    // Allow chaining.
    return this;
  }

  /**
   * Gets or sets the description of the `KssModifier`.
   *
   * If the `description` is provided, the description of this `KssModifier` is
   * set. Otherwise, the description of the `KssModifier` is returned.
   *
   * @param {string} [description] Optional. The description of the
   *   `KssModifier`.
   * @returns {string|KssModifier} If description is given, the current
   *   `KssModifier` object is returned to allow chaining of methods. Otherwise,
   *   the description of the `KssModifier` is returned.
   */
  description(description) {
    if (typeof description === 'undefined') {
      return this.data.description;
    }

    this.data.description = description;
    // Allow chaining.
    return this;
  }

  /**
   * Gets or sets CSS class(es) suitable to insert into a markup sample to
   * display the modifier's design.
   *
   * By default, the CSS classes the className() method returns are based on the
   * modifier's name. If the modifier's name includes a pseudo-class, e.g.
   * `:hover`, this method will replace the ":" with "pseudo-class-", which
   * matches the selector expected by the kss.js script and its
   * KssStateGenerator.
   *
   * ```
   * modifier.name('.primary:hover');
   * modifier.className(); // Returns "primary pseudo-class-hover"
   * ```
   *
   * To override, the default behavior the class(es) can also be set manually;
   * if the `className` parameter is provided, the className of this
   * `KssModifier` is set and will later be returned as-is instead of calculated
   * based on the `name()`.
   *
   * @param {string} [className] Optional. The class(es) of the `KssModifier`.
   * @returns {string|KssModifier} If the className parameter is given, the
   *   current `KssModifier` object is returned to allow chaining of methods.
   *   Otherwise, the class name(s) of the `KssModifier` are returned.
   */
  className(className) {
    if (typeof className === 'undefined') {
      if (this.data.className) {
        return this.data.className;
      } else {
        let name = this.name().replace(/:/g, '.pseudo-class-');

        // If the name includes child selectors, we only want the first parent
        // selector. Markup should not be multiple elements deep at this stage.
        name = name.split(/\s/)[0];

        // Split into space-separated classes.
        name = name
          .replace(/\./g, ' ')
          .replace(/^\s*/g, '');

        return name;
      }
    }

    this.data.className = className;
    // Allow chaining.
    return this;
  }

  /**
   * Returns the HTML markup used to render this modifier.
   *
   * The markup is retrieved from the KssModifier's section. See
   * `KssSection.markup()` to see how to set the markup.
   *
   * @returns {string} The markup of the modifier.
   */
  markup() {
    if (!this.section()) {
      return '';
    }

    return (this.section().markup() || '');
  }

  /**
   * Return the `KssModifier` as a JSON object.
   *
   * @returns {Object} A JSON object representation of the `KssModifier`.
   */
  toJSON() {
    return {
      name: this.name(),
      description: this.description(),
      className: this.className()
    };
  }
}

module.exports = KssModifier;