<commit_before>if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports){
  module.exports = 'highcharts-ng';
}
(function () {
  'use strict';
  /*global angular: false, Highcharts: false */

  angular.module('highcharts-ng', [])
    .provider('highchartsNG', highchartsNGProvider)
    .directive('highchart', ['highchartsNG', '$timeout', highchart]);

  function highchartsNGProvider(){
    var modules = [];
    var basePath = false;
    var lazyLoad = false;
    return {
      HIGHCHART: 'highcharts.js',
      HIGHSTOCK: 'stock/highstock.js',
      basePath: function (p) {
        basePath = p;
      },
      lazyLoad: function (list) {
        if (list === undefined) {
          modules = [this.HIGHCHART];
        } else {
          modules = list;
        }
        lazyLoad = true;
      },
      $get: ['$window', '$rootScope', function ($window, $rootScope) {
        if (!basePath) {
          basePath = (window.location.protocol === 'https:' ? 'https' : 'http') + '://code.highcharts.com/';
        }
        return highchartsNG($window, $rootScope, lazyLoad, basePath, modules);
      }]
    };
  }
  function highchartsNG($window, $rootScope, lazyload, basePath, modules) {
    var readyQueue = [];
    var loading = false;
    return {
      lazyLoad:lazyload,
      ready: function (callback, thisArg) {
        if (typeof $window.Highcharts !== 'undefined' || !lazyload) {
          callback();
        } else {
          readyQueue.push([callback, thisArg]);
          if (loading) {
            return;
          }
          loading = true;
          var self = this;
          if (typeof jQuery === 'undefined') {
            modules.unshift('adapters/standalone-framework.js');
          }
          var doWork = function () {
            if (modules.length === 0) {
              loading = false;
              $rootScope.$apply(function () {
                angular.forEach(readyQueue, function (e) {
                  // invoke callback passing 'thisArg'
                  e[0].apply(e[1], []);
                });
              });
            } else {
              var s = modules.shift();
              self.loadScript(s, doWork);
            }
          };
          doWork();
        }
      },
      loadScript: function (path, callback) {
        var s = document.createElement('script');
        s.type = 'text/javascript';
        s.src = basePath + path;
        s.onload = callback;
        document.getElementsByTagName('body')[0].appendChild(s);
      },
      //IE8 support
      indexOf: function (arr, find, i /*opt*/) {
        if (i === undefined) i = 0;
        if (i < 0) i += arr.length;
        if (i < 0) i = 0;
        for (var n = arr.length; i < n; i++)
          if (i in arr && arr[i] === find)
            return i;
        return -1;
      },

      prependMethod: function (obj, method, func) {
        var original = obj[method];
        obj[method] = function () {
          var args = Array.prototype.slice.call(arguments);
          func.apply(this, args);
          if (original) {
            return original.apply(this, args);
          } else {
            return;
          }

        };
      },

      deepExtend: function deepExtend(destination, source) {
        //Slightly strange behaviour in edge cases (e.g. passing in non objects)
        //But does the job for current use cases.
        if (angular.isArray(source)) {
          destination = angular.isArray(destination) ? destination : [];
          for (var i = 0; i < source.length; i++) {
            destination[i] = deepExtend(destination[i] || {}, source[i]);
          }
        } else if (angular.isObject(source)) {
          for (var property in source) {
            destination[property] = deepExtend(destination[property] || {}, source[property]);
          }
        } else {
          destination = source;
        }
        return destination;
      }
    };
  }

  function highchart(highchartsNGUtils, $timeout) {

    // acceptable shared state
    var seriesId = 0;
    var ensureIds = function (series) {
      var changed = false;
      angular.forEach(series, function(s) {
        if (!angular.isDefined(s.id)) {
          s.id = 'series-' + seriesId++;
          changed = true;
        }
      });
      return changed;
    };

    // immutable
    var axisNames = [ 'xAxis', 'yAxis' ];
    var chartTypeMap = {
      'stock': 'StockChart',
      'map':   'Map',
      'chart': 'Chart'
    };

    var getMergedOptions = function (scope, element, config) {
      var mergedOptions = {};

      var defaultOptions = {
        chart: {
          events: {}
        },
        title: {},
        subtitle: {},
        series: [],
        credits: {},
        plotOptions: {},
        navigator: {enabled: false},
        xAxis: {
          events: {}
        }
      };

      if (config.options) {
        mergedOptions = highchartsNGUtils.deepExtend(defaultOptions, config.options);
      } else {
        mergedOptions = defaultOptions;
      }
      mergedOptions.chart.renderTo = element[0];

      angular.forEach(axisNames, function(axisName) {
        if(angular.isDefined(config[axisName])) {
          mergedOptions[axisName] = highchartsNGUtils.deepExtend(mergedOptions[axisName], config[axisName]);

          if(angular.isDefined(config[axisName].currentMin) ||
              angular.isDefined(config[axisName].currentMax)) {

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'selection', function(e){
              var thisChart = this;
              if (e[axisName]) {
                scope.$apply(function () {
                  scope.config[axisName].currentMin = e[axisName][0].min;
                  scope.config[axisName].currentMax = e[axisName][0].max;
                });
              } else {
                //handle reset button - zoom out to all
                scope.$apply(function () {
                  scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                  scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
                });
              }
            });

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'addSeries', function(e){
              scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
              scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
            });
            highchartsNGUtils.prependMethod(mergedOptions[axisName].events, 'setExtremes', function (e) {
              if (e.trigger && e.trigger !== 'zoom') { // zoom trigger is handled by selection event
                $timeout(function () {
                  scope.config[axisName].currentMin = e.min;
                  scope.config[axisName].currentMax = e.max;
                  scope.config[axisName].min = e.min; // set min and max to adjust scrollbar/navigator
                  scope.config[axisName].max = e.max;
                }, 0);
              }
            });
          }
        }
      });

      if(config.title) {
        mergedOptions.title = config.title;
      }
      if (config.subtitle) {
        mergedOptions.subtitle = config.subtitle;
      }
      if (config.credits) {
        mergedOptions.credits = config.credits;
      }
      if(config.size) {
        if (config.size.width) {
          mergedOptions.chart.width = config.size.width;
        }
        if (config.size.height) {
          mergedOptions.chart.height = config.size.height;
        }
      }
      return mergedOptions;
    };

    var updateZoom = function (axis, modelAxis) {
      var extremes = axis.getExtremes();
      if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
        if (axis.setExtremes) {
          axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
        } else {
          axis.detachedsetExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
        }
      }
    };

    var processExtremes = function(chart, axis, axisName) {
      if(axis.currentMin || axis.currentMax) {
        chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
      }
    };

    var chartOptionsWithoutEasyOptions = function (options) {
      return angular.extend(
        highchartsNGUtils.deepExtend({}, options),
        { data: null, visible: null }
      );
    };

    var getChartType = function(scope) {
      if (scope.config === undefined) return 'Chart';
      return chartTypeMap[('' + scope.config.chartType).toLowerCase()] ||
             (scope.config.useHighStocks ? 'StockChart' : 'Chart');
    };

    var res = {
      restrict: 'EAC',
      replace: true,
      template: '

',
      scope: {
        config: '=',
        disableDataWatch: '='
      },
      link: function (scope, element, attrs) {
        // We keep some chart-specific variables here as a closure
        // instead of storing them on 'scope'.

        // prevSeriesOptions is maintained by processSeries
        var prevSeriesOptions = {};

        var processSeries = function(series) {
          var i;
          var ids = [];

          if(series) {
            var setIds = ensureIds(series);
            if(setIds && !scope.disableDataWatch) {
              //If we have set some ids this will trigger another digest cycle.
              //In this scenario just return early and let the next cycle take care of changes
              return false;
            }

            //Find series to add or update
            angular.forEach(series, function(s) {
              ids.push(s.id);
              var chartSeries = chart.get(s.id);
              if (chartSeries) {
                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                  chartSeries.update(angular.copy(s), false);
                } else {
                  if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                    chartSeries.setVisible(s.visible, false);
                  }
                  chartSeries.setData(angular.copy(s.data), false);
                }
              } else {
                chart.addSeries(angular.copy(s), false);
              }
              prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
            });

            //  Shows no data text if all series are empty
            if(scope.config.noData) {
              var chartContainsData = false;

              for(i = 0; i < series.length; i++) {
                if (series[i].data && series[i].data.length > 0) {
                  chartContainsData = true;

                  break;
                }
              }

              if (!chartContainsData) {
                chart.showLoading(scope.config.noData);
              } else {
                chart.hideLoading();
              }
            }
          }

          //Now remove any missing series
          for(i = chart.series.length - 1; i >= 0; i--) {
            var s = chart.series[i];
            if (s.options.id !== 'highcharts-navigator-series' && highchartsNGUtils.indexOf(ids, s.options.id) < 0) {
              s.remove(false);
            }
          }

          return true;
        };

        // chart is maintained by initChart
        var chart = false;
        var initChart = function() {
          if (chart) chart.destroy();
          prevSeriesOptions = {};
          var config = scope.config || {};
          var mergedOptions = getMergedOptions(scope, element, config);
          var func = config.func || undefined;
          var chartType = getChartType(scope);

          chart = new Highcharts[chartType](mergedOptions, func);

          for (var i = 0; i < axisNames.length; i++) {
            if (config[axisNames[i]]) {
              processExtremes(chart, config[axisNames[i]], axisNames[i]);
            }
          }
          if(config.loading) {
            chart.showLoading();
          }
          config.getHighcharts = function() {
            return chart;
          };

        };
        initChart();

        if(scope.disableDataWatch){
          scope.$watchCollection('config.series', function (newSeries, oldSeries) {
            processSeries(newSeries);
            chart.redraw();
          });
        } else {
          scope.$watch('config.series', function (newSeries, oldSeries) {
            var needsRedraw = processSeries(newSeries);
            if(needsRedraw) {
              chart.redraw();
            }
          }, true);
        }

        scope.$watch('config.title', function (newTitle) {
          chart.setTitle(newTitle, true);
        }, true);

        scope.$watch('config.subtitle', function (newSubtitle) {
          chart.setTitle(true, newSubtitle);
        }, true);

        scope.$watch('config.loading', function (loading) {
          if(loading) {
            chart.showLoading(loading === true ? null : loading);
          } else {
            chart.hideLoading();
          }
        });
        scope.$watch('config.noData', function (noData) {
          if(scope.config && scope.config.loading) {
            chart.showLoading(noData);
          }
        }, true);

        scope.$watch('config.credits.enabled', function (enabled) {
          if (enabled) {
            chart.credits.show();
          } else if (chart.credits) {
            chart.credits.hide();
          }
        });

        scope.$watch(getChartType, function (chartType, oldChartType) {
          if (chartType === oldChartType) return;
          initChart();
        });

        angular.forEach(axisNames, function(axisName) {
          scope.$watch('config.' + axisName, function(newAxes, oldAxes) {
            if (newAxes === oldAxes || !newAxes) {
              return;
            }

            if (angular.isArray(newAxes)) {

              for (var axisIndex = 0; axisIndex < newAxes.length; axisIndex++) {
                var axis = newAxes[axisIndex];

                if (axisIndex < chart[axisName].length) {
                  chart[axisName][axisIndex].update(axis, false);
                  updateZoom(chart[axisName][axisIndex], angular.copy(axis));
                }

              }

            } else {
              // update single axis
              chart[axisName][0].update(newAxes, false);
              updateZoom(chart[axisName][0], angular.copy(newAxes));
            }

            chart.redraw();
          }, true);
        });
        scope.$watch('config.options', function (newOptions, oldOptions, scope) {
          //do nothing when called on registration
          if (newOptions === oldOptions) return;
          initChart();
          processSeries(scope.config.series);
          chart.redraw();
        }, true);

        scope.$watch('config.size', function (newSize, oldSize) {
          if(newSize === oldSize) return;
          if(newSize) {
            chart.setSize(newSize.width || chart.chartWidth, newSize.height || chart.chartHeight);
          }
        }, true);

        scope.$on('highchartsng.reflow', function () {
          chart.reflow();
        });

        scope.$on('$destroy', function() {
          if (chart) {
            try{
              chart.destroy();
            }catch(ex){
              // fail silently as highcharts will throw exception if element doesn't exist
            }

            $timeout(function(){
              element.remove();
            }, 0);
          }
        });

      }
    };

    // override link fn if lazy loading is enabled
    if(highchartsNGUtils.lazyLoad){
      var oldLink = res.link;
      res.link = function(){
        var args = arguments;
        highchartsNGUtils.ready(function(){
          oldLink.apply(this, args);
        }, this);
      };
    }
    return res;
  }
}());
<commit_msg>fix(deepExtend): Ensure dst is obj when src is obj<commit_after>if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports){
  module.exports = 'highcharts-ng';
}
(function () {
  'use strict';
  /*global angular: false, Highcharts: false */

  angular.module('highcharts-ng', [])
    .provider('highchartsNG', highchartsNGProvider)
    .directive('highchart', ['highchartsNG', '$timeout', highchart]);

  function highchartsNGProvider(){
    var modules = [];
    var basePath = false;
    var lazyLoad = false;
    return {
      HIGHCHART: 'highcharts.js',
      HIGHSTOCK: 'stock/highstock.js',
      basePath: function (p) {
        basePath = p;
      },
      lazyLoad: function (list) {
        if (list === undefined) {
          modules = [this.HIGHCHART];
        } else {
          modules = list;
        }
        lazyLoad = true;
      },
      $get: ['$window', '$rootScope', function ($window, $rootScope) {
        if (!basePath) {
          basePath = (window.location.protocol === 'https:' ? 'https' : 'http') + '://code.highcharts.com/';
        }
        return highchartsNG($window, $rootScope, lazyLoad, basePath, modules);
      }]
    };
  }
  function highchartsNG($window, $rootScope, lazyload, basePath, modules) {
    var readyQueue = [];
    var loading = false;
    return {
      lazyLoad:lazyload,
      ready: function (callback, thisArg) {
        if (typeof $window.Highcharts !== 'undefined' || !lazyload) {
          callback();
        } else {
          readyQueue.push([callback, thisArg]);
          if (loading) {
            return;
          }
          loading = true;
          var self = this;
          if (typeof jQuery === 'undefined') {
            modules.unshift('adapters/standalone-framework.js');
          }
          var doWork = function () {
            if (modules.length === 0) {
              loading = false;
              $rootScope.$apply(function () {
                angular.forEach(readyQueue, function (e) {
                  // invoke callback passing 'thisArg'
                  e[0].apply(e[1], []);
                });
              });
            } else {
              var s = modules.shift();
              self.loadScript(s, doWork);
            }
          };
          doWork();
        }
      },
      loadScript: function (path, callback) {
        var s = document.createElement('script');
        s.type = 'text/javascript';
        s.src = basePath + path;
        s.onload = callback;
        document.getElementsByTagName('body')[0].appendChild(s);
      },
      //IE8 support
      indexOf: function (arr, find, i /*opt*/) {
        if (i === undefined) i = 0;
        if (i < 0) i += arr.length;
        if (i < 0) i = 0;
        for (var n = arr.length; i < n; i++)
          if (i in arr && arr[i] === find)
            return i;
        return -1;
      },

      prependMethod: function (obj, method, func) {
        var original = obj[method];
        obj[method] = function () {
          var args = Array.prototype.slice.call(arguments);
          func.apply(this, args);
          if (original) {
            return original.apply(this, args);
          } else {
            return;
          }

        };
      },

      deepExtend: function deepExtend(destination, source) {
        //Slightly strange behaviour in edge cases (e.g. passing in non objects)
        //But does the job for current use cases.
        if (angular.isArray(source)) {
          destination = angular.isArray(destination) ? destination : [];
          for (var i = 0; i < source.length; i++) {
            destination[i] = deepExtend(destination[i] || {}, source[i]);
          }
        } else if (angular.isObject(source)) {
          destination = angular.isObject(destination) ? destination : {};
          for (var property in source) {
            destination[property] = deepExtend(destination[property] || {}, source[property]);
          }
        } else {
          destination = source;
        }
        return destination;
      }
    };
  }

  function highchart(highchartsNGUtils, $timeout) {

    // acceptable shared state
    var seriesId = 0;
    var ensureIds = function (series) {
      var changed = false;
      angular.forEach(series, function(s) {
        if (!angular.isDefined(s.id)) {
          s.id = 'series-' + seriesId++;
          changed = true;
        }
      });
      return changed;
    };

    // immutable
    var axisNames = [ 'xAxis', 'yAxis' ];
    var chartTypeMap = {
      'stock': 'StockChart',
      'map':   'Map',
      'chart': 'Chart'
    };

    var getMergedOptions = function (scope, element, config) {
      var mergedOptions = {};

      var defaultOptions = {
        chart: {
          events: {}
        },
        title: {},
        subtitle: {},
        series: [],
        credits: {},
        plotOptions: {},
        navigator: {enabled: false},
        xAxis: {
          events: {}
        }
      };

      if (config.options) {
        mergedOptions = highchartsNGUtils.deepExtend(defaultOptions, config.options);
      } else {
        mergedOptions = defaultOptions;
      }
      mergedOptions.chart.renderTo = element[0];

      angular.forEach(axisNames, function(axisName) {
        if(angular.isDefined(config[axisName])) {
          mergedOptions[axisName] = highchartsNGUtils.deepExtend(mergedOptions[axisName], config[axisName]);

          if(angular.isDefined(config[axisName].currentMin) ||
              angular.isDefined(config[axisName].currentMax)) {

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'selection', function(e){
              var thisChart = this;
              if (e[axisName]) {
                scope.$apply(function () {
                  scope.config[axisName].currentMin = e[axisName][0].min;
                  scope.config[axisName].currentMax = e[axisName][0].max;
                });
              } else {
                //handle reset button - zoom out to all
                scope.$apply(function () {
                  scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                  scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
                });
              }
            });

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'addSeries', function(e){
              scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
              scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
            });
            highchartsNGUtils.prependMethod(mergedOptions[axisName].events, 'setExtremes', function (e) {
              if (e.trigger && e.trigger !== 'zoom') { // zoom trigger is handled by selection event
                $timeout(function () {
                  scope.config[axisName].currentMin = e.min;
                  scope.config[axisName].currentMax = e.max;
                  scope.config[axisName].min = e.min; // set min and max to adjust scrollbar/navigator
                  scope.config[axisName].max = e.max;
                }, 0);
              }
            });
          }
        }
      });

      if(config.title) {
        mergedOptions.title = config.title;
      }
      if (config.subtitle) {
        mergedOptions.subtitle = config.subtitle;
      }
      if (config.credits) {
        mergedOptions.credits = config.credits;
      }
      if(config.size) {
        if (config.size.width) {
          mergedOptions.chart.width = config.size.width;
        }
        if (config.size.height) {
          mergedOptions.chart.height = config.size.height;
        }
      }
      return mergedOptions;
    };

    var updateZoom = function (axis, modelAxis) {
      var extremes = axis.getExtremes();
      if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
        if (axis.setExtremes) {
          axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
        } else {
          axis.detachedsetExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
        }
      }
    };

    var processExtremes = function(chart, axis, axisName) {
      if(axis.currentMin || axis.currentMax) {
        chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
      }
    };

    var chartOptionsWithoutEasyOptions = function (options) {
      return angular.extend(
        highchartsNGUtils.deepExtend({}, options),
        { data: null, visible: null }
      );
    };

    var getChartType = function(scope) {
      if (scope.config === undefined) return 'Chart';
      return chartTypeMap[('' + scope.config.chartType).toLowerCase()] ||
             (scope.config.useHighStocks ? 'StockChart' : 'Chart');
    };

    var res = {
      restrict: 'EAC',
      replace: true,
      template: '

',
      scope: {
        config: '=',
        disableDataWatch: '='
      },
      link: function (scope, element, attrs) {
        // We keep some chart-specific variables here as a closure
        // instead of storing them on 'scope'.

        // prevSeriesOptions is maintained by processSeries
        var prevSeriesOptions = {};

        var processSeries = function(series) {
          var i;
          var ids = [];

          if(series) {
            var setIds = ensureIds(series);
            if(setIds && !scope.disableDataWatch) {
              //If we have set some ids this will trigger another digest cycle.
              //In this scenario just return early and let the next cycle take care of changes
              return false;
            }

            //Find series to add or update
            angular.forEach(series, function(s) {
              ids.push(s.id);
              var chartSeries = chart.get(s.id);
              if (chartSeries) {
                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                  chartSeries.update(angular.copy(s), false);
                } else {
                  if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                    chartSeries.setVisible(s.visible, false);
                  }
                  chartSeries.setData(angular.copy(s.data), false);
                }
              } else {
                chart.addSeries(angular.copy(s), false);
              }
              prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
            });

            //  Shows no data text if all series are empty
            if(scope.config.noData) {
              var chartContainsData = false;

              for(i = 0; i < series.length; i++) {
                if (series[i].data && series[i].data.length > 0) {
                  chartContainsData = true;

                  break;
                }
              }

              if (!chartContainsData) {
                chart.showLoading(scope.config.noData);
              } else {
                chart.hideLoading();
              }
            }
          }

          //Now remove any missing series
          for(i = chart.series.length - 1; i >= 0; i--) {
            var s = chart.series[i];
            if (s.options.id !== 'highcharts-navigator-series' && highchartsNGUtils.indexOf(ids, s.options.id) < 0) {
              s.remove(false);
            }
          }

          return true;
        };

        // chart is maintained by initChart
        var chart = false;
        var initChart = function() {
          if (chart) chart.destroy();
          prevSeriesOptions = {};
          var config = scope.config || {};
          var mergedOptions = getMergedOptions(scope, element, config);
          var func = config.func || undefined;
          var chartType = getChartType(scope);

          chart = new Highcharts[chartType](mergedOptions, func);

          for (var i = 0; i < axisNames.length; i++) {
            if (config[axisNames[i]]) {
              processExtremes(chart, config[axisNames[i]], axisNames[i]);
            }
          }
          if(config.loading) {
            chart.showLoading();
          }
          config.getHighcharts = function() {
            return chart;
          };

        };
        initChart();

        if(scope.disableDataWatch){
          scope.$watchCollection('config.series', function (newSeries, oldSeries) {
            processSeries(newSeries);
            chart.redraw();
          });
        } else {
          scope.$watch('config.series', function (newSeries, oldSeries) {
            var needsRedraw = processSeries(newSeries);
            if(needsRedraw) {
              chart.redraw();
            }
          }, true);
        }

        scope.$watch('config.title', function (newTitle) {
          chart.setTitle(newTitle, true);
        }, true);

        scope.$watch('config.subtitle', function (newSubtitle) {
          chart.setTitle(true, newSubtitle);
        }, true);

        scope.$watch('config.loading', function (loading) {
          if(loading) {
            chart.showLoading(loading === true ? null : loading);
          } else {
            chart.hideLoading();
          }
        });
        scope.$watch('config.noData', function (noData) {
          if(scope.config && scope.config.loading) {
            chart.showLoading(noData);
          }
        }, true);

        scope.$watch('config.credits.enabled', function (enabled) {
          if (enabled) {
            chart.credits.show();
          } else if (chart.credits) {
            chart.credits.hide();
          }
        });

        scope.$watch(getChartType, function (chartType, oldChartType) {
          if (chartType === oldChartType) return;
          initChart();
        });

        angular.forEach(axisNames, function(axisName) {
          scope.$watch('config.' + axisName, function(newAxes, oldAxes) {
            if (newAxes === oldAxes || !newAxes) {
              return;
            }

            if (angular.isArray(newAxes)) {

              for (var axisIndex = 0; axisIndex < newAxes.length; axisIndex++) {
                var axis = newAxes[axisIndex];

                if (axisIndex < chart[axisName].length) {
                  chart[axisName][axisIndex].update(axis, false);
                  updateZoom(chart[axisName][axisIndex], angular.copy(axis));
                }

              }

            } else {
              // update single axis
              chart[axisName][0].update(newAxes, false);
              updateZoom(chart[axisName][0], angular.copy(newAxes));
            }

            chart.redraw();
          }, true);
        });
        scope.$watch('config.options', function (newOptions, oldOptions, scope) {
          //do nothing when called on registration
          if (newOptions === oldOptions) return;
          initChart();
          processSeries(scope.config.series);
          chart.redraw();
        }, true);

        scope.$watch('config.size', function (newSize, oldSize) {
          if(newSize === oldSize) return;
          if(newSize) {
            chart.setSize(newSize.width || chart.chartWidth, newSize.height || chart.chartHeight);
          }
        }, true);

        scope.$on('highchartsng.reflow', function () {
          chart.reflow();
        });

        scope.$on('$destroy', function() {
          if (chart) {
            try{
              chart.destroy();
            }catch(ex){
              // fail silently as highcharts will throw exception if element doesn't exist
            }

            $timeout(function(){
              element.remove();
            }, 0);
          }
        });

      }
    };

    // override link fn if lazy loading is enabled
    if(highchartsNGUtils.lazyLoad){
      var oldLink = res.link;
      res.link = function(){
        var args = arguments;
        highchartsNGUtils.ready(function(){
          oldLink.apply(this, args);
        }, this);
      };
    }
    return res;
  }
}());
<|endoftext|>


<commit_before>import * as hostData from './data';
import * as lightData from '../light/data';
import * as slotFuncs from '../slot/content';
import assignFuncs from '../util/assign-funcs';
import assignProps from '../util/assign-props';
import fragFromHtml from '../util/frag-from-html';
import getSlot from './get-slot';
import htmlFromFrag from '../util/html-from-frag';
import polyfillLight from '../light/polyfill';

const configurable = true;

// Fake parentNode helpers.

function applyParentNode (node, parent) {
  lightData.slotted.set(node, true);
  lightData.parentNode.set(node, parent);
  polyfillLight(node);
}

function removeParentNode (node) {
  lightData.slotted.set(node, false);
  lightData.parentNode.set(node, null);
}

// Slotting helpers.

function arrayItem (idx) {
  return this[idx];
}

function doForNodesIfSlot (elem, node, func) {
  const nodes = nodeToArray(node);
  const nodesLen = nodes.length;

  for (let a = 0; a < nodesLen; a++) {
    const node = nodes[a];
    const slot = getSlot(elem, node);

    if (slot) {
      func(elem, node, slot);
    } else if (node.parentNode) {
      // If the node does not have a slot then we make sure that it is not
      // inserted anywhere else in the document.
      node.parentNode.removeChild(node);
    }
  }
}

function makeLikeNodeList (arr) {
  arr.item = arrayItem;
  return arr;
}

function nodeToArray (node) {
  return node instanceof DocumentFragment ? toArray(node.childNodes) : [node];
}

function toArray (obj) {
  return Array.prototype.slice.call(obj);
}

// Prop overrides.

const hostProps = {
  childElementCount: {
    configurable,
    get () {
      return this.children.length;
    }
  },
  childNodes: {
    get () {
      let nodes = [];
      const slots = this.__slots;
      if (slots) {
        for (let name in slots) {
          const slot = slots[name];
          const childNodes = slot.getAssignedNodes().childNodes;
          const childNodesLen = childNodes.length;
          for (let a = 0; a < childNodesLen; a++) {
            nodes.push(childNodes[a]);
          }
        }
      }
      return makeLikeNodeList(nodes);
    }
  },
  children: {
    get () {
      return makeLikeNodeList(this.childNodes.filter(node => node.nodeType === 1));
    }
  },
  firstChild: {
    get () {
      return this.childNodes[0] || null;
    }
  },
  firstElementChild: {
    get () {
      return this.children[0] || null;
    }
  },
  innerHTML: {
    get () {
      return htmlFromFrag(this);
    },
    set (innerHTML) {
      const copy = fragFromHtml(innerHTML);
      while (this.hasChildNodes()) {
        this.removeChild(this.firstChild);
      }
      while (copy.hasChildNodes()) {
        this.appendChild(copy.firstChild);
      }
    }
  },
  lastChild: {
    get () {
      const ch = this.childNodes;
      return ch[ch.length - 1] || null;
    }
  },
  lastElementChild: {
    get () {
      const ch = this.children;
      return ch[ch.length - 1] || null;
    }
  },
  outerHTML: {
    get () {
      const name = this.tagName.toLowerCase();
      const attributes = toArray(this.attributes).map(function (attr) {
        return ` ${attr.name}${attr.value ? `="${attr.value}"` : ''}`;
      }).join('');
      return `<${name}${attributes}>${this.innerHTML}</${name}>`;
    }
  },
  textContent: {
    get () {
      return this.childNodes.map(node => node.textContent).join('');
    },
    set (textContent) {
      while (this.hasChildNodes()) {
        this.removeChild(this.firstChild);
      }
      this.appendChild(document.createTextNode(textContent));
    }
  }
};

// Method overrides.

const funcs = {
  appendChild (newNode) {
    doForNodesIfSlot(this, newNode, function (elem, node, slot) {
      slotFuncs.appendChild(slot, node);
      applyParentNode(node, elem);
    });
    return newNode;
  },
  hasChildNodes () {
    return this.childNodes.length > 0;
  },
  insertBefore (newNode, refNode) {
    doForNodesIfSlot(this, newNode, function (elem, node, slot) {
      slotFuncs.insertBefore(slot, node, refNode);
      applyParentNode(node, elem);
    });
    return newNode;
  },
  removeChild (refNode) {
    doForNodesIfSlot(this, refNode, function (elem, node, slot) {
      slotFuncs.removeChild(slot, node);
      removeParentNode(node);
    });
    return refNode;
  },
  replaceChild (newNode, refNode) {
    // If the ref node is not in the light DOM, just return it.
    if (refNode.parentNode !== this) {
      return refNode;
    }

    // We're dealing with a representation of the light DOM, so we insert nodes
    // relative to the location of the refNode in the light DOM, not the where
    // it appears in the composed DOM.
    const insertBefore = refNode.nextSibling;

    // Clean up the reference node.
    this.removeChild(refNode);

    // Add new nodes in place of the reference node.
    doForNodesIfSlot(this, newNode, function (elem, node, slot) {
      slotFuncs.insertBefore(slot, node, insertBefore);
      applyParentNode(node, elem);
    });

    return refNode;
  }
};

function polyfill (host) {
  assignProps(host, hostProps);
  assignFuncs(host, funcs);
}

export default function (host) {
  if (hostData.polyfilled.get(host)) {
    return;
  }
  polyfill(host);
  hostData.polyfilled.set(host, true);
  return host;
}
<commit_msg>Organisation.<commit_after>import * as hostData from './data';
import * as lightData from '../light/data';
import * as slotFuncs from '../slot/content';
import assignFuncs from '../util/assign-funcs';
import assignProps from '../util/assign-props';
import fragFromHtml from '../util/frag-from-html';
import getSlot from './get-slot';
import htmlFromFrag from '../util/html-from-frag';
import polyfillLight from '../light/polyfill';

const configurable = true;

// Fake parentNode helpers.

function applyParentNode (node, parent) {
  lightData.slotted.set(node, true);
  lightData.parentNode.set(node, parent);
  polyfillLight(node);
}

function removeParentNode (node) {
  lightData.slotted.set(node, false);
  lightData.parentNode.set(node, null);
}

// Slotting helpers.

function arrayItem (idx) {
  return this[idx];
}

function doForNodesIfSlot (elem, node, func) {
  const nodes = nodeToArray(node);
  const nodesLen = nodes.length;

  for (let a = 0; a < nodesLen; a++) {
    const node = nodes[a];
    const slot = getSlot(elem, node);

    if (slot) {
      func(elem, node, slot);
    } else if (node.parentNode) {
      // If the node does not have a slot then we make sure that it is not
      // inserted anywhere else in the document.
      node.parentNode.removeChild(node);
    }
  }
}

function makeLikeNodeList (arr) {
  arr.item = arrayItem;
  return arr;
}

function nodeToArray (node) {
  return node instanceof DocumentFragment ? toArray(node.childNodes) : [node];
}

function toArray (obj) {
  return Array.prototype.slice.call(obj);
}

// Prop overrides.

const props = {
  childElementCount: {
    configurable,
    get () {
      return this.children.length;
    }
  },
  childNodes: {
    get () {
      let nodes = [];
      const slots = this.__slots;
      if (slots) {
        for (let name in slots) {
          const slot = slots[name];
          const childNodes = slot.getAssignedNodes().childNodes;
          const childNodesLen = childNodes.length;
          for (let a = 0; a < childNodesLen; a++) {
            nodes.push(childNodes[a]);
          }
        }
      }
      return makeLikeNodeList(nodes);
    }
  },
  children: {
    get () {
      return makeLikeNodeList(this.childNodes.filter(node => node.nodeType === 1));
    }
  },
  firstChild: {
    get () {
      return this.childNodes[0] || null;
    }
  },
  firstElementChild: {
    get () {
      return this.children[0] || null;
    }
  },
  innerHTML: {
    get () {
      return htmlFromFrag(this);
    },
    set (innerHTML) {
      const copy = fragFromHtml(innerHTML);
      while (this.hasChildNodes()) {
        this.removeChild(this.firstChild);
      }
      while (copy.hasChildNodes()) {
        this.appendChild(copy.firstChild);
      }
    }
  },
  lastChild: {
    get () {
      const ch = this.childNodes;
      return ch[ch.length - 1] || null;
    }
  },
  lastElementChild: {
    get () {
      const ch = this.children;
      return ch[ch.length - 1] || null;
    }
  },
  outerHTML: {
    get () {
      const name = this.tagName.toLowerCase();
      const attributes = toArray(this.attributes).map(function (attr) {
        return ` ${attr.name}${attr.value ? `="${attr.value}"` : ''}`;
      }).join('');
      return `<${name}${attributes}>${this.innerHTML}</${name}>`;
    }
  },
  textContent: {
    get () {
      return this.childNodes.map(node => node.textContent).join('');
    },
    set (textContent) {
      while (this.hasChildNodes()) {
        this.removeChild(this.firstChild);
      }
      this.appendChild(document.createTextNode(textContent));
    }
  }
};

// Method overrides.

const funcs = {
  appendChild (newNode) {
    doForNodesIfSlot(this, newNode, function (elem, node, slot) {
      slotFuncs.appendChild(slot, node);
      applyParentNode(node, elem);
    });
    return newNode;
  },
  hasChildNodes () {
    return this.childNodes.length > 0;
  },
  insertBefore (newNode, refNode) {
    doForNodesIfSlot(this, newNode, function (elem, node, slot) {
      slotFuncs.insertBefore(slot, node, refNode);
      applyParentNode(node, elem);
    });
    return newNode;
  },
  removeChild (refNode) {
    doForNodesIfSlot(this, refNode, function (elem, node, slot) {
      slotFuncs.removeChild(slot, node);
      removeParentNode(node);
    });
    return refNode;
  },
  replaceChild (newNode, refNode) {
    // If the ref node is not in the light DOM, just return it.
    if (refNode.parentNode !== this) {
      return refNode;
    }

    // We're dealing with a representation of the light DOM, so we insert nodes
    // relative to the location of the refNode in the light DOM, not the where
    // it appears in the composed DOM.
    const insertBefore = refNode.nextSibling;

    // Clean up the reference node.
    this.removeChild(refNode);

    // Add new nodes in place of the reference node.
    doForNodesIfSlot(this, newNode, function (elem, node, slot) {
      slotFuncs.insertBefore(slot, node, insertBefore);
      applyParentNode(node, elem);
    });

    return refNode;
  }
};

function polyfill (host) {
  assignProps(host, props);
  assignFuncs(host, funcs);
}

export default function (host) {
  if (hostData.polyfilled.get(host)) {
    return;
  }
  polyfill(host);
  hostData.polyfilled.set(host, true);
  return host;
}
<|endoftext|>


<commit_before>"use strict";
module.exports = function() {
var ASSERT = require("./assert.js");
var inherits = require( "./util.js").inherits;

var rignore = new RegExp(
    "\\b(?:Promise(?:Array|Spawn)?\\$_\\w+|tryCatch(?:1|2|Apply)|setTimeout" +
    "|makeNodePromisified|processImmediate|nextTick" +
    "|Async\\$\\w+)\\b"
);

var rtraceline = null;
var formatStack = null;
var areNamesMangled = false;

function CapturedTrace( ignoreUntil, isTopLevel ) {
    ASSERT( typeof ignoreUntil === "function" );
    if( !areNamesMangled ) {
        ASSERT( typeof ignoreUntil.name === "string" );
        //Polyfills for V8's stacktrace API work on strings
        //instead of function identities so the function must have
        //an unique name
        ASSERT( ignoreUntil.name.length > 0 );
    }
    this.captureStackTrace( ignoreUntil, isTopLevel );

}
inherits( CapturedTrace, Error );

CapturedTrace.prototype.captureStackTrace =
function CapturedTrace$captureStackTrace( ignoreUntil, isTopLevel ) {
    captureStackTrace( this, ignoreUntil, isTopLevel );
};

CapturedTrace.possiblyUnhandledRejection =
function CapturedTrace$PossiblyUnhandledRejection( reason ) {
    if( typeof console === "object" ) {
        var stack = reason.stack;
        var message = "Possibly unhandled " + formatStack( stack, reason );
        if( typeof console.error === "function" ) {
            console.error( message );
        }
        else if( typeof console.log === "function" ) {
            console.log( message );
        }
    }
};

areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
    "CapturedTrace$captureStackTrace";

CapturedTrace.combine = function CapturedTrace$Combine( current, prev ) {
    var curLast = current.length - 1;
    //Eliminate common roots
    for( var i = prev.length - 1; i >= 0; --i ) {
        var line = prev[i];
        if( current[ curLast ] === line ) {
            current.pop();
            curLast--;
        }
        else {
            break;
        }
    }
    current.push( FROM_PREVIOUS_EVENT );
    var lines = current.concat( prev );

    var ret = [];

    //Eliminate library internal stuff and async callers
    //that nobody cares about
    for( var i = 0, len = lines.length; i < len; ++i ) {

        if( ( rignore.test( lines[i] ) ||
            ( i > 0 && !rtraceline.test( lines[i] ) ) &&
            lines[i] !== FROM_PREVIOUS_EVENT )
        ) {
            continue;
        }
        ret.push( lines[i] );
    }
    return ret;
};

CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
    return typeof captureStackTrace === "function";
};

var captureStackTrace = (function stackDetection() {
    function formatNonError( obj ) {
        var str = obj.toString();
        if( str === "[object Object]") {
            try {
                var newStr = JSON.stringify(obj);
                str = newStr;
            }
            catch( e ) {

            }
        }
        return ("(<" + str + ">, no stack trace)");
    }

    //V8
    if( typeof Error.stackTraceLimit === "number" &&
        typeof Error.captureStackTrace === "function" ) {
        rtraceline = /^\s*at\s*/;
        formatStack = function( stack, error ) {
            ASSERT( typeof error === "object");
            ASSERT( error !== null );

            if( typeof stack === "string" ) return stack;

            if( error.name !== void 0 &&
                error.message !== void 0 ) {
                return error.name + ". " + error.message;
            }
            return formatNonError( error );

        };
        var captureStackTrace = Error.captureStackTrace;
        return function CapturedTrace$_captureStackTrace(
            receiver, ignoreUntil, isTopLevel ) {
            var prev = -1;
            if( !isTopLevel ) {
                prev = Error.stackTraceLimit;
                Error.stackTraceLimit =
                    Math.max(1, Math.min(10000, prev) / 3 | 0);
            }
            captureStackTrace( receiver, ignoreUntil );

            if( !isTopLevel ) {
                Error.stackTraceLimit = prev;
            }
        };
    }
    var err = new Error();

    //SpiderMonkey
    //Relies on .name strings which must not be mangled
    if( !areNamesMangled && typeof err.stack === "string" &&
        typeof "".startsWith === "function" &&
        ( err.stack.startsWith("stackDetection@")) &&
        stackDetection.name === "stackDetection" ) {

        Object.defineProperty( Error, "stackTraceLimit", {
            writable: true,
            enumerable: false,
            configurable: false,
            value: 25
        });
        rtraceline = /@/;
        var rline = /[@\n]/;

        formatStack = function( stack, error ) {
            if( typeof stack === "string" ) {
                return ( error.name + ". " + error.message + "\n" + stack );
            }

            if( error.name !== void 0 &&
                error.message !== void 0 ) {
                return error.name + ". " + error.message;
            }
            return formatNonError( error );
        };

        return function captureStackTrace(o, fn) {
            var name = fn.name;
            var stack = new Error().stack;
            var split = stack.split( rline );
            var i, len = split.length;
            for (i = 0; i < len; i += 2) {
                if (split[i] === name) {
                    break;
                }
            }
            ASSERT( i + 2 < split.length );
            split = split.slice(i + 2);
            len = split.length - 2;
            var ret = "";
            for (i = 0; i < len; i += 2) {
                ret += split[i];
                ret += "@";
                ret += split[i + 1];
                ret += "\n";
            }
            o.stack = ret;
        };
    }
    else {
        return null;
    }
})();

return CapturedTrace;
};<commit_msg>better formatting of non errors<commit_after>"use strict";
module.exports = function() {
var ASSERT = require("./assert.js");
var inherits = require( "./util.js").inherits;

var rignore = new RegExp(
    "\\b(?:Promise(?:Array|Spawn)?\\$_\\w+|tryCatch(?:1|2|Apply)|setTimeout" +
    "|makeNodePromisified|processImmediate|nextTick" +
    "|Async\\$\\w+)\\b"
);

var rtraceline = null;
var formatStack = null;
var areNamesMangled = false;

function CapturedTrace( ignoreUntil, isTopLevel ) {
    ASSERT( typeof ignoreUntil === "function" );
    if( !areNamesMangled ) {
        ASSERT( typeof ignoreUntil.name === "string" );
        //Polyfills for V8's stacktrace API work on strings
        //instead of function identities so the function must have
        //an unique name
        ASSERT( ignoreUntil.name.length > 0 );
    }
    this.captureStackTrace( ignoreUntil, isTopLevel );

}
inherits( CapturedTrace, Error );

CapturedTrace.prototype.captureStackTrace =
function CapturedTrace$captureStackTrace( ignoreUntil, isTopLevel ) {
    captureStackTrace( this, ignoreUntil, isTopLevel );
};

CapturedTrace.possiblyUnhandledRejection =
function CapturedTrace$PossiblyUnhandledRejection( reason ) {
    if( typeof console === "object" ) {
        var stack = reason.stack;
        var message = "Possibly unhandled " + formatStack( stack, reason );
        if( typeof console.error === "function" ) {
            console.error( message );
        }
        else if( typeof console.log === "function" ) {
            console.log( message );
        }
    }
};

areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
    "CapturedTrace$captureStackTrace";

CapturedTrace.combine = function CapturedTrace$Combine( current, prev ) {
    var curLast = current.length - 1;
    //Eliminate common roots
    for( var i = prev.length - 1; i >= 0; --i ) {
        var line = prev[i];
        if( current[ curLast ] === line ) {
            current.pop();
            curLast--;
        }
        else {
            break;
        }
    }
    current.push( FROM_PREVIOUS_EVENT );
    var lines = current.concat( prev );

    var ret = [];

    //Eliminate library internal stuff and async callers
    //that nobody cares about
    for( var i = 0, len = lines.length; i < len; ++i ) {

        if( ( rignore.test( lines[i] ) ||
            ( i > 0 && !rtraceline.test( lines[i] ) ) &&
            lines[i] !== FROM_PREVIOUS_EVENT )
        ) {
            continue;
        }
        ret.push( lines[i] );
    }
    return ret;
};

CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
    return typeof captureStackTrace === "function";
};

var captureStackTrace = (function stackDetection() {
    function snip( str ) {
        var maxChars = 41;
        if( str.length < maxChars ) {
            return str;
        }
        return str.substr(0, maxChars - 3) + "...";
    }

    function formatNonError( obj ) {
        var str = obj.toString();
        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
        if( ruselessToString.test( str ) ) {
            try {
                var newStr = JSON.stringify(obj);
                str = newStr;
            }
            catch( e ) {

            }
        }
        return ("(<" + snip( str ) + ">, no stack trace)");
    }

    //V8
    if( typeof Error.stackTraceLimit === "number" &&
        typeof Error.captureStackTrace === "function" ) {
        rtraceline = /^\s*at\s*/;
        formatStack = function( stack, error ) {
            ASSERT( typeof error === "object");
            ASSERT( error !== null );

            if( typeof stack === "string" ) return stack;

            if( error.name !== void 0 &&
                error.message !== void 0 ) {
                return error.name + ". " + error.message;
            }
            return formatNonError( error );

        };
        var captureStackTrace = Error.captureStackTrace;
        return function CapturedTrace$_captureStackTrace(
            receiver, ignoreUntil, isTopLevel ) {
            var prev = -1;
            if( !isTopLevel ) {
                prev = Error.stackTraceLimit;
                Error.stackTraceLimit =
                    Math.max(1, Math.min(10000, prev) / 3 | 0);
            }
            captureStackTrace( receiver, ignoreUntil );

            if( !isTopLevel ) {
                Error.stackTraceLimit = prev;
            }
        };
    }
    var err = new Error();

    //SpiderMonkey
    //Relies on .name strings which must not be mangled
    if( !areNamesMangled && typeof err.stack === "string" &&
        typeof "".startsWith === "function" &&
        ( err.stack.startsWith("stackDetection@")) &&
        stackDetection.name === "stackDetection" ) {

        Object.defineProperty( Error, "stackTraceLimit", {
            writable: true,
            enumerable: false,
            configurable: false,
            value: 25
        });
        rtraceline = /@/;
        var rline = /[@\n]/;

        formatStack = function( stack, error ) {
            if( typeof stack === "string" ) {
                return ( error.name + ". " + error.message + "\n" + stack );
            }

            if( error.name !== void 0 &&
                error.message !== void 0 ) {
                return error.name + ". " + error.message;
            }
            return formatNonError( error );
        };

        return function captureStackTrace(o, fn) {
            var name = fn.name;
            var stack = new Error().stack;
            var split = stack.split( rline );
            var i, len = split.length;
            for (i = 0; i < len; i += 2) {
                if (split[i] === name) {
                    break;
                }
            }
            ASSERT( i + 2 < split.length );
            split = split.slice(i + 2);
            len = split.length - 2;
            var ret = "";
            for (i = 0; i < len; i += 2) {
                ret += split[i];
                ret += "@";
                ret += split[i + 1];
                ret += "\n";
            }
            o.stack = ret;
        };
    }
    else {
        return null;
    }
})();

return CapturedTrace;
};
<|endoftext|>


<commit_before>import { dispatch  as createDispatch }  from 'd3-dispatch'
import { modulo } from '@zambezi/fun'
import { rebind, keyCodeHandler } from '@zambezi/d3-utils'
import { reduce, indexBy, findIndex, range, debounce, map, uniqueId } from 'underscore'
import { select, event } from 'd3-selection'
import { someResult as some } from '@zambezi/fun'
import { unwrap } from '@zambezi/grid'

import './cell-selection.css'

export function createCellSelection() {

  const dispatch = createDispatch(
          'cell-selected-change'
        , 'cell-active-action'
        , 'cell-active-change'
        , 'cell-active-paste'
        )
      , pasteId = uniqueId('paste.')

  let gesture = 'click'
    , selected = []
    , selectedCandidates
    , selectedRowsByColumnId = {}
    , acceptPasteFrom = []
    , active
    , selectable = true
    , trackPaste = true
    , typeToActivate = true

  function cellSelection(s) {
    s.each(cellSelectionEach)
  }

  cellSelection.selected = function(value) {
    if (!arguments.length) return selected
    selected = value
    selectedCandidates = value
    return cellSelection
  }

  cellSelection.trackPaste = function(value) {
    if (!arguments.length) return trackPaste
    trackPaste = value
    return cellSelection
  }

  cellSelection.acceptPasteFrom = function(value) {
    if (!arguments.length) return acceptPasteFrom
    acceptPasteFrom = value
    return cellSelection
  }

  cellSelection.typeToActivate = function(value) {
    if (!arguments.length) return typeToActivate
    typeToActivate = value
    return cellSelection
  }

  cellSelection.selectable = function(value) {
    if (!arguments.length) return selectable
    selectable = value
    return cellSelection
  }

  cellSelection.active = function(value) {
    if (!arguments.length) return active
    active = value
    return cellSelection
  }

  cellSelection.rowChangedKey = function(targetRow) {
    const unwrappedRow = unwrap(targetRow)
        , selectedColumnIds = []
    let key = ''

    selected.forEach(addSelected)

    function addSelected({row, column}) {
      if (unwrappedRow !== row) return
      selectedColumnIds.push(column.id)
    }

    key = selectedColumnIds.join('↑')

    if (active && unwrappedRow === active.row) {
      key += ('☆' + active.column.id)
    }
    return key
  }

  return rebind().from(dispatch, 'on')(cellSelection)

  function cellSelectionEach(bundle, i) {
    const target = select(this)
        , columns = bundle.columns
        , rows = bundle.rows
        , columnById = indexBy(columns, 'id')

    setupPasteEvents()
    setupKeyboardNavEvents()

    if (selectedCandidates) updateFromCandidates()

    bundle.dispatcher
        .on('cell-enter.cell-selection', onCellEnter)
        .on('cell-update.cell-selection', onCellUpdate)

    function moveHorizontal(step) {
      if (!active) return

      const { column, row } = active
          , currentColumnIndex = columns.indexOf(column)
          , newColumn = columns[modulo(currentColumnIndex + step, columns.length)]

      setActive({ row, column: newColumn })
      target.dispatch('redraw', { bubbles: true })
    }

    function moveVertical(step, rows) {

      if (!active) return

      const { column, row } = active
          , currentRowIndex = findIndex(rows, r => unwrap(r) === row)
          , newRow = rows[modulo(currentRowIndex + step, rows.length)]

      setActive({ row: newRow, column })
      target.dispatch('redraw', { bubbles: true })
    }

    function setActiveIfNone(d) {
      if (active) return
      if (!bundle.length) return
      if (!columns.length) return
      setActive({ row: bundle[0], column: columns[0] })
      select(this).dispatch('redraw', { bubbles: true })
    }

    function updateFromCandidates() {
      selectedRowsByColumnId = selectedCandidates.reduce(toRealSelection, {})
      selected = compileSelected()
      selectedCandidates = null
    }

    function toRealSelection(acc, { row, column }) {
      const columnFound = typeof column == 'string' ? columnById[column] : column
          , rowFound = typeof row == 'number' ? bundle[row] : row

      if (!columnFound || !rowFound) {
        console.warn("Couldn't find cell for", { row, column })
        return acc
      }

      const columnId = columnFound.id
          , set = acc[columnId] || new Set()

      set.add(rowFound)
      acc[columnId] = set
      return acc
    }

    function compileSelected() {
      return reduce(selectedRowsByColumnId, toCells, [])
    }

    function setupKeyboardNavEvents() {
      target.attr('tabindex', '0')
          .on('focus', debounce(setActiveIfNone, 200))
          .on(
            'keydown.keyboard-cell-selection'
          , some(
              keyCodeHandler(() => dispatch.call('cell-active-action', this, active), 13) // enter
            , keyCodeHandler(d => moveVertical(-1, d.rows), 38) // up
            , keyCodeHandler(d => moveVertical(1, d.rows), 40)  // down
            , keyCodeHandler(() => moveHorizontal(-1), 37)      // left
            , keyCodeHandler(() => moveHorizontal(1), 39)       // right
            , keyCodeHandler(onTab, 9)
            , activateFromInput
            )
          )
    }

    function setupPasteEvents() {
      select(document).on(pasteId, trackPaste ? onPaste : null)
    }

    function activateFromInput(d) {
      const { key } = event

      if (!typeToActivate) return
      if (!active) return

      if (isASpecialKey(key)) return

      dispatch.call('cell-active-action', target.node(), active, key)
    }

    function onPaste() {
      const targetNode = target.node()
          , allAcceptedNodes = (acceptPasteFrom || []).concat(targetNode)

      if (!allAcceptedNodes.some(n => n.contains(document.activeElement))) return
      dispatch.call('cell-active-paste', targetNode, active, event.clipboardData)
    }

    function onTab(d) {
      moveHorizontal(event.shiftKey ? -1 : 1)
      event.preventDefault()
    }

    function onCellEnter(d, i) {
      select(this).on('click.cell-selection', onClick)
    }

    function onClick(d, i) {
      const column = d.column
          , columnId = column.id
          , set = selectedRowsByColumnId[columnId]
          , row = unwrap(d.row)
          , { shiftKey, ctrlKey } = event
          , target = { row, column }
          , isAlreadySelected = set && set.has(row)
          , hasActive = !!active

      if (selectable) {
        switch(true) {
          case ctrlKey && shiftKey && hasActive:
            addRangeToSelection(active, target)
            break

          case shiftKey && hasActive:
            setSelectionToRange(active, target)
            break

          case ctrlKey && isAlreadySelected:
            removeFromSelected(target)
            break

          case ctrlKey:
            addToSelected(target)
            break

          default:
            selectOnly(target)
        }

        selected = compileSelected()
        dispatch.call('cell-selected-change', this, selected, active)
      }

      setActive(target)
      select(this).dispatch('redraw', { bubbles: true })
    }

    function setActive({ row, column}) {
      active = { row: unwrap(row), column }
      dispatch.call('cell-active-change', this, active)
    }

    function setSelectionToRange(a, b) {
      selectedRowsByColumnId = {}
      rangeFrom(a, b).forEach(addToSelected)
    }

    function addRangeToSelection(a, b) {
      rangeFrom(a, b).forEach(addToSelected)
    }

    function rangeFrom(a, b) {
      const columnRange = rangeFromUnorderedIndices(
              columns
            , columns.indexOf(a.column)
            , columns.indexOf(b.column)
            )
          , rowRange = rangeFromUnorderedIndices(
              rows
            , findIndex(rows, r => unwrap(r) == a.row)
            , findIndex(rows, r => unwrap(r) == b.row)
            ).map(unwrap)

      return rowRange.reduce(allCellsInColumns, [])

      function allCellsInColumns(acc, row) {
        return acc.concat(columnRange.map(column => ({ column, row })))
      }
    }

    function selectOnly(target) {
      selectedRowsByColumnId = {}
      addToSelected(target)
    }

    function rangeFromUnorderedIndices(array, i1, i2) {
      return array.slice(
        Math.min(i1, i2)
      , Math.max(i1, i2) + 1
      )
    }

    function removeFromSelected({ row, column }) {
      const columnId = column.id
          , set = selectedRowsByColumnId[columnId]

      if (set) set.delete(row)
    }

    function addToSelected({ row, column }) {
      const columnId = column.id
          , set = selectedRowsByColumnId[columnId] || new Set()

      set.add(row)
      selectedRowsByColumnId[columnId] = set
    }

    function toCells(acc, set, columnId) {
      const column = columnById[columnId]
      return acc.concat(Array.from(set.values()).map(row => ({ row, column })))
    }
  }

  function onCellUpdate(d, i){
    select(this)
        .classed('is-selected', isCellSelected)
        .classed('is-active', isCellActive)
  }

  function isCellSelected(d, i) {
    const rowSetForColumn = selectedRowsByColumnId[d.column.id]
    return rowSetForColumn && rowSetForColumn.has(unwrap(d.row))
  }

  function isCellActive(d) {
    if (!active) return false
    return areSameCell(d, active)
  }
}

function isASpecialKey(key) {
  return key.length > 1
}

function areSameCell(a, b) {
  return a && b && unwrap(a.row) == unwrap(b.row) && a.column == b.column
}

<commit_msg>Prevent paste, etc. from activating a cell.<commit_after>import { dispatch  as createDispatch }  from 'd3-dispatch'
import { modulo } from '@zambezi/fun'
import { rebind, keyCodeHandler } from '@zambezi/d3-utils'
import { reduce, indexBy, findIndex, range, debounce, map, uniqueId } from 'underscore'
import { select, event } from 'd3-selection'
import { someResult as some } from '@zambezi/fun'
import { unwrap } from '@zambezi/grid'

import './cell-selection.css'

export function createCellSelection() {

  const dispatch = createDispatch(
          'cell-selected-change'
        , 'cell-active-action'
        , 'cell-active-change'
        , 'cell-active-paste'
        )
      , pasteId = uniqueId('paste.')

  let gesture = 'click'
    , selected = []
    , selectedCandidates
    , selectedRowsByColumnId = {}
    , acceptPasteFrom = []
    , active
    , selectable = true
    , trackPaste = true
    , typeToActivate = true

  function cellSelection(s) {
    s.each(cellSelectionEach)
  }

  cellSelection.selected = function(value) {
    if (!arguments.length) return selected
    selected = value
    selectedCandidates = value
    return cellSelection
  }

  cellSelection.trackPaste = function(value) {
    if (!arguments.length) return trackPaste
    trackPaste = value
    return cellSelection
  }

  cellSelection.acceptPasteFrom = function(value) {
    if (!arguments.length) return acceptPasteFrom
    acceptPasteFrom = value
    return cellSelection
  }

  cellSelection.typeToActivate = function(value) {
    if (!arguments.length) return typeToActivate
    typeToActivate = value
    return cellSelection
  }

  cellSelection.selectable = function(value) {
    if (!arguments.length) return selectable
    selectable = value
    return cellSelection
  }

  cellSelection.active = function(value) {
    if (!arguments.length) return active
    active = value
    return cellSelection
  }

  cellSelection.rowChangedKey = function(targetRow) {
    const unwrappedRow = unwrap(targetRow)
        , selectedColumnIds = []
    let key = ''

    selected.forEach(addSelected)

    function addSelected({row, column}) {
      if (unwrappedRow !== row) return
      selectedColumnIds.push(column.id)
    }

    key = selectedColumnIds.join('↑')

    if (active && unwrappedRow === active.row) {
      key += ('☆' + active.column.id)
    }
    return key
  }

  return rebind().from(dispatch, 'on')(cellSelection)

  function cellSelectionEach(bundle, i) {
    const target = select(this)
        , columns = bundle.columns
        , rows = bundle.rows
        , columnById = indexBy(columns, 'id')

    setupPasteEvents()
    setupKeyboardNavEvents()

    if (selectedCandidates) updateFromCandidates()

    bundle.dispatcher
        .on('cell-enter.cell-selection', onCellEnter)
        .on('cell-update.cell-selection', onCellUpdate)

    function moveHorizontal(step) {
      if (!active) return

      const { column, row } = active
          , currentColumnIndex = columns.indexOf(column)
          , newColumn = columns[modulo(currentColumnIndex + step, columns.length)]

      setActive({ row, column: newColumn })
      target.dispatch('redraw', { bubbles: true })
    }

    function moveVertical(step, rows) {

      if (!active) return

      const { column, row } = active
          , currentRowIndex = findIndex(rows, r => unwrap(r) === row)
          , newRow = rows[modulo(currentRowIndex + step, rows.length)]

      setActive({ row: newRow, column })
      target.dispatch('redraw', { bubbles: true })
    }

    function setActiveIfNone(d) {
      if (active) return
      if (!bundle.length) return
      if (!columns.length) return
      setActive({ row: bundle[0], column: columns[0] })
      select(this).dispatch('redraw', { bubbles: true })
    }

    function updateFromCandidates() {
      selectedRowsByColumnId = selectedCandidates.reduce(toRealSelection, {})
      selected = compileSelected()
      selectedCandidates = null
    }

    function toRealSelection(acc, { row, column }) {
      const columnFound = typeof column == 'string' ? columnById[column] : column
          , rowFound = typeof row == 'number' ? bundle[row] : row

      if (!columnFound || !rowFound) {
        console.warn("Couldn't find cell for", { row, column })
        return acc
      }

      const columnId = columnFound.id
          , set = acc[columnId] || new Set()

      set.add(rowFound)
      acc[columnId] = set
      return acc
    }

    function compileSelected() {
      return reduce(selectedRowsByColumnId, toCells, [])
    }

    function setupKeyboardNavEvents() {
      target.attr('tabindex', '0')
          .on('focus', debounce(setActiveIfNone, 200))
          .on(
            'keydown.keyboard-cell-selection'
          , some(
              keyCodeHandler(() => dispatch.call('cell-active-action', this, active), 13) // enter
            , keyCodeHandler(d => moveVertical(-1, d.rows), 38) // up
            , keyCodeHandler(d => moveVertical(1, d.rows), 40)  // down
            , keyCodeHandler(() => moveHorizontal(-1), 37)      // left
            , keyCodeHandler(() => moveHorizontal(1), 39)       // right
            , keyCodeHandler(onTab, 9)
            , activateFromInput
            )
          )
    }

    function setupPasteEvents() {
      select(document).on(pasteId, trackPaste ? onPaste : null)
    }

    function activateFromInput(d) {
      const { key, ctrlKey, altKey, metaKey } = event

      if (!typeToActivate) return
      if (ctrlKey || altKey || metaKey) return
      if (!active) return
      if (isASpecialKey(key)) return

      dispatch.call('cell-active-action', target.node(), active, key)
    }

    function onPaste() {

      const targetNode = target.node()
          , allAcceptedNodes = (acceptPasteFrom || []).concat(targetNode)

      if (!allAcceptedNodes.some(n => n.contains(document.activeElement))) return
      dispatch.call('cell-active-paste', targetNode, active, event.clipboardData)
    }

    function onTab(d) {
      moveHorizontal(event.shiftKey ? -1 : 1)
      event.preventDefault()
    }

    function onCellEnter(d, i) {
      select(this).on('click.cell-selection', onClick)
    }

    function onClick(d, i) {
      const column = d.column
          , columnId = column.id
          , set = selectedRowsByColumnId[columnId]
          , row = unwrap(d.row)
          , { shiftKey, ctrlKey } = event
          , target = { row, column }
          , isAlreadySelected = set && set.has(row)
          , hasActive = !!active

      if (selectable) {
        switch(true) {
          case ctrlKey && shiftKey && hasActive:
            addRangeToSelection(active, target)
            break

          case shiftKey && hasActive:
            setSelectionToRange(active, target)
            break

          case ctrlKey && isAlreadySelected:
            removeFromSelected(target)
            break

          case ctrlKey:
            addToSelected(target)
            break

          default:
            selectOnly(target)
        }

        selected = compileSelected()
        dispatch.call('cell-selected-change', this, selected, active)
      }

      setActive(target)
      select(this).dispatch('redraw', { bubbles: true })
    }

    function setActive({ row, column}) {
      active = { row: unwrap(row), column }
      dispatch.call('cell-active-change', this, active)
    }

    function setSelectionToRange(a, b) {
      selectedRowsByColumnId = {}
      rangeFrom(a, b).forEach(addToSelected)
    }

    function addRangeToSelection(a, b) {
      rangeFrom(a, b).forEach(addToSelected)
    }

    function rangeFrom(a, b) {
      const columnRange = rangeFromUnorderedIndices(
              columns
            , columns.indexOf(a.column)
            , columns.indexOf(b.column)
            )
          , rowRange = rangeFromUnorderedIndices(
              rows
            , findIndex(rows, r => unwrap(r) == a.row)
            , findIndex(rows, r => unwrap(r) == b.row)
            ).map(unwrap)

      return rowRange.reduce(allCellsInColumns, [])

      function allCellsInColumns(acc, row) {
        return acc.concat(columnRange.map(column => ({ column, row })))
      }
    }

    function selectOnly(target) {
      selectedRowsByColumnId = {}
      addToSelected(target)
    }

    function rangeFromUnorderedIndices(array, i1, i2) {
      return array.slice(
        Math.min(i1, i2)
      , Math.max(i1, i2) + 1
      )
    }

    function removeFromSelected({ row, column }) {
      const columnId = column.id
          , set = selectedRowsByColumnId[columnId]

      if (set) set.delete(row)
    }

    function addToSelected({ row, column }) {
      const columnId = column.id
          , set = selectedRowsByColumnId[columnId] || new Set()

      set.add(row)
      selectedRowsByColumnId[columnId] = set
    }

    function toCells(acc, set, columnId) {
      const column = columnById[columnId]
      return acc.concat(Array.from(set.values()).map(row => ({ row, column })))
    }
  }

  function onCellUpdate(d, i){
    select(this)
        .classed('is-selected', isCellSelected)
        .classed('is-active', isCellActive)
  }

  function isCellSelected(d, i) {
    const rowSetForColumn = selectedRowsByColumnId[d.column.id]
    return rowSetForColumn && rowSetForColumn.has(unwrap(d.row))
  }

  function isCellActive(d) {
    if (!active) return false
    return areSameCell(d, active)
  }
}

function isASpecialKey(key) {
  return key.length > 1
}

function areSameCell(a, b) {
  return a && b && unwrap(a.row) == unwrap(b.row) && a.column == b.column
}

<|endoftext|>