text: /**
   * @file
   * Defines the behaviors needed for cropper integration.
   */
  
  (function ($, Drupal) {
    'use strict';
  
    /**
     * @class Drupal.ImageWidgetCropType
     *
     * @param {Drupal.ImageWidgetCrop} instance
     *   The main ImageWidgetCrop instance that created this one.
     *
     * @param {HTMLElement|jQuery} element
     *   The wrapper element.
     */
    Drupal.ImageWidgetCropType = function (instance, element) {
  
      /**
       * The ImageWidgetCrop instance responsible for creating this type.
       *
       * @type {Drupal.ImageWidgetCrop}
       */
      this.instance = instance;
  
      /**
       * The Cropper plugin wrapper element.
       *
       * @type {jQuery}
       */
      this.$cropperWrapper = $();
  
      /**
       * The wrapper element.
       *
       * @type {jQuery}
       */
      this.$wrapper = $(element);
  
      /**
       * The table element, if any.
       *
       * @type {jQuery}
       */
      this.$table = this.$wrapper.find(this.selectors.table);
  
      /**
       * The image element.
       *
       * @type {jQuery}
       */
      this.$image = this.$wrapper.find(this.selectors.image);
  
      /**
       * The reset element.
       *
       * @type {jQuery}
       */
      this.$reset = this.$wrapper.find(this.selectors.reset);
  
      /**
       * @type {Cropper}
       */
      this.cropper = null;
  
      /**
       * Flag indicating whether this instance is enabled.
       *
       * @type {Boolean}
       */
      this.enabled = true;
  
      /**
       * The hard limit of the crop.
       *
       * @type {{height: Number, width: Number, reached: {height: Boolean, width: Boolean}}}
       */
      this.hardLimit = {
        height: null,
        width: null,
        reached: {
          height: false,
          width: false
        }
      };
  
      /**
       * The unique identifier for this ImageWidgetCrop type.
       *
       * @type {String}
       */
      this.id = null;
  
      /**
       * Flag indicating whether the instance has been initialized.
       *
       * @type {Boolean}
       */
      this.initialized = false;
  
      /**
       * An object of recorded setInterval instances.
       *
       * @type {Object.<Number, jQuery>}
       */
      this.intervals = {};
  
      /**
       * The delta ratio of image based on its natural dimensions.
       *
       * @type {Number}
       */
      this.naturalDelta = null;
  
      /**
       * The natural height of the image.
       *
       * @type {Number}
       */
      this.naturalHeight = null;
  
      /**
       * The natural width of the image.
       *
       * @type {Number}
       */
      this.naturalWidth = null;
  
      /**
       * The original height of the image.
       *
       * @type {Number}
       */
      this.originalHeight = 0;
  
      /**
       * The original width of the image.
       *
       * @type {Number}
       */
      this.originalWidth = 0;
  
      /**
       * The current Cropper options.
       *
       * @type {Cropper.options}
       */
      this.options = {};
  
      /**
       * Flag indicating whether to show the default crop.
       *
       * @type {Boolean}
       */
      this.showDefaultCrop = true;
  
      /**
       * The soft limit of the crop.
       *
       * @type {{height: Number, width: Number, reached: {height: Boolean, width: Boolean}}}
       */
      this.softLimit = {
        height: null,
        width: null,
        reached: {
          height: false,
          width: false
        }
      };
  
      /**
       * The numeric representation of a ratio.
       *
       * @type {Number}
       */
      this.ratio = NaN;
  
      /**
       * The value elements.
       *
       * @type {Object.<String, jQuery>}
       */
      this.values = {
        applied: this.$wrapper.find(this.selectors.values.applied),
        height: this.$wrapper.find(this.selectors.values.height),
        width: this.$wrapper.find(this.selectors.values.width),
        x: this.$wrapper.find(this.selectors.values.x),
        y: this.$wrapper.find(this.selectors.values.y)
      };
  
      /**
       * Flag indicating whether the instance is currently visible.
       *
       * @type {Boolean}
       */
      this.visible = false;
  
      // Initialize the instance.
      this.init();
    };
  
    /**
     * The prefix used for all Image Widget Crop data attributes.
     *
     * @type {RegExp}
     */
    Drupal.ImageWidgetCropType.prototype.dataPrefix = /^drupalIwc/;
  
    /**
     * Default options to pass to the Cropper plugin.
     *
     * @type {Object}
     */
    Drupal.ImageWidgetCropType.prototype.defaultOptions = {
      autoCropArea: 1,
      background: false,
      responsive: false,
      viewMode: 1,
      zoomable: false
    };
  
    /**
     * The selectors used to identify elements for this module.
     *
     * @type {Object}
     */
    Drupal.ImageWidgetCropType.prototype.selectors = {
      image: '[data-drupal-iwc=image]',
      reset: '[data-drupal-iwc=reset]',
      table: '[data-drupal-iwc=table]',
      values: {
        applied: '[data-drupal-iwc-value=applied]',
        height: '[data-drupal-iwc-value=height]',
        width: '[data-drupal-iwc-value=width]',
        x: '[data-drupal-iwc-value=x]',
        y: '[data-drupal-iwc-value=y]'
      }
    };
  
    /**
     * The "built" event handler for the Cropper plugin.
     */
    Drupal.ImageWidgetCropType.prototype.built = function () {
      this.$cropperWrapper = this.$wrapper.find('.cropper-container');
      this.updateHardLimits();
      this.updateSoftLimits();
    };
  
    /**
     * The "cropend" event handler for the Cropper plugin.
     */
    Drupal.ImageWidgetCropType.prototype.cropEnd = function () {
      // Immediately return if there is no cropper instance (for whatever reason).
      if (!this.cropper) {
        return;
      }
  
      // Retrieve the cropper data.
      var data = this.cropper.getData();
  
      // Ensure the applied state is enabled.
      data.applied = 1;
  
      // Data returned by Cropper plugin should be multiplied with delta in order
      // to get the proper crop sizes for the original image.
      this.setValues(data, this.naturalDelta);
  
      // Trigger summary updates.
      this.$wrapper.trigger('summaryUpdated');
    };
  
    /**
     * The "cropmove" event handler for the Cropper plugin.
     */
    Drupal.ImageWidgetCropType.prototype.cropMove = function () {
      this.updateSoftLimits();
    };
  
    /**
     * Destroys this instance.
     */
    Drupal.ImageWidgetCropType.prototype.destroy = function () {
      this.destroyCropper();
  
      this.$image.off('.iwc');
      this.$reset.off('.iwc');
  
      // Clear any intervals that were set.
      for (var interval in this.intervals) {
        if (this.intervals.hasOwnProperty(interval)) {
          clearInterval(interval);
          delete this.intervals[interval];
        }
      }
    };
  
    /**
     * Destroys the Cropper plugin instance.
     */
    Drupal.ImageWidgetCropType.prototype.destroyCropper = function () {
      this.$image.off('.iwc.cropper');
      if (this.cropper) {
        this.cropper.destroy();
        this.cropper = null;
      }
    };
  
    /**
     * Disables this instance.
     */
    Drupal.ImageWidgetCropType.prototype.disable = function () {
      if (this.cropper) {
        this.cropper.disable();
      }
      this.$table.removeClass('responsive-enabled--opened');
    };
  
    /**
     * Enables this instance.
     */
    Drupal.ImageWidgetCropType.prototype.enable = function () {
      if (this.cropper) {
        this.cropper.enable();
      }
      this.$table.addClass('responsive-enabled--opened');
    };
  
    /**
     * Retrieves a crop value.
     *
     * @param {'applied'|'height'|'width'|'x'|'y'} name
     *   The name of the crop value to retrieve.
     * @param {Number} [delta]
     *   The delta amount to divide value by, if any.
     *
     * @return {Number}
     *   The crop value.
     */
    Drupal.ImageWidgetCropType.prototype.getValue = function (name, delta) {
      var value = 0;
      if (this.values[name] && this.values[name][0]) {
        value = parseInt(this.values[name][0].value, 10) || 0;
      }
      return name !== 'applied' && value && delta ? Math.round(value / delta) : value;
    };
  
    /**
     * Retrieves all crop values.
     *
     * @param {Number} [delta]
     *   The delta amount to divide value by, if any.
     *
     * @return {{applied: Number, height: Number, width: Number, x: Number, y: Number}}
     *   The crop value key/value pairs.
     */
    Drupal.ImageWidgetCropType.prototype.getValues = function (delta) {
      var values = {};
      for (var name in this.values) {
        if (this.values.hasOwnProperty(name)) {
          values[name] = this.getValue(name, delta);
        }
      }
      return values;
    };
  
    /**
     * Initializes the instance.
     */
    Drupal.ImageWidgetCropType.prototype.init = function () {
      // Immediately return if already initialized.
      if (this.initialized) {
        return;
      }
  
      // Set the default options.
      this.options = $.extend({}, this.defaultOptions);
  
      // Extend this instance with data from the wrapper.
      var data = this.$wrapper.data();
      for (var i in data) {
        if (data.hasOwnProperty(i) && this.dataPrefix.test(i)) {
          // Remove Drupal + module prefix and lowercase the first letter.
          var prop = i.replace(this.dataPrefix, '');
          prop = prop.charAt(0).toLowerCase() + prop.slice(1);
  
          // Check if data attribute exists on this object.
          if (prop && this.hasOwnProperty(prop)) {
            var value = data[i];
  
            // Parse the ratio value.
            if (prop === 'ratio') {
              value = this.parseRatio(value);
            }
            this[prop] = typeof value === 'object' ? $.extend(true, {}, this[prop], value) : value;
          }
        }
      }
  
      // Bind necessary events.
      this.$image
        .on('visible.iwc', function () {
          this.visible = true;
          this.naturalHeight = parseInt(this.$image.prop('naturalHeight'), 10);
          this.naturalWidth = parseInt(this.$image.prop('naturalWidth'), 10);
          // Calculate delta between original and thumbnail images.
          this.naturalDelta = this.originalHeight && this.naturalHeight ? this.originalHeight / this.naturalHeight : null;
        }.bind(this))
        // Only initialize the cropper plugin once.
        .one('visible.iwc', this.initializeCropper.bind(this))
        .on('hidden.iwc', function () {
          this.visible = false;
        }.bind(this));
  
      this.$reset
        .on('click.iwc', this.reset.bind(this));
  
      // Star polling visibility of the image that should be able to be cropped.
      this.pollVisibility(this.$image);
  
      // Bind the drupalSetSummary callback.
      this.$wrapper.drupalSetSummary(this.updateSummary.bind(this));
  
      // Trigger the initial summaryUpdate event.
      this.$wrapper.trigger('summaryUpdated');
      var isIE = /*@cc_on!@*/false || !!document.documentMode;
      if (isIE) {
        var $image = this.$image;
        $('.image-data__crop-wrapper > summary').on('click', function () {
          setTimeout(function () {$image.trigger('visible.iwc')}, 100);
        });
      }
    };
  
    /**
     * Initializes the Cropper plugin.
     */
    Drupal.ImageWidgetCropType.prototype.initializeCropper = function () {
      // Calculate minimal height for cropper container (minimal width is 200).
      var minDelta = (this.originalWidth / 200);
      this.options.minContainerHeight = this.originalHeight / minDelta;
  
      // Only autoCrop if 'Show default crop' is checked.
      this.options.autoCrop = this.showDefaultCrop;
  
      // Set aspect ratio.
      this.options.aspectRatio = this.ratio;
  
      // Initialize data.
      var values = this.getValues(this.naturalDelta);
      this.options.data = this.options.data || {};
      if (values.applied) {
        // Remove the "applied" value as it has no meaning in Cropper.
        delete values.applied;
  
        // Merge in the values.
        this.options.data = $.extend(true, this.options.data, values);
  
        // Enforce autoCrop if there's currently a crop applied.
        this.options.autoCrop = true;
      }
  
      this.options.data.rotate = 0;
      this.options.data.scaleX = 1;
      this.options.data.scaleY = 1;
  
      this.$image
        .on('built.iwc.cropper', this.built.bind(this))
        .on('cropend.iwc.cropper', this.cropEnd.bind(this))
        .on('cropmove.iwc.cropper', this.cropMove.bind(this))
        .cropper(this.options);
  
      this.cropper = this.$image.data('cropper');
      this.options = this.cropper.options;
  
      // If "Show default crop" is checked apply default crop.
      if (this.showDefaultCrop) {
        // All data returned by cropper plugin multiple with delta in order to get
        // proper crop sizes for original image.
        this.setValue(this.$image.cropper('getData'), this.naturalDelta);
        this.$wrapper.trigger('summaryUpdated');
      }
    };
  
    /**
     * Creates a poll that checks visibility of an item.
     *
     * @param {HTMLElement|jQuery} element
     *   The element to poll.
     *
     * Replace once vertical tabs have proper events ?
     * When following issue are fixed @see https://www.drupal.org/node/2653570.
     */
    Drupal.ImageWidgetCropType.prototype.pollVisibility = function (element) {
      var $element = $(element);
  
      // Immediately return if there's no element.
      if (!$element[0]) {
        return;
      }
  
      var isElementVisible = function (el) {
        var rect = el.getBoundingClientRect();
        var vWidth = window.innerWidth || document.documentElement.clientWidth;
        var vHeight = window.innerHeight || document.documentElement.clientHeight;
  
        // Immediately Return false if it's not in the viewport.
        if (rect.right < 0 || rect.bottom < 0 || rect.left > vWidth || rect.top > vHeight) {
          return false;
        }
  
        // Return true if any of its four corners are visible.
        var efp = function (x, y) {
          return document.elementFromPoint(x, y);
        };
        return (
          el.contains(efp(rect.left, rect.top))
          || el.contains(efp(rect.right, rect.top))
          || el.contains(efp(rect.right, rect.bottom))
          || el.contains(efp(rect.left, rect.bottom))
        );
      };
  
      var value = null;
      var interval = setInterval(function () {
        var visible = isElementVisible($element[0]);
        if (value !== visible) {
          $element.trigger((value = visible) ? 'visible.iwc' : 'hidden.iwc');
        }
      }, 250);
      this.intervals[interval] = $element;
    };
  
    /**
     * Parses a ration value into a numeric one.
     *
     * @param {String} ratio
     *   A string representation of the ratio.
     *
     * @return {Number.<float>|NaN}
     *   The numeric representation of the ratio.
     */
    Drupal.ImageWidgetCropType.prototype.parseRatio = function (ratio) {
      if (ratio && /:/.test(ratio)) {
        var parts = ratio.split(':');
        var num1 = parseInt(parts[0], 10);
        var num2 = parseInt(parts[1], 10);
        return num1 / num2;
      }
      return parseFloat(ratio);
    };
  
    /**
     * Reset cropping for an element.
     *
     * @param {Event} e
     *   The event object.
     */
    Drupal.ImageWidgetCropType.prototype.reset = function (e) {
      if (!this.cropper) {
        return;
      }
  
      if (e instanceof Event || e instanceof $.Event) {
        e.preventDefault();
        e.stopPropagation();
      }
  
      this.options = $.extend({}, this.cropper.options, this.defaultOptions);
  
      var delta = null;
  
      // Retrieve all current values and zero (0) them out.
      var values = this.getValues();
      for (var name in values) {
        if (values.hasOwnProperty(name)) {
          values[name] = 0;
        }
      }
  
      // If 'Show default crop' is not checked just re-initialize the cropper.
      if (!this.showDefaultCrop) {
        this.destroyCropper();
        this.initializeCropper();
      }
      // Reset cropper to the original values.
      else {
        this.cropper.reset();
        this.cropper.options = this.options;
  
        // Set the delta.
        delta = this.naturalDelta;
  
        // Merge in the original cropper values.
        values = $.extend(values, this.cropper.getData());
      }
  
      this.setValues(values, delta);
      this.$wrapper.trigger('summaryUpdated');
    };
  
    /**
     * The "resize" event handler proxied from the main instance.
     *
     * @see Drupal.ImageWidgetCrop.prototype.resize
     */
    Drupal.ImageWidgetCropType.prototype.resize = function () {
      // Immediately return if currently not visible.
      if (!this.visible) {
        return;
      }
  
      // Get previous data for cropper.
      var canvasDataOld = this.$image.cropper('getCanvasData');
      var cropBoxData = this.$image.cropper('getCropBoxData');
  
      // Re-render cropper.
      this.$image.cropper('render');
  
      // Get new data for cropper and calculate resize ratio.
      var canvasDataNew = this.$image.cropper('getCanvasData');
      var ratio = 1;
      if (canvasDataOld.width !== 0) {
        ratio = canvasDataNew.width / canvasDataOld.width;
      }
  
      // Set new data for crop box.
      $.each(cropBoxData, function (index, value) {
        cropBoxData[index] = value * ratio;
      });
      this.$image.cropper('setCropBoxData', cropBoxData);
  
      this.updateHardLimits();
      this.updateSoftLimits();
      this.$wrapper.trigger('summaryUpdated');
    };
  
    /**
     * Sets a single crop value.
     *
     * @param {'applied'|'height'|'width'|'x'|'y'} name
     *   The name of the crop value to set.
     * @param {Number} value
     *   The value to set.
     * @param {Number} [delta]
     *   A delta to modify the value with.
     */
    Drupal.ImageWidgetCropType.prototype.setValue = function (name, value, delta) {
      if (!this.values.hasOwnProperty(name) || !this.values[name][0]) {
        return;
      }
      value = value ? parseFloat(value) : 0;
      if (delta && name !== 'applied') {
        value = Math.round(value * delta);
      }
      this.values[name][0].value = value;
      this.values[name].trigger('change.iwc, input.iwc');
    };
  
    /**
     * Sets multiple crop values.
     *
     * @param {{applied: Number, height: Number, width: Number, x: Number, y: Number}} obj
     *   An object of key/value pairs of values to set.
     * @param {Number} [delta]
     *   A delta to modify the value with.
     */
    Drupal.ImageWidgetCropType.prototype.setValues = function (obj, delta) {
      for (var name in obj) {
        if (!obj.hasOwnProperty(name)) {
          continue;
        }
        this.setValue(name, obj[name], delta);
      }
    };
  
    /**
     * Converts horizontal and vertical dimensions to canvas dimensions.
     *
     * @param {Number} x - horizontal dimension in image space.
     * @param {Number} y - vertical dimension in image space.
     */
    Drupal.ImageWidgetCropType.prototype.toCanvasDimensions = function (x, y) {
      var imageData = this.cropper.getImageData();
      return {
        width: imageData.width * (x / this.originalWidth),
        height: imageData.height * (y / this.originalHeight)
      }
    };
  
    /**
     * Converts horizontal and vertical dimensions to image dimensions.
     *
     * @param {Number} x - horizontal dimension in canvas space.
     * @param {Number} y - vertical dimension in canvas space.
     */
    Drupal.ImageWidgetCropType.prototype.toImageDimensions = function (x, y) {
      var imageData = this.cropper.getImageData();
      return {
        width: x * (this.originalWidth / imageData.width),
        height: y * (this.originalHeight / imageData.height)
      }
    };
  
    /**
     * Update hard limits.
     */
    Drupal.ImageWidgetCropType.prototype.updateHardLimits = function () {
      // Immediately return if there is no cropper plugin instance or hard limits.
      if (!this.cropper || !this.hardLimit.width || !this.hardLimit.height) {
        return;
      }
  
      var options = this.cropper.options;
  
      // Limits works in canvas so we need to convert dimensions.
      var converted = this.toCanvasDimensions(this.hardLimit.width, this.hardLimit.height);
      options.minCropBoxWidth = converted.width;
      options.minCropBoxHeight = converted.height;
  
      // After updating the options we need to limit crop box.
      this.cropper.limitCropBox(true, false);
    };
  
    /**
     * Update soft limits.
     */
    Drupal.ImageWidgetCropType.prototype.updateSoftLimits = function () {
      // Immediately return if there is no cropper plugin instance or soft limits.
      if (!this.cropper || !this.softLimit.width || !this.softLimit.height) {
        return;
      }
  
      // We do comparison in image dimensions so lets convert first.
      var cropBoxData = this.cropper.getCropBoxData();
      var converted = this.toImageDimensions(cropBoxData.width, cropBoxData.height);
  
      var dimensions = ['width', 'height'];
      for (var i = 0, l = dimensions.length; i < l; i++) {
        var dimension = dimensions[i];
        if (converted[dimension] < this.softLimit[dimension]) {
          if (!this.softLimit.reached[dimension]) {
            this.softLimit.reached[dimension] = true;
          }
        }
        else if (this.softLimit.reached[dimension]) {
          this.softLimit.reached[dimension] = false;
        }
        this.$cropperWrapper.toggleClass('cropper--' + dimension + '-soft-limit-reached', this.softLimit.reached[dimension]);
      }
      this.$wrapper.trigger('summaryUpdated');
    };
  
    /**
     * Updates the summary of the wrapper.
     */
    Drupal.ImageWidgetCropType.prototype.updateSummary = function () {
      var summary = [];
      if (this.getValue('applied')) {
        summary.push(Drupal.t('Cropping applied.'));
      }
      if (this.softLimit.reached.height || this.softLimit.reached.width) {
        summary.push(Drupal.t('Soft limit reached.'));
      }
      return summary.join('<br>');
    };
  
  }(jQuery, Drupal));
  