<commit_before>/**
 * The select menu is a simple widget designed to filter a dimension by selecting an option from
 * an HTML `<select/>` menu. The menu can be optionally turned into a multiselect.
 * @class selectMenu
 * @memberof dc
 * @mixes dc.baseMixin
 * @example
 * // create a select menu under #select-container using the default global chart group
 * var select = dc.selectMenu('#select-container')
 *                .dimension(states)
 *                .group(stateGroup);
 * // the option text can be set via the title() function
 * // by default the option text is '`key`: `value`'
 * select.title(function (d){
 *     return 'STATE: ' + d.key;
 * })
 * @param {String|node|d3.selection|dc.compositeChart} parent - Any valid
 * [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying
 * a dom block element such as a div; or a dom element or d3 selection.
 * @param {String} [chartGroup] - The name of the chart group this widget should be placed in.
 * Interaction with the widget will only trigger events and redraws within its group.
 * @returns {selectMenu}
 **/
dc.selectMenu = function (parent, chartGroup) {
    var SELECT_CSS_CLASS = 'dc-select-menu';
    var OPTION_CSS_CLASS = 'dc-select-option';

    var _chart = dc.baseMixin({});

    var _select;
    var _promptText = 'Select all';
    var _multiple = false;
    var _size = null;
    var _order = function (a, b) {
        return _chart.keyAccessor()(a) > _chart.keyAccessor()(b) ?
             1 : _chart.keyAccessor()(b) > _chart.keyAccessor()(a) ?
            -1 : 0;
    };

    var _filterDisplayed = function (d) {
        return _chart.valueAccessor()(d) > 0;
    };

    _chart.data(function (group) {
        return group.all().filter(_filterDisplayed);
    });

    _chart._doRender = function () {
        _chart.select('select').remove();
        _select = _chart.root().append('select')
                        .classed(SELECT_CSS_CLASS, true);
        _select.append('option').text(_promptText).attr('value', '');

        _chart._doRedraw();
        return _chart;
    };

    _chart._doRedraw = function () {
        setAttributes();
        renderOptions();
        // select the option(s) corresponding to current filter(s)
        if (_chart.hasFilter() && _multiple) {
            _select.selectAll('option')
                .property('selected', function (d) {
                    return d && _chart.filters().indexOf(String(_chart.keyAccessor()(d))) >= 0;
                });
        } else if (_chart.hasFilter()) {
            _select.property('value', _chart.filter());
        } else {
            _select.property('value', '');
        }
        return _chart;
    };

    function renderOptions () {
        var options = _select.selectAll('option.' + OPTION_CSS_CLASS)
          .data(_chart.data(), function (d) { return _chart.keyAccessor()(d); });

        options.enter()
              .append('option')
              .classed(OPTION_CSS_CLASS, true)
              .attr('value', function (d) { return _chart.keyAccessor()(d); });

        options.text(_chart.title());
        options.exit().remove();
        _select.selectAll('option.' + OPTION_CSS_CLASS).sort(_order);

        _select.on('change', onChange);
        return options;
    }

    function onChange (d, i) {
        var values;
        var target = d3.event.target;
        if (target.selectedOptions) {
            var selectedOptions = Array.prototype.slice.call(target.selectedOptions);
            values = selectedOptions.map(function (d) {
                return d.value;
            });
        } else { // IE and other browsers do not support selectedOptions
            // adapted from this polyfill: https://gist.github.com/brettz9/4212217
            var options = [].slice.call(d3.event.target.options);
            values = options.filter(function (option) {
                return option.selected;
            }).map(function (option) {
                return option.value;
            });
        }
        // console.log(values);
        // check if only prompt option is selected
        if (values.length === 1 && values[0] === '') {
            values = null;
        } else if (!_multiple && values.length === 1) {
            values = values[0];
        }
        _chart.onChange(values);
    }

    _chart.onChange = function (val) {
        if (val && _multiple) {
            _chart.replaceFilter([val]);
        } else if (val) {
            _chart.replaceFilter(val);
        } else {
            _chart.filterAll();
        }
        dc.events.trigger(function () {
            _chart.redrawGroup();
        });
    };

    function setAttributes () {
        if (_multiple) {
            _select.attr('multiple', true);
        } else {
            _select.attr('multiple', null);
        }
        if (_size !== null) {
            _select.attr('size', _size);
        } else {
            _select.attr('size', null);
        }
    }

    /**
     * Get or set the function that controls the ordering of option tags in the
     * select menu. By default options are ordered by the group key in ascending
     * order.
     * @name order
     * @memberof dc.selectMenu
     * @instance
     * @param {Function} [order]
     * @example
     * // order by the group's value
     * chart.order(function (a,b) {
     *     return a.value > b.value ? 1 : b.value > a.value ? -1 : 0;
     * });
     **/
    _chart.order = function (order) {
        if (!arguments.length) {
            return _order;
        }
        _order = order;
        return _chart;
    };

    /**
     * Get or set the text displayed in the options used to prompt selection.
     * @name promptText
     * @memberof dc.selectMenu
     * @instance
     * @param {String} [promptText='Select all']
     * @example
     * chart.promptText('All states');
     **/
    _chart.promptText = function (_) {
        if (!arguments.length) {
            return _promptText;
        }
        _promptText = _;
        return _chart;
    };

    /**
     * Get or set the function that filters option tags prior to display. By default options
     * with a value of < 1 are not displayed.
     * @name filterDisplayed
     * @memberof dc.selectMenu
     * @instance
     * @param {function} [filterDisplayed]
     * @example
     * // display all options override the `filterDisplayed` function:
     * chart.filterDisplayed(function () {
     *     return true;
     * });
     **/
    _chart.filterDisplayed = function (filterDisplayed) {
        if (!arguments.length) {
            return _filterDisplayed;
        }
        _filterDisplayed = filterDisplayed;
        return _chart;
    };

    /**
     * Controls the type of select menu. Setting it to true converts the underlying
     * HTML tag into a multiple select.
     * @name multiple
     * @memberof dc.selectMenu
     * @instance
     * @param {boolean} [multiple=false]
     * @example
     * chart.multiple(true);
     **/
    _chart.multiple = function (multiple) {
        if (!arguments.length) {
            return _multiple;
        }
        _multiple = multiple;

        return _chart;
    };

    /**
     * Controls the height, in lines, of the select menu, when `.multiple()` is true. If `null` (the default),
     * uses the browser's default height.
     * @name size
     * @memberof dc.selectMenu
     * @instance
     * @param {?number} [size
     * @example
     * chart.size(10);
     **/
    _chart.size = function (size) {
        if (!arguments.length) {
            return _size;
        }
        _size = size;

        return _chart;
    };

    return _chart.anchor(parent, chartGroup);
};
<commit_msg>selectMenu: promptValue; size->numberVisible<commit_after>/**
 * The select menu is a simple widget designed to filter a dimension by selecting an option from
 * an HTML `<select/>` menu. The menu can be optionally turned into a multiselect.
 * @class selectMenu
 * @memberof dc
 * @mixes dc.baseMixin
 * @example
 * // create a select menu under #select-container using the default global chart group
 * var select = dc.selectMenu('#select-container')
 *                .dimension(states)
 *                .group(stateGroup);
 * // the option text can be set via the title() function
 * // by default the option text is '`key`: `value`'
 * select.title(function (d){
 *     return 'STATE: ' + d.key;
 * })
 * @param {String|node|d3.selection|dc.compositeChart} parent - Any valid
 * [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying
 * a dom block element such as a div; or a dom element or d3 selection.
 * @param {String} [chartGroup] - The name of the chart group this widget should be placed in.
 * Interaction with the widget will only trigger events and redraws within its group.
 * @returns {selectMenu}
 **/
dc.selectMenu = function (parent, chartGroup) {
    var SELECT_CSS_CLASS = 'dc-select-menu';
    var OPTION_CSS_CLASS = 'dc-select-option';

    var _chart = dc.baseMixin({});

    var _select;
    var _promptText = 'Select all';
    var _multiple = false;
    var _promptValue = null;
    var _numberVisible = null;
    var _order = function (a, b) {
        return _chart.keyAccessor()(a) > _chart.keyAccessor()(b) ?
             1 : _chart.keyAccessor()(b) > _chart.keyAccessor()(a) ?
            -1 : 0;
    };

    var _filterDisplayed = function (d) {
        return _chart.valueAccessor()(d) > 0;
    };

    _chart.data(function (group) {
        return group.all().filter(_filterDisplayed);
    });

    _chart._doRender = function () {
        _chart.select('select').remove();
        _select = _chart.root().append('select')
                        .classed(SELECT_CSS_CLASS, true);
        _select.append('option').text(_promptText).attr('value', '');

        _chart._doRedraw();
        return _chart;
    };

    _chart._doRedraw = function () {
        setAttributes();
        renderOptions();
        // select the option(s) corresponding to current filter(s)
        if (_chart.hasFilter() && _multiple) {
            _select.selectAll('option')
                .property('selected', function (d) {
                    return d && _chart.filters().indexOf(String(_chart.keyAccessor()(d))) >= 0;
                });
        } else if (_chart.hasFilter()) {
            _select.property('value', _chart.filter());
        } else {
            _select.property('value', '');
        }
        return _chart;
    };

    function renderOptions () {
        var options = _select.selectAll('option.' + OPTION_CSS_CLASS)
          .data(_chart.data(), function (d) { return _chart.keyAccessor()(d); });

        options.enter()
              .append('option')
              .classed(OPTION_CSS_CLASS, true)
              .attr('value', function (d) { return _chart.keyAccessor()(d); });

        options.text(_chart.title());
        options.exit().remove();
        _select.selectAll('option.' + OPTION_CSS_CLASS).sort(_order);

        _select.on('change', onChange);
        return options;
    }

    function onChange (d, i) {
        var values;
        var target = d3.event.target;
        if (target.selectedOptions) {
            var selectedOptions = Array.prototype.slice.call(target.selectedOptions);
            values = selectedOptions.map(function (d) {
                return d.value;
            });
        } else { // IE and other browsers do not support selectedOptions
            // adapted from this polyfill: https://gist.github.com/brettz9/4212217
            var options = [].slice.call(d3.event.target.options);
            values = options.filter(function (option) {
                return option.selected;
            }).map(function (option) {
                return option.value;
            });
        }
        // console.log(values);
        // check if only prompt option is selected
        if (values.length === 1 && values[0] === '') {
            values = _promptValue || null;
        } else if (!_multiple && values.length === 1) {
            values = values[0];
        }
        _chart.onChange(values);
    }

    _chart.onChange = function (val) {
        if (val && _multiple) {
            _chart.replaceFilter([val]);
        } else if (val) {
            _chart.replaceFilter(val);
        } else {
            _chart.filterAll();
        }
        dc.events.trigger(function () {
            _chart.redrawGroup();
        });
    };

    function setAttributes () {
        if (_multiple) {
            _select.attr('multiple', true);
        } else {
            _select.attr('multiple', null);
        }
        if (_numberVisible !== null) {
            _select.attr('size', _numberVisible);
        } else {
            _select.attr('size', null);
        }
    }

    /**
     * Get or set the function that controls the ordering of option tags in the
     * select menu. By default options are ordered by the group key in ascending
     * order.
     * @name order
     * @memberof dc.selectMenu
     * @instance
     * @param {Function} [order]
     * @example
     * // order by the group's value
     * chart.order(function (a,b) {
     *     return a.value > b.value ? 1 : b.value > a.value ? -1 : 0;
     * });
     **/
    _chart.order = function (order) {
        if (!arguments.length) {
            return _order;
        }
        _order = order;
        return _chart;
    };

    /**
     * Get or set the text displayed in the options used to prompt selection.
     * @name promptText
     * @memberof dc.selectMenu
     * @instance
     * @param {String} [promptText='Select all']
     * @example
     * chart.promptText('All states');
     **/
    _chart.promptText = function (_) {
        if (!arguments.length) {
            return _promptText;
        }
        _promptText = _;
        return _chart;
    };

    /**
     * Get or set the function that filters option tags prior to display. By default options
     * with a value of < 1 are not displayed.
     * @name filterDisplayed
     * @memberof dc.selectMenu
     * @instance
     * @param {function} [filterDisplayed]
     * @example
     * // display all options override the `filterDisplayed` function:
     * chart.filterDisplayed(function () {
     *     return true;
     * });
     **/
    _chart.filterDisplayed = function (filterDisplayed) {
        if (!arguments.length) {
            return _filterDisplayed;
        }
        _filterDisplayed = filterDisplayed;
        return _chart;
    };

    /**
     * Controls the type of select menu. Setting it to true converts the underlying
     * HTML tag into a multiple select.
     * @name multiple
     * @memberof dc.selectMenu
     * @instance
     * @param {boolean} [multiple=false]
     * @example
     * chart.multiple(true);
     **/
    _chart.multiple = function (multiple) {
        if (!arguments.length) {
            return _multiple;
        }
        _multiple = multiple;

        return _chart;
    };

    /**
     * Controls the default value when only the prompt value is selected. If `null`, no filtering
     * will occur when just the prompt is selected.
     * @name promptValue
     * @memberof dc.selectMenu
     * @instance
     * @param {*} [promptValue=null]
     **/
    _chart.promptValue = function (promptValue) {
        if (!arguments.length) {
            return _promptValue;
        }
        _promptValue = promptValue;

        return _chart;
    };

    /**
     * Controls the number of items to show in the select menu, when `.multiple()` is true. This
     * controls the [`size` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#Attributes) of
     * the `select` element. If `null` (the default), uses the browser's default height.
     * @name numberItems
     * @memberof dc.selectMenu
     * @instance
     * @param {?number} [numberVisible=null]
     * @example
     * chart.numberVisible(10);
     **/
    _chart.numberVisible = function (numberVisible) {
        if (!arguments.length) {
            return _numberVisible;
        }
        _numberVisible = numberVisible;

        return _chart;
    };

    _chart.size = dc.logger.deprecate(_chart.numberVisible, 'selectMenu.size is ambiguous - use numberVisible instead');

    return _chart.anchor(parent, chartGroup);
};
<|endoftext|>


<commit_before>/*
 * Copyright 2012 The Polymer Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

/*
  This is a limited shim for shadowDOM css styling.
  https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles

  The intention here is to support only the styling features which can be
  relatively simply implemented. The goal is to allow users to avoid the
  most obvious pitfalls and do so without compromising performance significantly.
  For shadowDOM styling that's not covered here, a set of best practices
  can be provided that should allow users to accomplish more complex styling.

  The following is a list of specific shadowDOM styling features and a brief
  discussion of the approach used to shim.

  Shimmed features:

  * @host: ShadowDOM allows styling of the shadowRoot's host element using the
  @host rule. To shim this feature, the @host styles are reformatted and
  prefixed with a given scope name and promoted to a document level stylesheet.
  For example, given a scope name of .foo, a rule like this:

    @host {
      * {
        background: red;
      }
    }

  becomes:

    .foo {
      background: red;
    }

  * encapsultion: Styles defined within shadowDOM, apply only to
  dom inside the shadowDOM. To shim this feature, non-@host rules within
  style elements are scoped by adding an attribute selector suffix to each
  simple selector that contains the scope name. Each element in the definition
  template content is also given the scope attribute. Thus, these rules match
  only elements that have the scope attribute.
  For example, given a scope name of x-foo, a rule like this:

    div {
      font-weight: bold;
    }

  becomes:

    div[x-foo] {
      font-weight: bold;
    }

  Note that elements that are dynamically added to a scope must have the scope
  selector added to them manually.

  * ::pseudo: These rules are converted to rules that take advantage of the
  pseudo attribute. For example, a shadowRoot like this inside an x-foo

Special

  with a rule like this:

    x-foo::x-special { ... }

  becomes:

    x-foo [pseudo=x-special] { ... }

  Unaddressed shadowDOM styling features:

  * upper/lower bound encapsulation: Styles which are defined outside a
  shadowRoot should not cross the shadowDOM boundary and should not apply
  inside a shadowRoot.

  This styling behavior is not emulated. Some possible ways to do this that
  were rejected due to complexity and/or performance concerns include: (1) reset
  every possible property for every possible selector for a given scope name;
  (2) re-implement css in javascript.

  As an alternative, users should make sure to use selectors
  specific to the scope in which they are working.

  * ::distributed: This behavior is not emulated. It's often not necessary
  to style the contents of a specific insertion point and instead, descendants
  of the host element can be styled selectively. Users can also create an
  extra node around an insertion point and style that node's contents
  via descendent selectors. For example, with a shadowRoot like this:

    <content></content>

  could become:

      <content></content>

  Note the use of @polyfill in the comment above a shadowDOM specific style
  declaration. This is a directive to the styling shim to use the selector
  in comments in lieu of the next selector when running under polyfill.
*/
(function(scope) {

var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
var concat = Array.prototype.concat.call.bind(Array.prototype.concat);
var slice = Array.prototype.slice.call.bind(Array.prototype.slice);

var stylizer = {
  hostRuleRe: /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
  selectorRe: /([^{]*)({[\s\S]*?})/gim,
  hostElementRe: /(\*)|(\:scope)/,
  hostFixableRe: /^[.\[:]/,
  cssCommentRe: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
  cssPolyfillCommentRe: /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
  cssPseudoRe: /::(x-[^\s{,(]*)/gim,
  selectorReSuffix: '([>\\s~+\[.,{:][\\s\\S]*)?$',
  hostRe: /@host/gim,
  cache: {},
  shimStyling: function(element) {
    if (window.ShadowDOMPolyfill && element) {
      // use caching to make working with styles nodes easier and to facilitate
      // lookup of extendee
      var name = element.options.name;
      stylizer.cacheDefinition(element);
      stylizer.applyScopeToContent(element.templateContent, name);
      stylizer.shimPolyfillDirectives(element.styles, name);
      // find styles and apply shimming...
      stylizer.applyShimming(stylizer.stylesForElement(element), name);
    }
  },
  // Shim styles to be placed inside a shadowRoot.
  // 1. shim @host rules and inherited @host rules
  // 2. shim scoping: apply .scoped when available or pseudo-scoping when not
  // (e.g. a selector 'div' becomes 'x-foo div')
  shimShadowDOMStyling: function(styles, name) {
    if (window.ShadowDOMPolyfill) {
      stylizer.shimPolyfillDirectives(styles, name);
      stylizer.applyShimming(styles, name);
    }
  },
  applyShimming: function(styles, name) {
    var cssText = this.shimAtHost(styles, name);
    cssText += this.shimScoping(styles, name);
    this.addCssToDocument(cssText);
  },
  //TODO(sorvell): use SideTable
  cacheDefinition: function(element) {
    var name = element.options.name;
    var template = element.querySelector('template');
    var content = template && templateContent(template);
    var styles = content && content.querySelectorAll('style');
    element.styles = styles ? slice(styles) : [];
    element.templateContent = content;
    stylizer.cache[name] = element;
  },
  applyScopeToContent: function(root, name) {
    if (root) {
      forEach(root.querySelectorAll('*'), function(node) {
        node.setAttribute(name, '');
      });
      forEach(root.querySelectorAll('template'), function(template) {
        this.applyScopeToContent(templateContent(template), name);
      }, this);
    }
  },
  stylesForElement: function(element) {
    var styles = element.styles;
    var shadow = element.templateContent &&
      element.templateContent.querySelector('shadow');
    if (shadow || (element.templateContent === null)) {
      var extendee = this.findExtendee(element.options.name);
      if (extendee) {
        var extendeeStyles = this.stylesForElement(extendee);
        styles = concat(slice(extendeeStyles), slice(styles));
      }
    }
    return styles;
  },
  findExtendee: function(name) {
    var element = this.cache[name];
    return element && this.cache[element.options.extends];
  },
  /*
   * Process styles to convert native ShadowDOM rules that will trip
   * up the css parser; we rely on decorating the stylesheet with comments.
   *
   * For example, we convert this rule:
   *
   * (comment start) @polyfill @host g-menu-item (comment end)
   * shadow::-webkit-distributed(g-menu-item) {
   *
   * to this:
   *
   * scopeName g-menu-item {
   *
  **/
  shimPolyfillDirectives: function(styles, name) {
    if (window.ShadowDOMPolyfill) {
      if (styles) {
        forEach(styles, function(s) {
          s.textContent = this.convertPolyfillDirectives(s.textContent, name);
        }, this);
      }
    }
  },
  // form: @host { .foo { declarations } }
  // becomes: scopeName.foo { declarations }
  shimAtHost: function(styles, name) {
    if (styles) {
      return this.convertAtHostStyles(styles, name);
    }
  },
  /* Ensure styles are scoped. Pseudo-scoping takes a rule like:
   *
   *  .foo {... }
   *
   *  and converts this to
   *
   *  scopeName .foo { ... }
  */
  shimScoping: function(styles, name) {
    if (styles) {
      return this.convertScopedStyles(styles, name);
    }
  },
  convertPolyfillDirectives: function(cssText, name) {
    var r = '', l = 0, matches;
    while (matches=this.cssPolyfillCommentRe.exec(cssText)) {
      r += cssText.substring(l, matches.index);
      // remove end comment delimiter (*/)
      r += name + ' ' + matches[1].slice(0, -2) + '{';
      l = this.cssPolyfillCommentRe.lastIndex;
    }
    r += cssText.substring(l, cssText.length);
    return r;
  },
  // consider styles that do not include component name in the selector to be
  // unscoped and in need of promotion;
  // for convenience, also consider keyframe rules this way.
  findAtHostRules: function(cssRules, matcher) {
    return Array.prototype.filter.call(cssRules,
      this.isHostRule.bind(this, matcher));
  },
  isHostRule: function(matcher, cssRule) {
    return (cssRule.selectorText && cssRule.selectorText.match(matcher)) ||
      (cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).length) ||
      (cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE);
  },
  convertAtHostStyles: function(styles, name) {
    var cssText = this.stylesToCssText(styles);
    var r = '', l=0, matches;
    while (matches=this.hostRuleRe.exec(cssText)) {
      r += cssText.substring(l, matches.index);
      r += this.scopeHostCss(matches[1], name);
      l = this.hostRuleRe.lastIndex;
    }
    r += cssText.substring(l, cssText.length);
    var selectorRe = new RegExp('^' + name + this.selectorReSuffix, 'm');
    var cssText = this.rulesToCss(this.findAtHostRules(this.cssToRules(r),
      selectorRe));
    return cssText;
  },
  scopeHostCss: function(cssText, name) {
    var r = '', matches;
    while (matches = this.selectorRe.exec(cssText)) {
      r += this.scopeHostSelector(matches[1], name) +' ' + matches[2] + '\n\t';
    }
    return r;
  },
  scopeHostSelector: function(selector, name) {
    var r = [], parts = selector.split(',');
    parts.forEach(function(p) {
      p = p.trim();
      // selector: *|:scope -> name
      if (p.match(this.hostElementRe)) {
        p = p.replace(this.hostElementRe, name);
      // selector: .foo -> name.foo, [bar] -> name[bar]
      } else if (p.match(this.hostFixableRe)) {
        p = name + p;
      }
      r.push(p);
    }, this);
    return r.join(', ');
  },
  convertScopedStyles: function(styles, name) {
    forEach(styles, function(s) {
      if (s.parentNode) {
        s.parentNode.removeChild(s);
      }
    });
    // TODO(sorvell): remove @host rules (use cssom rather than regex?)
    var cssText = this.stylesToCssText(styles).replace(this.hostRuleRe, '');
    var rules = this.cssToRules(cssText);
    cssText = this.scopeRules(rules, name);
    return this.convertPseudos(cssText);
  },
  convertPseudos: function(cssText) {
    return cssText.replace(this.cssPseudoRe, ' [pseudo=$1]');
  },
  // change a selector like 'div' to 'name div'
  scopeRules: function(cssRules, name) {
    var cssText = '';
    forEach(cssRules, function(rule) {
      if (rule.selectorText && (rule.style && rule.style.cssText)) {
        cssText += this.scopeSelector(rule.selectorText, name) + ' {\n\t';
        cssText += rule.style.cssText + '\n}\n\n';
      } else if (rule.media) {
        cssText += '@media ' + rule.media.mediaText + ' {\n';
        cssText += this.scopeRules(rule.cssRules, name);
        cssText += '\n}\n\n';
      } else if (rule.cssText) {
        cssText += rule.cssText + '\n\n';
      }
    }, this);
    return cssText;
  },
  scopeSelector: function(selector, name) {
    var r = [], parts = selector.split(','), t;
    var selectorRe = new RegExp('^' + name + this.selectorReSuffix, 'm');
    parts.forEach(function(p) {
      t = p.trim();
      if (t.match(selectorRe)) {
        r.push(t);
      } else {
        r.push(this.scopeCompoundSelector(t, name));
      }
    }, this);
    return r.join(', ');
  },
  // return a selector with [name] suffix on each simple selector
  // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
  scopeCompoundSelector: function(selector, name) {
    // TODO(sorvell): process complete set of complex selector markers
    var splits = [' ', '.', '>'],
      scoped = selector,
      attrName = '[' + name + ']';
    splits.forEach(function(sep) {
      var parts = scoped.split(sep);
      scoped = parts.map(function(p) {
        var t = p.trim();
        if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
          p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
        }
        return p;
      }).join(sep);
    });
    return scoped;
  },
  stylesToCssText: function(styles, preserveComments) {
    var cssText = '';
    forEach(styles, function(s) {
      cssText += s.textContent + '\n\n';
    });
    // strip comments for easier processing
    if (!preserveComments) {
      cssText = this.stripCssComments(cssText);
    }
    return cssText;
  },
  stripCssComments: function(cssText) {
    return cssText.replace(this.cssCommentRe, '');
  },
  cssToRules: function(cssText) {
    var style = document.createElement('style');
    style.textContent = cssText;
    document.head.appendChild(style);
    var rules = style.sheet.cssRules;
    style.parentNode.removeChild(style);
    return rules;
  },
  rulesToCss: function(cssRules) {
    for (var i=0, css=[]; i < cssRules.length; i++) {
      css.push(cssRules[i].cssText);
    }
    return css.join('\n\n');
  },
  addCssToDocument: function(cssText) {
    if (cssText) {
      this.getSheet().appendChild(document.createTextNode(cssText));
    }
  },
  // support for creating @host rules
  getSheet: function() {
    if (!this.sheet) {
      this.sheet = document.createElement("style");
      this.sheet.setAttribute('polymer-polyfill', '');
    }
    return this.sheet;
  },
  addSheetToDocument: function() {
    this.addCssToDocument('style { display: none !important; }\n');
    var head = document.querySelector('head');
    head.insertBefore(this.getSheet(), head.childNodes[0]);
  }
};

// add polyfill stylesheet to document
if (window.ShadowDOMPolyfill) {
  stylizer.addSheetToDocument();
}

// exports
Polymer.shimStyling = stylizer.shimStyling;
Polymer.shimShadowDOMStyling = stylizer.shimShadowDOMStyling;
Polymer.shimPolyfillDirectives = stylizer.shimPolyfillDirectives.bind(stylizer);

})(window);<commit_msg>make strict polyfill styling optional via Polymer.strictPolyfillStyling flag.<commit_after>/*
 * Copyright 2012 The Polymer Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

/*
  This is a limited shim for shadowDOM css styling.
  https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles

  The intention here is to support only the styling features which can be
  relatively simply implemented. The goal is to allow users to avoid the
  most obvious pitfalls and do so without compromising performance significantly.
  For shadowDOM styling that's not covered here, a set of best practices
  can be provided that should allow users to accomplish more complex styling.

  The following is a list of specific shadowDOM styling features and a brief
  discussion of the approach used to shim.

  Shimmed features:

  * @host: ShadowDOM allows styling of the shadowRoot's host element using the
  @host rule. To shim this feature, the @host styles are reformatted and
  prefixed with a given scope name and promoted to a document level stylesheet.
  For example, given a scope name of .foo, a rule like this:

    @host {
      * {
        background: red;
      }
    }

  becomes:

    .foo {
      background: red;
    }

  * encapsultion: Styles defined within shadowDOM, apply only to
  dom inside the shadowDOM. Polymer uses one of two techniques to imlement
  this feature.

  By default, rules are prefixed with the host element tag name
  as a descendant selector. This ensures styling does not leak out of the 'top'
  of the element's shadowDOM. For example,

  div {
      font-weight: bold;
    }

  becomes:

  x-foo div {
      font-weight: bold;
    }

  becomes:

  Alternatively, if Polymer.strictPolyfillStyling is set to true then
  selectors are scoped by adding an attribute selector suffix to each
  simple selector that contains the host element tag name. Each element
  in the element's shadowDOM template is also given the scope attribute.
  Thus, these rules match only elements that have the scope attribute.
  For example, given a scope name of x-foo, a rule like this:

    div {
      font-weight: bold;
    }

  becomes:

    div[x-foo] {
      font-weight: bold;
    }

  Note that elements that are dynamically added to a scope must have the scope
  selector added to them manually.

  * ::pseudo: These rules are converted to rules that take advantage of the
  pseudo attribute. For example, a shadowRoot like this inside an x-foo

Special

  with a rule like this:

    x-foo::x-special { ... }

  becomes:

    x-foo [pseudo=x-special] { ... }

  Unaddressed shadowDOM styling features:

  * upper/lower bound encapsulation: Styles which are defined outside a
  shadowRoot should not cross the shadowDOM boundary and should not apply
  inside a shadowRoot.

  This styling behavior is not emulated. Some possible ways to do this that
  were rejected due to complexity and/or performance concerns include: (1) reset
  every possible property for every possible selector for a given scope name;
  (2) re-implement css in javascript.

  As an alternative, users should make sure to use selectors
  specific to the scope in which they are working.

  * ::distributed: This behavior is not emulated. It's often not necessary
  to style the contents of a specific insertion point and instead, descendants
  of the host element can be styled selectively. Users can also create an
  extra node around an insertion point and style that node's contents
  via descendent selectors. For example, with a shadowRoot like this:

    <content></content>

  could become:

      <content></content>

  Note the use of @polyfill in the comment above a shadowDOM specific style
  declaration. This is a directive to the styling shim to use the selector
  in comments in lieu of the next selector when running under polyfill.
*/
(function(scope) {

var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
var concat = Array.prototype.concat.call.bind(Array.prototype.concat);
var slice = Array.prototype.slice.call.bind(Array.prototype.slice);

var stylizer = {
  hostRuleRe: /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
  selectorRe: /([^{]*)({[\s\S]*?})/gim,
  hostElementRe: /(\*)|(\:scope)/,
  hostFixableRe: /^[.\[:]/,
  cssCommentRe: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
  cssPolyfillCommentRe: /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
  cssPseudoRe: /::(x-[^\s{,(]*)/gim,
  selectorReSuffix: '([>\\s~+\[.,{:][\\s\\S]*)?$',
  hostRe: /@host/gim,
  cache: {},
  shimStyling: function(element) {
    if (window.ShadowDOMPolyfill && element) {
      // use caching to make working with styles nodes easier and to facilitate
      // lookup of extendee
      var name = element.options.name;
      stylizer.cacheDefinition(element);
      stylizer.shimPolyfillDirectives(element.styles, name);
      // find styles and apply shimming...
      if (Polymer.strictPolyfillStyling) {
        stylizer.applyScopeToContent(element.templateContent, name);
      }
      stylizer.applyShimming(stylizer.stylesForElement(element), name);
    }
  },
  // Shim styles to be placed inside a shadowRoot.
  // 1. shim @host rules and inherited @host rules
  // 2. shim scoping: apply .scoped when available or pseudo-scoping when not
  // (e.g. a selector 'div' becomes 'x-foo div')
  shimShadowDOMStyling: function(styles, name) {
    if (window.ShadowDOMPolyfill) {
      stylizer.shimPolyfillDirectives(styles, name);
      stylizer.applyShimming(styles, name);
    }
  },
  applyShimming: function(styles, name) {
    var cssText = this.shimAtHost(styles, name);
    cssText += this.shimScoping(styles, name);
    this.addCssToDocument(cssText);
  },
  cacheDefinition: function(element) {
    var name = element.options.name;
    var template = element.querySelector('template');
    var content = template && templateContent(template);
    var styles = content && content.querySelectorAll('style');
    element.styles = styles ? slice(styles) : [];
    element.templateContent = content;
    stylizer.cache[name] = element;
  },
  applyScopeToContent: function(root, name) {
    if (root) {
      forEach(root.querySelectorAll('*'), function(node) {
        node.setAttribute(name, '');
      });
      forEach(root.querySelectorAll('template'), function(template) {
        this.applyScopeToContent(templateContent(template), name);
      }, this);
    }
  },
  stylesForElement: function(element) {
    var styles = element.styles;
    var shadow = element.templateContent &&
      element.templateContent.querySelector('shadow');
    if (shadow || (element.templateContent === null)) {
      var extendee = this.findExtendee(element.options.name);
      if (extendee) {
        var extendeeStyles = this.stylesForElement(extendee);
        styles = concat(slice(extendeeStyles), slice(styles));
      }
    }
    return styles;
  },
  findExtendee: function(name) {
    var element = this.cache[name];
    return element && this.cache[element.options.extends];
  },
  /*
   * Process styles to convert native ShadowDOM rules that will trip
   * up the css parser; we rely on decorating the stylesheet with comments.
   *
   * For example, we convert this rule:
   *
   * (comment start) @polyfill @host g-menu-item (comment end)
   * shadow::-webkit-distributed(g-menu-item) {
   *
   * to this:
   *
   * scopeName g-menu-item {
   *
  **/
  shimPolyfillDirectives: function(styles, name) {
    if (window.ShadowDOMPolyfill) {
      if (styles) {
        forEach(styles, function(s) {
          s.textContent = this.convertPolyfillDirectives(s.textContent, name);
        }, this);
      }
    }
  },
  // form: @host { .foo { declarations } }
  // becomes: scopeName.foo { declarations }
  shimAtHost: function(styles, name) {
    if (styles) {
      return this.convertAtHostStyles(styles, name);
    }
  },
  /* Ensure styles are scoped. Pseudo-scoping takes a rule like:
   *
   *  .foo {... }
   *
   *  and converts this to
   *
   *  scopeName .foo { ... }
  */
  shimScoping: function(styles, name) {
    if (styles) {
      return this.convertScopedStyles(styles, name);
    }
  },
  convertPolyfillDirectives: function(cssText, name) {
    var r = '', l = 0, matches;
    while (matches=this.cssPolyfillCommentRe.exec(cssText)) {
      r += cssText.substring(l, matches.index);
      // remove end comment delimiter (*/)
      r += name + ' ' + matches[1].slice(0, -2) + '{';
      l = this.cssPolyfillCommentRe.lastIndex;
    }
    r += cssText.substring(l, cssText.length);
    return r;
  },
  // consider styles that do not include component name in the selector to be
  // unscoped and in need of promotion;
  // for convenience, also consider keyframe rules this way.
  findAtHostRules: function(cssRules, matcher) {
    return Array.prototype.filter.call(cssRules,
      this.isHostRule.bind(this, matcher));
  },
  isHostRule: function(matcher, cssRule) {
    return (cssRule.selectorText && cssRule.selectorText.match(matcher)) ||
      (cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).length) ||
      (cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE);
  },
  convertAtHostStyles: function(styles, name) {
    var cssText = this.stylesToCssText(styles);
    var r = '', l=0, matches;
    while (matches=this.hostRuleRe.exec(cssText)) {
      r += cssText.substring(l, matches.index);
      r += this.scopeHostCss(matches[1], name);
      l = this.hostRuleRe.lastIndex;
    }
    r += cssText.substring(l, cssText.length);
    var selectorRe = new RegExp('^' + name + this.selectorReSuffix, 'm');
    var cssText = this.rulesToCss(this.findAtHostRules(this.cssToRules(r),
      selectorRe));
    return cssText;
  },
  scopeHostCss: function(cssText, name) {
    var r = '', matches;
    while (matches = this.selectorRe.exec(cssText)) {
      r += this.scopeHostSelector(matches[1], name) +' ' + matches[2] + '\n\t';
    }
    return r;
  },
  scopeHostSelector: function(selector, name) {
    var r = [], parts = selector.split(',');
    parts.forEach(function(p) {
      p = p.trim();
      // selector: *|:scope -> name
      if (p.match(this.hostElementRe)) {
        p = p.replace(this.hostElementRe, name);
      // selector: .foo -> name.foo, [bar] -> name[bar]
      } else if (p.match(this.hostFixableRe)) {
        p = name + p;
      }
      r.push(p);
    }, this);
    return r.join(', ');
  },
  convertScopedStyles: function(styles, name) {
    forEach(styles, function(s) {
      if (s.parentNode) {
        s.parentNode.removeChild(s);
      }
    });
    // TODO(sorvell): remove @host rules (use cssom rather than regex?)
    var cssText = this.stylesToCssText(styles).replace(this.hostRuleRe, '');
    var rules = this.cssToRules(cssText);
    cssText = this.scopeRules(rules, name);
    return this.convertPseudos(cssText);
  },
  convertPseudos: function(cssText) {
    return cssText.replace(this.cssPseudoRe, ' [pseudo=$1]');
  },
  // change a selector like 'div' to 'name div'
  scopeRules: function(cssRules, name) {
    var cssText = '';
    var scopeFn = Polymer.strictPolyfillStyling ? this.scopeSelectorStrict
      : this.scopeSelector;
    forEach(cssRules, function(rule) {
      if (rule.selectorText && (rule.style && rule.style.cssText)) {
        cssText += scopeFn.call(this, rule.selectorText, name) + ' {\n\t';
        cssText += rule.style.cssText + '\n}\n\n';
      } else if (rule.media) {
        cssText += '@media ' + rule.media.mediaText + ' {\n';
        cssText += this.scopeRules(rule.cssRules, name);
        cssText += '\n}\n\n';
      } else if (rule.cssText) {
        cssText += rule.cssText + '\n\n';
      }
    }, this);
    return cssText;
  },
  scopeSelector: function(selector, name) {
    var r = [], parts = selector.split(',');
    parts.forEach(function(p) {
      r.push(name + ' ' + p.trim());
    });
    return r.join(', ');
  },
  scopeSelectorStrict: function(selector, name) {
    var r = [], parts = selector.split(','), t;
    var selectorRe = new RegExp('^' + name + this.selectorReSuffix, 'm');
    parts.forEach(function(p) {
      t = p.trim();
      if (t.match(selectorRe)) {
        r.push(t);
      } else {
        r.push(this.scopeCompoundSelector(t, name));
      }
    }, this);
    return r.join(', ');
  },
  // return a selector with [name] suffix on each simple selector
  // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
  scopeCompoundSelector: function(selector, name) {
    var splits = [' ', '>', '+', '~'],
      scoped = selector,
      attrName = '[' + name + ']';
    splits.forEach(function(sep) {
      var parts = scoped.split(sep);
      scoped = parts.map(function(p) {
        var t = p.trim();
        if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
          p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
        }
        return p;
      }).join(sep);
    });
    return scoped;
  },
  stylesToCssText: function(styles, preserveComments) {
    var cssText = '';
    forEach(styles, function(s) {
      cssText += s.textContent + '\n\n';
    });
    // strip comments for easier processing
    if (!preserveComments) {
      cssText = this.stripCssComments(cssText);
    }
    return cssText;
  },
  stripCssComments: function(cssText) {
    return cssText.replace(this.cssCommentRe, '');
  },
  cssToRules: function(cssText) {
    var style = document.createElement('style');
    style.textContent = cssText;
    document.head.appendChild(style);
    var rules = style.sheet.cssRules;
    style.parentNode.removeChild(style);
    return rules;
  },
  rulesToCss: function(cssRules) {
    for (var i=0, css=[]; i < cssRules.length; i++) {
      css.push(cssRules[i].cssText);
    }
    return css.join('\n\n');
  },
  addCssToDocument: function(cssText) {
    if (cssText) {
      this.getSheet().appendChild(document.createTextNode(cssText));
    }
  },
  // support for creating @host rules
  getSheet: function() {
    if (!this.sheet) {
      this.sheet = document.createElement("style");
      this.sheet.setAttribute('polymer-polyfill', '');
    }
    return this.sheet;
  },
  addSheetToDocument: function() {
    this.addCssToDocument('style { display: none !important; }\n');
    var head = document.querySelector('head');
    head.insertBefore(this.getSheet(), head.childNodes[0]);
  }
};

// add polyfill stylesheet to document
if (window.ShadowDOMPolyfill) {
  stylizer.addSheetToDocument();
}

// exports
Polymer.shimStyling = stylizer.shimStyling;
Polymer.shimShadowDOMStyling = stylizer.shimShadowDOMStyling;
Polymer.shimPolyfillDirectives = stylizer.shimPolyfillDirectives.bind(stylizer);
Polymer.strictPolyfillStyling = false;

})(window);<|endoftext|>


<commit_before>import BaseLfo from '../core/BaseLfo';
import parameters from 'parameters';

const definitions = {
  frameTime: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
  frameData: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
  frameMetadata: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
  streamParams: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
}

/**
 * Utility sink to log the `frameTime`, `frameData`, `frameMetadata` and/or
 * the `streamAttributes` in any.
 *
 * @param {Object} options - Override parameters' default values.
 * @param {Boolean} [options.frameTime=false] - If set to true, log all
 *  incomming `frameTime`
 * @param {Boolean} [options.frameData=false] - If set to true, log all
 *  incomming `frameData`
 * @param {Boolean} [options.frameMetadata=false] - If set to true, log all
 *  incomming `frameMetadata`
 * @param {Boolean} [options.streamParams=false] - If set to true, log
 *  `streamParams` of previous operator when the graph is started.
 *
 * @todo - should extends LfoSink
 *
 * @example
 * const logger = new Logger({ frameTime: true });
 * whateverOperator.connect(logger);
 */
class Logger extends BaseLfo {
  constructor(options) {
    super();

    this.params = parameters(definitions, options);
  }

  processStreamParams(prevStreamParams) {
    if (this.params.get('streamParams') === true)
      console.log(prevStreamParams);
  }

  processFunction(frameTime, frameData, frameMetadata) {
    if (this.params.get('frameTime') === true)
      console.log(frameTime);

    if (this.params.get('frameData') === true)
      console.log(frameData);

    if (this.params.get('frameMetadata') === true)
      console.log(frameMetadata);
  }
}

export default Logger;
<commit_msg>updated Logger sink<commit_after>import BaseLfo from '../core/BaseLfo';
import parameters from 'parameters';

const definitions = {
  time: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
  data: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
  metadata: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
  streamParams: {
    type: 'boolean',
    default: false,
    metas: { kind: 'dynamic' }
  },
}

/**
 * Utility sink to log `frame.time`, `frame.data`, `frame.metadata` and/or
 * `streamAttributes` of any node.
 *
 * @param {Object} options - Override parameters' default values.
 * @param {Boolean} [options.time=false] - If set to true, log all
 *  incomming `frame.time`
 * @param {Boolean} [options.data=false] - If set to true, log all
 *  incomming `frame.data`
 * @param {Boolean} [options.metadata=false] - If set to true, log all
 *  incomming `frame.metadata`
 * @param {Boolean} [options.streamParams=false] - If set to true, log
 *  `streamParams` of the previous operator when the graph is started.
 *
 * @todo - should extends LfoSink
 *
 * @memberof module:sink
 *
 * @example
 * const logger = new Logger({ data: true });
 * whateverOperator.connect(logger);
 */
class Logger extends BaseLfo {
  constructor(options) {
    super();

    this.params = parameters(definitions, options);
  }

  /** @private */
  processStreamParams(prevStreamParams) {
    if (this.params.get('streamParams') === true)
      console.log(prevStreamParams);
  }

  /** @private */
  processFunction(frame) {
    if (this.params.get('time') === true)
      console.log(frame.time);

    if (this.params.get('data') === true)
      console.log(frame.data);

    if (this.params.get('metadata') === true)
      console.log(frame.metadata);
  }
}

export default Logger;
<|endoftext|>


<commit_before>import detectPrefixes from './detect-prefixes.js'

const { slice } = Array.prototype
const prefixes = detectPrefixes()

const LINEAR_ANIMATION = 'linear'
const SLIDES_TO_SCROLL = 1
const VALID_SWIPE_DISTANCE = 25

export function slidy (slider, options) {
  const {
    ease,
    frameDOMEl,
    infinite,
    items,
    rewind,
    rewindSpeed,
    slideSpeed,
    tailArrowClass
  } = options

  // if frameDOMEl is null, then we do nothing
  if (frameDOMEl === null) return

  const {abs, floor, min, max, round} = Math
  const windowDOM = window
  // DOM elements
  const slideContainerDOMEl = frameDOMEl.getElementsByClassName(options.classNameSlideContainer)[0]

  const prevArrow = frameDOMEl.getElementsByClassName(options.classNamePrevCtrl)[0]
  const nextArrow = frameDOMEl.getElementsByClassName(options.classNameNextCtrl)[0]

  // initialize some variables
  let frameWidth = 0
  let index = 0
  let loadedIndex = { 0: 1, 1: 1 }
  let maxOffset = 0
  let position = 0
  let slides = []
  let transitionEndCallback

  // event handling
  let touchOffset = { pageX: 0, pageY: 0 }
  let currentTouchOffset = { pageX: 0, pageY: 0 }
  let delta = { x: 0, y: 0 }
  let isScrolling = false

  // clamp a number between two min and max values
  function _clampNumber (x, minValue, maxValue) {
    return min(max(x, minValue), maxValue)
  }

  // get the width from a DOM element
  function _getWidthFromDOMEl (el) {
    return el.getBoundingClientRect().width
  }

  // calculate the offset with the width of the frame and the desired position
  function _getOffsetLeft (slidePosition) {
    return frameWidth * slidePosition
  }

  /**
   * private
   * _setupInfinite: function to setup if infinite is set
   *
   * @param  {array} slideArray
   * @return {array} array of updated slideContainer elements
   */
  function _setupInfinite (slideArray) {
    const { infinite } = options

    const totalSlides = slideArray.length
    const front = slideArray.slice(0, infinite)
    const back = slideArray.slice(totalSlides - infinite, totalSlides)
    const { firstChild } = slideContainerDOMEl

    front.forEach(function (el) {
      const cloned = el.cloneNode(true)
      slideContainerDOMEl.appendChild(cloned)
    })

    back.reverse()
      .forEach(function (el) {
        const cloned = el.cloneNode(true)
        slideContainerDOMEl.insertBefore(cloned, firstChild)
      })

    return slice.call(slideContainerDOMEl.children)
  }

  /**
   * translates to a given position in a given time in milliseconds
   *
   * @to        {number} number in pixels where to translate to
   * @duration  {number} time in milliseconds for the transistion
   * @ease      {string} easing css property
   */
  function _translate (to, duration, ease) {
    const easeCssText = ease ? `${prefixes.transitionTiming}: ${ease};` : ''
    const durationCssText = duration ? `${prefixes.transitionDuration}: ${duration}ms;` : ''
    const cssText = `${easeCssText}${durationCssText}
      ${prefixes.transform}: ${prefixes.translate(to)};`

    slideContainerDOMEl.style.cssText = cssText
  }

  function _setTailArrowClasses () {
    if (infinite) { return }
    if (prevArrow && prevArrow.classList) {
      index < 1
        ? prevArrow.classList.add(tailArrowClass)
        : prevArrow.classList.remove(tailArrowClass)
    }
    if (nextArrow && nextArrow.classList) {
      index > options.items.length - 2
        ? nextArrow.classList.add(tailArrowClass)
        : nextArrow.classList.remove(tailArrowClass)
    }
  }

  /**
   * slidefunction called by prev, next & touchend
   *
   * determine nextIndex and slide to next postion
   * under restrictions of the defined options
   *
   * @direction  {boolean} 'true' for right, 'false' for left
   */
  function slide (direction) {
    let duration = slideSpeed

    const movement = direction ? 1 : -1
    const totalSlides = slides.length

    // calculate the nextIndex according to the movement
    let nextIndex = index + (SLIDES_TO_SCROLL * movement)

    // nextIndex should be between 0 and totalSlides minus 1
    nextIndex = _clampNumber(nextIndex, 0, totalSlides - 1)

    if (infinite && direction === undefined) {
      nextIndex += infinite
    }

    let nextOffset = _clampNumber(_getOffsetLeft(nextIndex) * -1, maxOffset * -1, 0)

    if (rewind && direction && abs(position) === maxOffset) {
      nextOffset = 0
      nextIndex = 0
      duration = rewindSpeed
    }

    // translate to the nextOffset by a defined duration and ease function
    _translate(nextOffset, duration, ease)

    // update the position with the next position
    position = nextOffset

    // if the nextIndex is possible according to totalSlides, then use it
    if (nextIndex <= totalSlides) {
      index = nextIndex
    }

    if (infinite && (nextIndex === totalSlides - infinite || nextIndex === 0)) {
      index = direction ? infinite : totalSlides - (infinite * 2)

      position = _getOffsetLeft(index) * -1

      transitionEndCallback = function () {
        _translate(_getOffsetLeft(index) * -1, 0)
      }
    } else {
      const indexToLoad = index + (SLIDES_TO_SCROLL * movement)
      const indexLoaded = !!loadedIndex[indexToLoad]
      if (indexToLoad < totalSlides && indexToLoad >= 0 && !indexLoaded) {
        // insert in the correct position
        slideContainerDOMEl.appendChild(slides[indexToLoad])
        loadedIndex[indexToLoad] = 1
      }
    }

    // Checking wheter to paint or hide the arrows.
    _setTailArrowClasses()

    options.doAfterSlide({ currentSlide: index })
  }

  function _removeTouchEventsListeners (all = false) {
    frameDOMEl.removeEventListener('touchmove', onTouchmove)
    frameDOMEl.removeEventListener('touchend', onTouchend)
    frameDOMEl.removeEventListener('touchcancel', onTouchend)
    if (all) {
      frameDOMEl.removeEventListener('touchstart', onTouchstart)
    }
  }

  function _removeAllEventsListeners () {
    _removeTouchEventsListeners(true)
    slideContainerDOMEl.removeEventListener(prefixes.transitionEnd, onTransitionEnd)
    windowDOM.removeEventListener('resize', onResize)
  }

  function onTransitionEnd () {
    if (transitionEndCallback) {
      transitionEndCallback()
      transitionEndCallback = undefined
    }
  }

  function onTouchstart (event) {
    const { pageX, pageY } = event.targetTouches ? event.targetTouches[0] : event
    touchOffset = currentTouchOffset = { pageX, pageY }
    frameDOMEl.addEventListener('touchmove', onTouchmove)
    frameDOMEl.addEventListener('touchend', onTouchend)
    frameDOMEl.addEventListener('touchcancel', onTouchend)
  }

  function onTouchmove (event) {
    const { pageX, pageY } = event.targetTouches ? event.targetTouches[0] : event

    delta = {
      x: pageX - touchOffset.pageX,
      y: pageY - touchOffset.pageY
    }

    const deltaNow = {
      x: pageX - currentTouchOffset.pageX,
      y: pageY - currentTouchOffset.pageY
    }

    currentTouchOffset = { pageX, pageY }

    const isScrollingNow = abs(deltaNow.x) < abs(deltaNow.y)
    isScrolling = !!(isScrolling || isScrollingNow)

    if (navigator.userAgent.indexOf('Android 4.3') >= 0 && !isScrollingNow) {
      event.preventDefault()
    }

    if (!isScrolling && delta.x !== 0) {
      _translate(Math.round(position + delta.x), 0)
    } else if (isScrolling) {
      onTouchend(event)
    }
  }

  function onTouchend (event) {
    /**
     * is valid if:
     * -> swipe distance is greater than the specified valid swipe distance
     * -> swipe distance is more then a third of the swipe area
     * @isValidSlide {Boolean}
     */
    const absoluteX = abs(delta.x)
    const isValid = absoluteX > VALID_SWIPE_DISTANCE || absoluteX > frameWidth / 3

    /**
     * is out of bounds if:
     * -> index is 0 and delta x is greater than 0
     * -> index is the last slide and delta is smaller than 0
     * @isOutOfBounds {Boolean}
     */
    const direction = delta.x < 0
    const isOutOfBounds = (!index && !direction) ||
        (index === slides.length - 1 && direction)

    if (isValid && !isOutOfBounds) {
      slide(direction)
    } else {
      _translate(position, options.snapBackSpeed, LINEAR_ANIMATION)
    }

    delta = {}
    touchOffset = {}
    isScrolling = false

    _removeTouchEventsListeners()
  }

  function _convertItemToDOM (string) {
    const wrappedString = `<li class='${options.classNameItem}'>${string}</li>`
    const el = document.createElement('template')
    if (el.content) {
      el.innerHTML = wrappedString
      return el.content
    } else {
      const container = document.createElement('ul')
      const fragment = document.createDocumentFragment()
      container.innerHTML = wrappedString
      while (container.firstChild) {
        fragment.appendChild(container.firstChild)
      }
      return fragment
    }
  }

  function onResize (event) {
    reset()
  }

  /**
   * public
   * setup function
   */
  function _setup () {
    const { infinite } = options
    const slidesArray = slice.call(items.map(i => _convertItemToDOM(i)))
    position = slideContainerDOMEl.offsetLeft

    slides = infinite ? _setupInfinite(slidesArray) : slice.call(slidesArray)

    _setTailArrowClasses()
    reset()

    slideContainerDOMEl.addEventListener(prefixes.transitionEnd, onTransitionEnd)
    frameDOMEl.addEventListener('touchstart', onTouchstart)
    windowDOM.addEventListener('resize', onResize)
  }

  /**
   * public
   * reset function: called on resize
   */
  function reset () {
    const {infinite, rewindOnResize} = options
    let { ease, rewindSpeed } = options

    frameWidth = _getWidthFromDOMEl(frameDOMEl)
    maxOffset = round((frameWidth * slides.length) - frameWidth)

    let slidesHeight = floor(slideContainerDOMEl.firstChild.getBoundingClientRect().height) + 'px'

    slideContainerDOMEl.style.height = slidesHeight
    frameDOMEl.style.height = slidesHeight

    if (rewindOnResize) {
      index = 0
    } else {
      ease = null
      rewindSpeed = 0
    }

    const offsetIndex = infinite ? index + infinite : index
    const newX = _getOffsetLeft(offsetIndex) * -1
    if (infinite) {
      _translate(newX, 0)
    } else {
      _translate(newX, rewindSpeed, ease)
    }
    index = offsetIndex
    position = newX
  }

  /**
   * public
   * returnIndex function: called on clickhandler
   */
  function returnIndex () {
    return index - options.infinite || 0
  }

  /**
   * public
   * prev function: called on clickhandler
   */
  function prev () {
    slide(false)
  }

  /**
   * public
   * next function: called on clickhandler
   */
  function next () {
    slide(true)
  }

  /**
   * public
   * destroy function: called to gracefully destroy the slidy instance
   */
  function destroy () {
    const { infinite } = options
    _removeAllEventsListeners()
    // remove cloned slides if infinite is set
    if (infinite) {
      const {firstChild, lastChild} = slideContainerDOMEl
      Array.apply(null, Array(infinite)).forEach(function () {
        slideContainerDOMEl.removeChild(firstChild)
        slideContainerDOMEl.removeChild(lastChild)
      })
    }
  }

  // trigger initial setup
  _setup()

  // expose public api
  return {
    reset,
    slide,
    returnIndex,
    prev,
    next,
    destroy
  }
}
<commit_msg>Back to ternary solutions<commit_after>import detectPrefixes from './detect-prefixes.js'

const { slice } = Array.prototype
const prefixes = detectPrefixes()

const LINEAR_ANIMATION = 'linear'
const SLIDES_TO_SCROLL = 1
const VALID_SWIPE_DISTANCE = 25

export function slidy (slider, options) {
  const {
    ease,
    frameDOMEl,
    infinite,
    items,
    rewind,
    rewindSpeed,
    slideSpeed,
    tailArrowClass
  } = options

  // if frameDOMEl is null, then we do nothing
  if (frameDOMEl === null) return

  const {abs, floor, min, max, round} = Math
  const windowDOM = window
  // DOM elements
  const slideContainerDOMEl = frameDOMEl.getElementsByClassName(options.classNameSlideContainer)[0]

  const prevArrow = frameDOMEl.getElementsByClassName(options.classNamePrevCtrl)[0]
  const nextArrow = frameDOMEl.getElementsByClassName(options.classNameNextCtrl)[0]

  // initialize some variables
  let frameWidth = 0
  let index = 0
  let loadedIndex = { 0: 1, 1: 1 }
  let maxOffset = 0
  let position = 0
  let slides = []
  let transitionEndCallback

  // event handling
  let touchOffset = { pageX: 0, pageY: 0 }
  let currentTouchOffset = { pageX: 0, pageY: 0 }
  let delta = { x: 0, y: 0 }
  let isScrolling = false

  // clamp a number between two min and max values
  function _clampNumber (x, minValue, maxValue) {
    return min(max(x, minValue), maxValue)
  }

  // get the width from a DOM element
  function _getWidthFromDOMEl (el) {
    return el.getBoundingClientRect().width
  }

  // calculate the offset with the width of the frame and the desired position
  function _getOffsetLeft (slidePosition) {
    return frameWidth * slidePosition
  }

  /**
   * private
   * _setupInfinite: function to setup if infinite is set
   *
   * @param  {array} slideArray
   * @return {array} array of updated slideContainer elements
   */
  function _setupInfinite (slideArray) {
    const { infinite } = options

    const totalSlides = slideArray.length
    const front = slideArray.slice(0, infinite)
    const back = slideArray.slice(totalSlides - infinite, totalSlides)
    const { firstChild } = slideContainerDOMEl

    front.forEach(function (el) {
      const cloned = el.cloneNode(true)
      slideContainerDOMEl.appendChild(cloned)
    })

    back.reverse()
      .forEach(function (el) {
        const cloned = el.cloneNode(true)
        slideContainerDOMEl.insertBefore(cloned, firstChild)
      })

    return slice.call(slideContainerDOMEl.children)
  }

  /**
   * translates to a given position in a given time in milliseconds
   *
   * @to        {number} number in pixels where to translate to
   * @duration  {number} time in milliseconds for the transistion
   * @ease      {string} easing css property
   */
  function _translate (to, duration, ease) {
    const easeCssText = ease ? `${prefixes.transitionTiming}: ${ease};` : ''
    const durationCssText = duration ? `${prefixes.transitionDuration}: ${duration}ms;` : ''
    const cssText = `${easeCssText}${durationCssText}
      ${prefixes.transform}: ${prefixes.translate(to)};`

    slideContainerDOMEl.style.cssText = cssText
  }

  function _setTailArrowClasses () {
    if (infinite) { return }
    if (prevArrow && prevArrow.classList) {
      let action = index < 1 ? 'add' : 'remove'
      prevArrow.classList[action](tailArrowClass)
    }
    if (nextArrow && nextArrow.classList) {
      let action = index > options.items.length - 2 ? 'add' : 'remove'
      nextArrow.classList[action](tailArrowClass)
    }
  }

  /**
   * slidefunction called by prev, next & touchend
   *
   * determine nextIndex and slide to next postion
   * under restrictions of the defined options
   *
   * @direction  {boolean} 'true' for right, 'false' for left
   */
  function slide (direction) {
    let duration = slideSpeed

    const movement = direction ? 1 : -1
    const totalSlides = slides.length

    // calculate the nextIndex according to the movement
    let nextIndex = index + (SLIDES_TO_SCROLL * movement)

    // nextIndex should be between 0 and totalSlides minus 1
    nextIndex = _clampNumber(nextIndex, 0, totalSlides - 1)

    if (infinite && direction === undefined) {
      nextIndex += infinite
    }

    let nextOffset = _clampNumber(_getOffsetLeft(nextIndex) * -1, maxOffset * -1, 0)

    if (rewind && direction && abs(position) === maxOffset) {
      nextOffset = 0
      nextIndex = 0
      duration = rewindSpeed
    }

    // translate to the nextOffset by a defined duration and ease function
    _translate(nextOffset, duration, ease)

    // update the position with the next position
    position = nextOffset

    // if the nextIndex is possible according to totalSlides, then use it
    if (nextIndex <= totalSlides) {
      index = nextIndex
    }

    if (infinite && (nextIndex === totalSlides - infinite || nextIndex === 0)) {
      index = direction ? infinite : totalSlides - (infinite * 2)

      position = _getOffsetLeft(index) * -1

      transitionEndCallback = function () {
        _translate(_getOffsetLeft(index) * -1, 0)
      }
    } else {
      const indexToLoad = index + (SLIDES_TO_SCROLL * movement)
      const indexLoaded = !!loadedIndex[indexToLoad]
      if (indexToLoad < totalSlides && indexToLoad >= 0 && !indexLoaded) {
        // insert in the correct position
        slideContainerDOMEl.appendChild(slides[indexToLoad])
        loadedIndex[indexToLoad] = 1
      }
    }

    // Checking wheter to paint or hide the arrows.
    _setTailArrowClasses()

    options.doAfterSlide({ currentSlide: index })
  }

  function _removeTouchEventsListeners (all = false) {
    frameDOMEl.removeEventListener('touchmove', onTouchmove)
    frameDOMEl.removeEventListener('touchend', onTouchend)
    frameDOMEl.removeEventListener('touchcancel', onTouchend)
    if (all) {
      frameDOMEl.removeEventListener('touchstart', onTouchstart)
    }
  }

  function _removeAllEventsListeners () {
    _removeTouchEventsListeners(true)
    slideContainerDOMEl.removeEventListener(prefixes.transitionEnd, onTransitionEnd)
    windowDOM.removeEventListener('resize', onResize)
  }

  function onTransitionEnd () {
    if (transitionEndCallback) {
      transitionEndCallback()
      transitionEndCallback = undefined
    }
  }

  function onTouchstart (event) {
    const { pageX, pageY } = event.targetTouches ? event.targetTouches[0] : event
    touchOffset = currentTouchOffset = { pageX, pageY }
    frameDOMEl.addEventListener('touchmove', onTouchmove)
    frameDOMEl.addEventListener('touchend', onTouchend)
    frameDOMEl.addEventListener('touchcancel', onTouchend)
  }

  function onTouchmove (event) {
    const { pageX, pageY } = event.targetTouches ? event.targetTouches[0] : event

    delta = {
      x: pageX - touchOffset.pageX,
      y: pageY - touchOffset.pageY
    }

    const deltaNow = {
      x: pageX - currentTouchOffset.pageX,
      y: pageY - currentTouchOffset.pageY
    }

    currentTouchOffset = { pageX, pageY }

    const isScrollingNow = abs(deltaNow.x) < abs(deltaNow.y)
    isScrolling = !!(isScrolling || isScrollingNow)

    if (navigator.userAgent.indexOf('Android 4.3') >= 0 && !isScrollingNow) {
      event.preventDefault()
    }

    if (!isScrolling && delta.x !== 0) {
      _translate(Math.round(position + delta.x), 0)
    } else if (isScrolling) {
      onTouchend(event)
    }
  }

  function onTouchend (event) {
    /**
     * is valid if:
     * -> swipe distance is greater than the specified valid swipe distance
     * -> swipe distance is more then a third of the swipe area
     * @isValidSlide {Boolean}
     */
    const absoluteX = abs(delta.x)
    const isValid = absoluteX > VALID_SWIPE_DISTANCE || absoluteX > frameWidth / 3

    /**
     * is out of bounds if:
     * -> index is 0 and delta x is greater than 0
     * -> index is the last slide and delta is smaller than 0
     * @isOutOfBounds {Boolean}
     */
    const direction = delta.x < 0
    const isOutOfBounds = (!index && !direction) ||
        (index === slides.length - 1 && direction)

    if (isValid && !isOutOfBounds) {
      slide(direction)
    } else {
      _translate(position, options.snapBackSpeed, LINEAR_ANIMATION)
    }

    delta = {}
    touchOffset = {}
    isScrolling = false

    _removeTouchEventsListeners()
  }

  function _convertItemToDOM (string) {
    const wrappedString = `<li class='${options.classNameItem}'>${string}</li>`
    const el = document.createElement('template')
    if (el.content) {
      el.innerHTML = wrappedString
      return el.content
    } else {
      const container = document.createElement('ul')
      const fragment = document.createDocumentFragment()
      container.innerHTML = wrappedString
      while (container.firstChild) {
        fragment.appendChild(container.firstChild)
      }
      return fragment
    }
  }

  function onResize (event) {
    reset()
  }

  /**
   * public
   * setup function
   */
  function _setup () {
    const { infinite } = options
    const slidesArray = slice.call(items.map(i => _convertItemToDOM(i)))
    position = slideContainerDOMEl.offsetLeft

    slides = infinite ? _setupInfinite(slidesArray) : slice.call(slidesArray)

    _setTailArrowClasses()
    reset()

    slideContainerDOMEl.addEventListener(prefixes.transitionEnd, onTransitionEnd)
    frameDOMEl.addEventListener('touchstart', onTouchstart)
    windowDOM.addEventListener('resize', onResize)
  }

  /**
   * public
   * reset function: called on resize
   */
  function reset () {
    const {infinite, rewindOnResize} = options
    let { ease, rewindSpeed } = options

    frameWidth = _getWidthFromDOMEl(frameDOMEl)
    maxOffset = round((frameWidth * slides.length) - frameWidth)

    let slidesHeight = floor(slideContainerDOMEl.firstChild.getBoundingClientRect().height) + 'px'

    slideContainerDOMEl.style.height = slidesHeight
    frameDOMEl.style.height = slidesHeight

    if (rewindOnResize) {
      index = 0
    } else {
      ease = null
      rewindSpeed = 0
    }

    const offsetIndex = infinite ? index + infinite : index
    const newX = _getOffsetLeft(offsetIndex) * -1
    if (infinite) {
      _translate(newX, 0)
    } else {
      _translate(newX, rewindSpeed, ease)
    }
    index = offsetIndex
    position = newX
  }

  /**
   * public
   * returnIndex function: called on clickhandler
   */
  function returnIndex () {
    return index - options.infinite || 0
  }

  /**
   * public
   * prev function: called on clickhandler
   */
  function prev () {
    slide(false)
  }

  /**
   * public
   * next function: called on clickhandler
   */
  function next () {
    slide(true)
  }

  /**
   * public
   * destroy function: called to gracefully destroy the slidy instance
   */
  function destroy () {
    const { infinite } = options
    _removeAllEventsListeners()
    // remove cloned slides if infinite is set
    if (infinite) {
      const {firstChild, lastChild} = slideContainerDOMEl
      Array.apply(null, Array(infinite)).forEach(function () {
        slideContainerDOMEl.removeChild(firstChild)
        slideContainerDOMEl.removeChild(lastChild)
      })
    }
  }

  // trigger initial setup
  _setup()

  // expose public api
  return {
    reset,
    slide,
    returnIndex,
    prev,
    next,
    destroy
  }
}
<|endoftext|>


<commit_before>'use strict'

import detectPrefixes from './detect-prefixes'
import checkSupportsPassive from './check-supports-passive'

const { slice } = Array.prototype
const prefixes = detectPrefixes()
const EVENT_OPTIONS = checkSupportsPassive() ? { passive: true } : false

const LINEAR_ANIMATION = 'linear'
const VALID_SWIPE_DISTANCE = 25

export function slidy (slider, options) {
  const {abs, floor, min, max, round} = Math

  let position
  let slidesWidth
  let frameWidth
  let slides
  let transitionEndCallback

  // DOM elements
  let frame
  let slideContainer

  let index = 0

  // event handling
  let touchOffset
  let currentTouchOffset
  let delta
  let isScrolling

  // clamp a number between two min and max values
  function _clampNumber (x, minValue, maxValue) {
    return min(max(x, minValue), maxValue)
  }

  // get the width from a DOM element
  function _getWidthFromDOMEl (el) {
    return el.getBoundingClientRect().width
  }

  // get the coordinates from touch event
  function _getTouchCoordinatesFromEvent (event) {
    const { pageX, pageY } = event.changedTouches ? event.changedTouches[0] : event
    return { pageX: round(pageX), pageY: round(pageY) }
  }

  // calculate the offset with the width of the frame and the desired position
  function _getOffsetLeft (slidePosition) {
    return frameWidth * slidePosition
  }

  /**
   * private
   * _setupInfinite: function to setup if infinite is set
   *
   * @param  {array} slideArray
   * @return {array} array of updated slideContainer elements
   */
  function _setupInfinite (slideArray) {
    const { infinite } = options

    const totalSlides = slideArray.length
    const front = slideArray.slice(0, infinite)
    const back = slideArray.slice(totalSlides - infinite, totalSlides)
    const { firstChild } = slideContainer

    front.forEach(function (el) {
      const cloned = el.cloneNode(true)
      slideContainer.appendChild(cloned)
    })

    back.reverse()
      .forEach(function (el) {
        const cloned = el.cloneNode(true)
        slideContainer.insertBefore(cloned, firstChild)
      })

    return slice.call(slideContainer.children)
  }

  /**
   * translates to a given position in a given time in milliseconds
   *
   * @to        {number} number in pixels where to translate to
   * @duration  {number} time in milliseconds for the transistion
   * @ease      {string} easing css property
   */
  function _translate (to, duration, ease) {
    const easeCssText = ease ? `${prefixes.transitionTiming}: ${ease};` : ''
    const durationCssText = duration ? `${prefixes.transitionDuration}: ${duration}ms;` : ''
    const cssText = `${easeCssText}${durationCssText}
      ${prefixes.transform}: ${prefixes.translate(to)};`

    slideContainer.style.cssText = cssText
  }

  /**
   * slidefunction called by prev, next & touchend
   *
   * determine nextIndex and slide to next postion
   * under restrictions of the defined options
   *
   * @direction  {boolean}
   */
  function slide (direction) {
    const {
      slideSpeed,
      slidesToScroll,
      infinite,
      rewind,
      rewindSpeed,
      ease
    } = options

    let duration = slideSpeed

    const movement = direction ? 1 : -1
    const maxOffset = round(slidesWidth - frameWidth)
    const totalSlides = slides.length

    // calculate the nextIndex according to the movement and the slidesToScroll
    let nextIndex = index + slidesToScroll * movement

    // nextIndex should be between 0 and totalSlides minus 1
    nextIndex = _clampNumber(nextIndex, 0, totalSlides - 1)

    if (infinite && direction === undefined) {
      nextIndex += infinite
    }

    let nextOffset = _clampNumber(_getOffsetLeft(nextIndex) * -1, maxOffset * -1, 0)

    if (rewind && direction && abs(position) === maxOffset) {
      nextOffset = 0
      nextIndex = 0
      duration = rewindSpeed
    }

    // translate to the nextOffset by a defined duration and ease function
    _translate(nextOffset, duration, ease)

    // update the position with the next position
    position = nextOffset

    // if the nextIndex is possible according to totalSlides, then use it
    if (nextIndex <= totalSlides) {
      index = nextIndex
    }

    if (infinite && (nextIndex === totalSlides - infinite || nextIndex === 0)) {
      index = direction ? infinite : totalSlides - (infinite * 2)

      position = _getOffsetLeft(index) * -1

      transitionEndCallback = function () {
        _translate(_getOffsetLeft(index) * -1, 0)
      }
    }

    setTimeout(function () {
      options.doAfterSlide({ currentSlide: index })
    }, 0)
  }

  function _startTouchEventsListeners () {
    frame.addEventListener('touchmove', onTouchmove, EVENT_OPTIONS)
    frame.addEventListener('touchend', onTouchend, EVENT_OPTIONS)
  }

  function _removeTouchEventsListeners (all = false) {
    frame.removeEventListener('touchmove', onTouchmove)
    frame.removeEventListener('touchend', onTouchend)
    if (all) {
      frame.removeEventListener('touchstart', onTouchstart)
    }
  }

  function _removeAllEventsListeners () {
    _removeTouchEventsListeners(true)
    frame.removeEventListener(prefixes.transitionEnd, onTransitionEnd)
    window.removeEventListener('resize', onResize)
  }

  function onTransitionEnd () {
    if (transitionEndCallback) {
      transitionEndCallback()
      transitionEndCallback = undefined
    }
  }

  function onTouchstart (event) {
    _startTouchEventsListeners()
    const { pageX, pageY } = _getTouchCoordinatesFromEvent(event)
    touchOffset = currentTouchOffset = { pageX, pageY }

    delta = {}
    isScrolling = false
  }

  function onTouchmove (event) {
    const { pageX, pageY } = _getTouchCoordinatesFromEvent(event)

    delta = {
      x: pageX - touchOffset.pageX,
      y: pageY - touchOffset.pageY
    }

    const deltaNow = {
      x: pageX - currentTouchOffset.pageX,
      y: pageY - currentTouchOffset.pageY
    }

    currentTouchOffset = { pageX, pageY }

    const isScrollingNow = abs(deltaNow.x) < abs(deltaNow.y)
    isScrolling = !!(isScrolling || isScrollingNow)

    if (!isScrolling && delta.x !== 0) {
      _translate(position + delta.x, 10, LINEAR_ANIMATION)
    } else if (isScrolling) {
      onTouchend(event)
    }
  }

  function onTouchend (event) {
    /**
     * is valid if:
     * -> swipe distance is greater than the specified valid swipe distance
     * -> swipe distance is more then a third of the swipe area
     * @isValidSlide {Boolean}
     */
    const absoluteX = abs(delta.x)
    const isValid = absoluteX > VALID_SWIPE_DISTANCE || absoluteX > frameWidth / 3

    /**
     * is out of bounds if:
     * -> index is 0 and delta x is greater than 0
     * -> index is the last slide and delta is smaller than 0
     * @isOutOfBounds {Boolean}
     */
    const direction = delta.x < 0
    const isOutOfBounds = !index && !direction ||
        index === slides.length - 1 && direction

    if (isValid && !isOutOfBounds) {
      slide(direction)
    } else {
      _translate(position, options.snapBackSpeed, LINEAR_ANIMATION)
    }

    touchOffset = {}
    isScrolling = false
    _removeTouchEventsListeners()
  }

  function onResize (event) {
    reset()
  }

  /**
   * public
   * setup function
   */
  function _setup () {
    const {
      classNameFrame,
      classNameSlideContainer,
      infinite
    } = options

    frame = slider.getElementsByClassName(classNameFrame)[0]
    slideContainer = frame.getElementsByClassName(classNameSlideContainer)[0]

    position = slideContainer.offsetLeft

    slides = infinite
             ? _setupInfinite(slice.call(slideContainer.children))
             : slice.call(slideContainer.children)

    reset()

    slideContainer.addEventListener(prefixes.transitionEnd, onTransitionEnd)
    frame.addEventListener('touchstart', onTouchstart, EVENT_OPTIONS)
    window.addEventListener('resize', onResize, EVENT_OPTIONS)
  }

  /**
   * public
   * reset function: called on resize
   */
  function reset () {
    const {infinite, rewindOnResize} = options
    let { ease, rewindSpeed } = options

    slidesWidth = _getWidthFromDOMEl(slideContainer)
    frameWidth = _getWidthFromDOMEl(frame)

    if (frameWidth === slidesWidth) {
      slidesWidth = slides.reduce(function (previousValue, slide) {
        return previousValue + _getWidthFromDOMEl(slide)
      }, 0)
    }

    const slidesHeight = floor(slideContainer.firstChild.getBoundingClientRect().height) + 'px'
    slider.style.height = slidesHeight
    slideContainer.style.height = slidesHeight
    frame.style.height = slidesHeight

    if (rewindOnResize) {
      index = 0
    } else {
      ease = null
      rewindSpeed = 0
    }

    const offsetIndex = infinite ? index + infinite : index
    const newX = _getOffsetLeft(offsetIndex) * -1
    if (infinite) {
      _translate(newX, 0)
    } else {
      _translate(newX, rewindSpeed, ease)
    }
    index = offsetIndex
    position = newX
  }

  /**
   * public
   * returnIndex function: called on clickhandler
   */
  function returnIndex () {
    return index - options.infinite || 0
  }

  /**
   * public
   * prev function: called on clickhandler
   */
  function prev () {
    slide(false)
  }

  /**
   * public
   * next function: called on clickhandler
   */
  function next () {
    slide(true)
  }

  /**
   * public
   * destroy function: called to gracefully destroy the slidy instance
   */
  function destroy () {
    const { infinite } = options
    _removeAllEventsListeners()
    // remove cloned slides if infinite is set
    if (infinite) {
      const {firstChild, lastChild} = slideContainer
      Array.apply(null, Array(infinite)).forEach(function () {
        slideContainer.removeChild(firstChild)
        slideContainer.removeChild(lastChild)
      })
    }
  }

  // trigger initial setup
  _setup()

  // expose public api
  return {
    reset,
    slide,
    returnIndex,
    prev,
    next,
    destroy
  }
}
<commit_msg>Initialize variables in order to V8 optimize them<commit_after>import detectPrefixes from './detect-prefixes.js'

const { slice } = Array.prototype
const prefixes = detectPrefixes()

const LINEAR_ANIMATION = 'linear'
const VALID_SWIPE_DISTANCE = 25

export function slidy (slider, options) {
  const {abs, floor, min, max, round} = Math
  const windowDOM = window

  let position = 0
  let slidesWidth = 0
  let frameWidth = 0
  let slides = 0
  let transitionEndCallback

  // DOM elements
  let frame
  let slideContainer

  let index = 0

  // event handling
  let touchOffset = {}
  let currentTouchOffset = {}
  let delta = {}
  let isScrolling = false

  // clamp a number between two min and max values
  function _clampNumber (x, minValue, maxValue) {
    return min(max(x, minValue), maxValue)
  }

  // get the width from a DOM element
  function _getWidthFromDOMEl (el) {
    return el.getBoundingClientRect().width
  }

  // get the coordinates from touch event
  function _getTouchCoordinatesFromEvent (event) {
    const { pageX, pageY } = event.targetTouches ? event.targetTouches[0] : event
    return { pageX: round(pageX), pageY: round(pageY) }
  }

  // calculate the offset with the width of the frame and the desired position
  function _getOffsetLeft (slidePosition) {
    return frameWidth * slidePosition
  }

  /**
   * private
   * _setupInfinite: function to setup if infinite is set
   *
   * @param  {array} slideArray
   * @return {array} array of updated slideContainer elements
   */
  function _setupInfinite (slideArray) {
    const { infinite } = options

    const totalSlides = slideArray.length
    const front = slideArray.slice(0, infinite)
    const back = slideArray.slice(totalSlides - infinite, totalSlides)
    const { firstChild } = slideContainer

    front.forEach(function (el) {
      const cloned = el.cloneNode(true)
      slideContainer.appendChild(cloned)
    })

    back.reverse()
      .forEach(function (el) {
        const cloned = el.cloneNode(true)
        slideContainer.insertBefore(cloned, firstChild)
      })

    return slice.call(slideContainer.children)
  }

  /**
   * translates to a given position in a given time in milliseconds
   *
   * @to        {number} number in pixels where to translate to
   * @duration  {number} time in milliseconds for the transistion
   * @ease      {string} easing css property
   */
  function _translate (to, duration, ease) {
    const easeCssText = ease ? `${prefixes.transitionTiming}: ${ease};` : ''
    const durationCssText = duration ? `${prefixes.transitionDuration}: ${duration}ms;` : ''
    const cssText = `${easeCssText}${durationCssText}
      ${prefixes.transform}: ${prefixes.translate(to)};`

    slideContainer.style.cssText = cssText
  }

  /**
   * slidefunction called by prev, next & touchend
   *
   * determine nextIndex and slide to next postion
   * under restrictions of the defined options
   *
   * @direction  {boolean}
   */
  function slide (direction) {
    const {
      slideSpeed,
      slidesToScroll,
      infinite,
      rewind,
      rewindSpeed,
      ease
    } = options

    let duration = slideSpeed

    const movement = direction ? 1 : -1
    const maxOffset = round(slidesWidth - frameWidth)
    const totalSlides = slides.length

    // calculate the nextIndex according to the movement and the slidesToScroll
    let nextIndex = index + slidesToScroll * movement

    // nextIndex should be between 0 and totalSlides minus 1
    nextIndex = _clampNumber(nextIndex, 0, totalSlides - 1)

    if (infinite && direction === undefined) {
      nextIndex += infinite
    }

    let nextOffset = _clampNumber(_getOffsetLeft(nextIndex) * -1, maxOffset * -1, 0)

    if (rewind && direction && abs(position) === maxOffset) {
      nextOffset = 0
      nextIndex = 0
      duration = rewindSpeed
    }

    // translate to the nextOffset by a defined duration and ease function
    _translate(nextOffset, duration, ease)

    // update the position with the next position
    position = nextOffset

    // if the nextIndex is possible according to totalSlides, then use it
    if (nextIndex <= totalSlides) {
      index = nextIndex
    }

    if (infinite && (nextIndex === totalSlides - infinite || nextIndex === 0)) {
      index = direction ? infinite : totalSlides - (infinite * 2)

      position = _getOffsetLeft(index) * -1

      transitionEndCallback = function () {
        _translate(_getOffsetLeft(index) * -1, 0)
      }
    }

    setTimeout(function () {
      options.doAfterSlide({ currentSlide: index })
    }, 0)
  }

  function _startTouchEventsListeners () {
    frame.addEventListener('touchmove', onTouchmove)
    frame.addEventListener('touchend', onTouchend)
  }

  function _removeTouchEventsListeners (all = false) {
    frame.removeEventListener('touchmove', onTouchmove)
    frame.removeEventListener('touchend', onTouchend)
    if (all) {
      frame.removeEventListener('touchstart', onTouchstart)
    }
  }

  function _removeAllEventsListeners () {
    _removeTouchEventsListeners(true)
    frame.removeEventListener(prefixes.transitionEnd, onTransitionEnd)
    windowDOM.removeEventListener('resize', onResize)
  }

  function onTransitionEnd () {
    if (transitionEndCallback) {
      transitionEndCallback()
      transitionEndCallback = undefined
    }
  }

  function onTouchstart (event) {
    _startTouchEventsListeners()
    const { pageX, pageY } = _getTouchCoordinatesFromEvent(event)
    touchOffset = currentTouchOffset = { pageX, pageY }
  }

  function onTouchmove (event) {
    window.requestAnimationFrame(function () {
      const { pageX, pageY } = _getTouchCoordinatesFromEvent(event)

      delta = {
        x: pageX - touchOffset.pageX,
        y: pageY - touchOffset.pageY
      }

      const deltaNow = {
        x: pageX - currentTouchOffset.pageX,
        y: pageY - currentTouchOffset.pageY
      }

      currentTouchOffset = { pageX, pageY }

      const isScrollingNow = abs(deltaNow.x) < abs(deltaNow.y)
      isScrolling = !!(isScrolling || isScrollingNow)

      if (!isScrolling && delta.x !== 0) {
        _translate(position + delta.x, 50, LINEAR_ANIMATION)
      } else if (isScrolling) {
        onTouchend(event)
      }
    })
  }

  function onTouchend (event) {
    /**
     * is valid if:
     * -> swipe distance is greater than the specified valid swipe distance
     * -> swipe distance is more then a third of the swipe area
     * @isValidSlide {Boolean}
     */
    const absoluteX = abs(delta.x)
    const isValid = absoluteX > VALID_SWIPE_DISTANCE || absoluteX > frameWidth / 3

    /**
     * is out of bounds if:
     * -> index is 0 and delta x is greater than 0
     * -> index is the last slide and delta is smaller than 0
     * @isOutOfBounds {Boolean}
     */
    const direction = delta.x < 0
    const isOutOfBounds = !index && !direction ||
        index === slides.length - 1 && direction

    if (isValid && !isOutOfBounds) {
      slide(direction)
    } else {
      _translate(position, options.snapBackSpeed, LINEAR_ANIMATION)
    }

    delta = {}
    touchOffset = {}
    isScrolling = false

    _removeTouchEventsListeners()
  }

  function onResize (event) {
    reset()
  }

  /**
   * public
   * setup function
   */
  function _setup () {
    const {
      classNameFrame,
      classNameSlideContainer,
      infinite
    } = options

    frame = slider.getElementsByClassName(classNameFrame)[0]
    slideContainer = frame.getElementsByClassName(classNameSlideContainer)[0]

    position = slideContainer.offsetLeft

    slides = infinite
             ? _setupInfinite(slice.call(slideContainer.children))
             : slice.call(slideContainer.children)

    reset()

    slideContainer.addEventListener(prefixes.transitionEnd, onTransitionEnd)
    frame.addEventListener('touchstart', onTouchstart)
    windowDOM.addEventListener('resize', onResize)
  }

  /**
   * public
   * reset function: called on resize
   */
  function reset () {
    const {infinite, rewindOnResize} = options
    let { ease, rewindSpeed } = options

    slidesWidth = _getWidthFromDOMEl(slideContainer)
    frameWidth = _getWidthFromDOMEl(frame)

    if (frameWidth === slidesWidth) {
      slidesWidth = slides.reduce(function (previousValue, slide) {
        return previousValue + _getWidthFromDOMEl(slide)
      }, 0)
    }

    const slidesHeight = floor(slideContainer.firstChild.getBoundingClientRect().height) + 'px'
    slider.style.height = slidesHeight
    slideContainer.style.height = slidesHeight
    frame.style.height = slidesHeight

    if (rewindOnResize) {
      index = 0
    } else {
      ease = null
      rewindSpeed = 0
    }

    const offsetIndex = infinite ? index + infinite : index
    const newX = _getOffsetLeft(offsetIndex) * -1
    if (infinite) {
      _translate(newX, 0)
    } else {
      _translate(newX, rewindSpeed, ease)
    }
    index = offsetIndex
    position = newX
  }

  /**
   * public
   * returnIndex function: called on clickhandler
   */
  function returnIndex () {
    return index - options.infinite || 0
  }

  /**
   * public
   * prev function: called on clickhandler
   */
  function prev () {
    slide(false)
  }

  /**
   * public
   * next function: called on clickhandler
   */
  function next () {
    slide(true)
  }

  /**
   * public
   * destroy function: called to gracefully destroy the slidy instance
   */
  function destroy () {
    const { infinite } = options
    _removeAllEventsListeners()
    // remove cloned slides if infinite is set
    if (infinite) {
      const {firstChild, lastChild} = slideContainer
      Array.apply(null, Array(infinite)).forEach(function () {
        slideContainer.removeChild(firstChild)
        slideContainer.removeChild(lastChild)
      })
    }
  }

  // trigger initial setup
  _setup()

  // expose public api
  return {
    reset,
    slide,
    returnIndex,
    prev,
    next,
    destroy
  }
}
<|endoftext|>