UNPKG

cropperjs

Version:
3,235 lines 128 kB
/*! Cropper.js v2.2.0 | (c) 2015-present Chen Fengyuan | MIT */
const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
const WINDOW = IS_BROWSER ? window : {};
const IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false;
const HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
const NAMESPACE = 'cropper';
const CROPPER_CANVAS = `${NAMESPACE}-canvas`;
const CROPPER_CROSSHAIR = `${NAMESPACE}-crosshair`;
const CROPPER_GIRD = `${NAMESPACE}-grid`;
const CROPPER_HANDLE = `${NAMESPACE}-handle`;
const CROPPER_IMAGE = `${NAMESPACE}-image`;
const CROPPER_SELECTION = `${NAMESPACE}-selection`;
const CROPPER_SHADE = `${NAMESPACE}-shade`;
const CROPPER_VIEWER = `${NAMESPACE}-viewer`;
// Actions
const ACTION_SELECT = 'select';
const ACTION_MOVE = 'move';
const ACTION_SCALE = 'scale';
const ACTION_ROTATE = 'rotate';
const ACTION_TRANSFORM = 'transform';
const ACTION_NONE = 'none';
const ACTION_RESIZE_NORTH = 'n-resize';
const ACTION_RESIZE_EAST = 'e-resize';
const ACTION_RESIZE_SOUTH = 's-resize';
const ACTION_RESIZE_WEST = 'w-resize';
const ACTION_RESIZE_NORTHEAST = 'ne-resize';
const ACTION_RESIZE_NORTHWEST = 'nw-resize';
const ACTION_RESIZE_SOUTHEAST = 'se-resize';
const ACTION_RESIZE_SOUTHWEST = 'sw-resize';
// Attributes
const ATTRIBUTE_ACTION = 'action';
// Native events
const EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
const EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
const EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
const EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
const EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
const EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
const EVENT_ERROR = 'error';
const EVENT_KEYDOWN = 'keydown';
const EVENT_LOAD = 'load';
const EVENT_RESIZE = 'resize';
const EVENT_WHEEL = 'wheel';
// Custom events
const EVENT_ACTION = 'action';
const EVENT_ACTION_END = 'actionend';
const EVENT_ACTION_MOVE = 'actionmove';
const EVENT_ACTION_START = 'actionstart';
const EVENT_CHANGE = 'change';
const EVENT_TRANSFORM = 'transform';
// Object fit values
const OBJECT_FIT_CONTAIN = 'contain';
const OBJECT_FIT_COVER = 'cover';
const OBJECT_FIT_FILL = 'fill';
const OBJECT_FIT_NONE = 'none';
const OBJECT_FIT_SCALE_DOWN = 'scale-down';

/**
 * Check if the given value is a string.
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if the given value is a string, else `false`.
 */
function isString(value) {
    return typeof value === 'string';
}
/**
 * Check if the given value is not a number.
 */
const isNaN = Number.isNaN || WINDOW.isNaN;
/**
 * Check if the given value is a number.
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if the given value is a number, else `false`.
 */
function isNumber(value) {
    return typeof value === 'number' && !isNaN(value);
}
/**
 * Check if the given value is a positive number.
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if the given value is a positive number, else `false`.
 */
function isPositiveNumber(value) {
    return isNumber(value) && value > 0 && value < Infinity;
}
/**
 * Check if the given value is undefined.
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if the given value is undefined, else `false`.
 */
function isUndefined(value) {
    return typeof value === 'undefined';
}
/**
 * Check if the given value is an object.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is an object, else `false`.
 */
function isObject(value) {
    return typeof value === 'object' && value !== null;
}
const { hasOwnProperty } = Object.prototype;
/**
 * Check if the given value is a plain object.
 * @param {*} value - The value to check.
 * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
 */
function isPlainObject(value) {
    if (!isObject(value)) {
        return false;
    }
    try {
        const { constructor } = value;
        const { prototype } = constructor;
        return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
    }
    catch (error) {
        return false;
    }
}
/**
 * Check if the given value is a function.
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if the given value is a function, else `false`.
 */
function isFunction(value) {
    return typeof value === 'function';
}
/**
 * Check if the given node is an element.
 * @param {*} node The node to check.
 * @returns {boolean} Returns `true` if the given node is an element; otherwise, `false`.
 */
function isElement(node) {
    return typeof node === 'object' && node !== null && node.nodeType === 1;
}
const REGEXP_CAMEL_CASE = /([a-z\d])([A-Z])/g;
/**
 * Transform the given string from camelCase to kebab-case.
 * @param {string} value The value to transform.
 * @returns {string} Returns the transformed value.
 */
function toKebabCase(value) {
    return String(value).replace(REGEXP_CAMEL_CASE, '$1-$2').toLowerCase();
}
const REGEXP_KEBAB_CASE = /-[A-z\d]/g;
/**
 * Transform the given string from kebab-case to camelCase.
 * @param {string} value The value to transform.
 * @returns {string} Returns the transformed value.
 */
function toCamelCase(value) {
    return value.replace(REGEXP_KEBAB_CASE, (substring) => substring.slice(1).toUpperCase());
}
const REGEXP_SPACES = /\s\s*/;
/**
 * Remove event listener from the event target.
 * {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener}
 * @param {EventTarget} target The target of the event.
 * @param {string} types The types of the event.
 * @param {EventListenerOrEventListenerObject} listener The listener of the event.
 * @param {EventListenerOptions} [options] The options specify characteristics about the event listener.
 */
function off(target, types, listener, options) {
    types.trim().split(REGEXP_SPACES).forEach((type) => {
        target.removeEventListener(type, listener, options);
    });
}
/**
 * Add event listener to the event target.
 * {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener}
 * @param {EventTarget} target The target of the event.
 * @param {string} types The types of the event.
 * @param {EventListenerOrEventListenerObject} listener The listener of the event.
 * @param {AddEventListenerOptions} [options] The options specify characteristics about the event listener.
 */
function on(target, types, listener, options) {
    types.trim().split(REGEXP_SPACES).forEach((type) => {
        target.addEventListener(type, listener, options);
    });
}
/**
 * Add once event listener to the event target.
 * @param {EventTarget} target The target of the event.
 * @param {string} types The types of the event.
 * @param {EventListenerOrEventListenerObject} listener The listener of the event.
 * @param {AddEventListenerOptions} [options] The options specify characteristics about the event listener.
 */
function once(target, types, listener, options) {
    on(target, types, listener, Object.assign(Object.assign({}, options), { once: true }));
}
const defaultEventOptions = {
    bubbles: true,
    cancelable: true,
    composed: true,
};
/**
 * Dispatch event on the event target.
 * {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent}
 * @param {EventTarget} target The target of the event.
 * @param {string} type The name of the event.
 * @param {*} [detail] The data passed when initializing the event.
 * @param {CustomEventInit} [options] The other event options.
 * @returns {boolean} Returns the result value.
 */
function emit(target, type, detail, options) {
    return target.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign(Object.assign({}, defaultEventOptions), { detail }), options)));
}
/**
 * Get the real event target by checking composed path.
 * This is useful when dealing with events that can cross shadow DOM boundaries.
 * {@link https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath}
 * @param {Event} event The event object.
 * @returns {EventTarget | null} The first element in the composed path, or the original event target.
 */
function getComposedPathTarget(event) {
    if (typeof event.composedPath === 'function') {
        const path = event.composedPath();
        return path.find(isElement) || event.target;
    }
    return event.target;
}
const resolvedPromise = Promise.resolve();
/**
 * Defers the callback to be executed after the next DOM update cycle.
 * @param {*} [context] The `this` context.
 * @param {Function} [callback] The callback to execute after the next DOM update cycle.
 * @returns {Promise} A promise that resolves to nothing.
 */
function nextTick(context, callback) {
    return callback
        ? resolvedPromise.then(context ? callback.bind(context) : callback)
        : resolvedPromise;
}
/**
 * Get the root document node.
 * @param {Element} element The target element.
 * @returns {Document|DocumentFragment|null} The document node.
 */
function getRootDocument(element) {
    const rootNode = element.getRootNode();
    switch (rootNode.nodeType) {
        case 1:
            return rootNode.ownerDocument;
        case 9:
            return rootNode;
        case 11:
            return rootNode;
    }
    return null;
}
/**
 * Get the offset base on the document.
 * @param {Element} element The target element.
 * @returns {object} The offset data.
 */
function getOffset(element) {
    const { documentElement } = element.ownerDocument;
    const box = element.getBoundingClientRect();
    return {
        left: box.left + (WINDOW.pageXOffset - documentElement.clientLeft),
        top: box.top + (WINDOW.pageYOffset - documentElement.clientTop),
    };
}
const REGEXP_ANGLE_UNIT = /deg|g?rad|turn$/i;
/**
 * Convert an angle to a radian number.
 * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/angle}
 * @param {number|string} angle The angle to convert.
 * @returns {number} Returns the radian number.
 */
function toAngleInRadian(angle) {
    const value = parseFloat(angle) || 0;
    if (value !== 0) {
        const [unit = 'rad'] = String(angle).match(REGEXP_ANGLE_UNIT) || [];
        switch (unit.toLowerCase()) {
            case 'deg':
                return (value / 360) * (Math.PI * 2);
            case 'grad':
                return (value / 400) * (Math.PI * 2);
            case 'turn':
                return value * (Math.PI * 2);
        }
    }
    return value;
}
const SIZE_ADJUSTMENT_TYPE_CONTAIN = 'contain';
const SIZE_ADJUSTMENT_TYPE_COVER = 'cover';
/**
 * Get the max sizes in a rectangle under the given aspect ratio.
 * @param {object} data The original sizes.
 * @param {string} [type] The adjust type.
 * @returns {object} Returns the result sizes.
 */
function getAdjustedSizes(data, type = SIZE_ADJUSTMENT_TYPE_CONTAIN) {
    const { aspectRatio } = data;
    let { width, height } = data;
    const isValidWidth = isPositiveNumber(width);
    const isValidHeight = isPositiveNumber(height);
    if (isValidWidth && isValidHeight) {
        const adjustedWidth = height * aspectRatio;
        if ((type === SIZE_ADJUSTMENT_TYPE_CONTAIN && adjustedWidth > width)
            || (type === SIZE_ADJUSTMENT_TYPE_COVER && adjustedWidth < width)) {
            height = width / aspectRatio;
        }
        else {
            width = height * aspectRatio;
        }
    }
    else if (isValidWidth) {
        height = width / aspectRatio;
    }
    else if (isValidHeight) {
        width = height * aspectRatio;
    }
    return {
        width,
        height,
    };
}
/**
 * Multiply multiple matrices.
 * @param {Array} matrix The first matrix.
 * @param {Array} args The rest matrices.
 * @returns {Array} Returns the result matrix.
 */
function multiplyMatrices(matrix, ...args) {
    if (args.length === 0) {
        return matrix;
    }
    const [a1, b1, c1, d1, e1, f1] = matrix;
    const [a2, b2, c2, d2, e2, f2] = args[0];
    // ┌ a1 c1 e1 ┐   ┌ a2 c2 e2 ┐
    // │ b1 d1 f1 │ × │ b2 d2 f2 │
    // └ 0  0  1  ┘   └ 0  0  1  ┘
    matrix = [
        a1 * a2 + c1 * b2 /* + e1 * 0 */,
        b1 * a2 + d1 * b2 /* + f1 * 0 */,
        a1 * c2 + c1 * d2 /* + e1 * 0 */,
        b1 * c2 + d1 * d2 /* + f1 * 0 */,
        a1 * e2 + c1 * f2 + e1 /* * 1 */,
        b1 * e2 + d1 * f2 + f1 /* * 1 */,
    ];
    return multiplyMatrices(matrix, ...args.slice(1));
}

var style$8 = `:host([hidden]){display:none!important}`;

const REGEXP_SUFFIX = /left|top|width|height/i;
const DEFAULT_SHADOW_ROOT_MODE = 'open';
const shadowRoots = new WeakMap();
const styleSheets = new WeakMap();
const tagNames = new Map();
const supportsAdoptedStyleSheets = Boolean(WINDOW.document
    && Array.isArray(WINDOW.document.adoptedStyleSheets)
    && WINDOW.CSSStyleSheet
    && 'replaceSync' in WINDOW.CSSStyleSheet.prototype);
class CropperElement extends HTMLElement {
    get $sharedStyle() {
        return `${this.themeColor ? `:host{--theme-color: ${this.themeColor};}` : ''}${style$8}`;
    }
    constructor() {
        var _a, _b;
        super();
        this.shadowRootMode = DEFAULT_SHADOW_ROOT_MODE;
        this.slottable = true;
        const name = (_b = (_a = Object.getPrototypeOf(this)) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.$name;
        if (name) {
            tagNames.set(name, this.tagName.toLowerCase());
        }
    }
    static get observedAttributes() {
        return [
            'shadow-root-mode',
            'slottable',
            'theme-color',
        ];
    }
    // Convert attribute to property
    attributeChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        const propertyName = toCamelCase(name);
        const oldPropertyValue = this[propertyName];
        let newPropertyValue = newValue;
        switch (typeof oldPropertyValue) {
            case 'boolean':
                newPropertyValue = newValue !== null && newValue !== 'false';
                break;
            case 'number':
                newPropertyValue = Number(newValue);
                break;
        }
        this[propertyName] = newPropertyValue;
        switch (name) {
            case 'theme-color': {
                const styleSheet = styleSheets.get(this);
                const styles = this.$sharedStyle;
                if (styleSheet && styles) {
                    if (supportsAdoptedStyleSheets) {
                        styleSheet.replaceSync(styles);
                    }
                    else {
                        styleSheet.textContent = styles;
                    }
                }
                break;
            }
        }
    }
    // Convert property to attribute
    $propertyChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        name = toKebabCase(name);
        switch (typeof newValue) {
            case 'boolean':
                if (newValue === true) {
                    if (!this.hasAttribute(name)) {
                        this.setAttribute(name, '');
                    }
                }
                else {
                    this.removeAttribute(name);
                }
                break;
            case 'number':
                if (isNaN(newValue)) {
                    newValue = '';
                }
                else {
                    newValue = String(newValue);
                }
            // Fall through
            // case 'string':
            // eslint-disable-next-line no-fallthrough
            default:
                if (newValue) {
                    if (this.getAttribute(name) !== newValue) {
                        this.setAttribute(name, newValue);
                    }
                }
                else {
                    this.removeAttribute(name);
                }
        }
    }
    connectedCallback() {
        // Observe properties after observed attributes
        Object.getPrototypeOf(this).constructor.observedAttributes.forEach((attribute) => {
            const property = toCamelCase(attribute);
            let value = this[property];
            if (!isUndefined(value)) {
                this.$propertyChangedCallback(property, undefined, value);
            }
            Object.defineProperty(this, property, {
                enumerable: true,
                configurable: true,
                get() {
                    return value;
                },
                set(newValue) {
                    const oldValue = value;
                    value = newValue;
                    this.$propertyChangedCallback(property, oldValue, newValue);
                },
            });
        });
        const shadow = this.shadowRoot || this.attachShadow({
            mode: this.shadowRootMode || DEFAULT_SHADOW_ROOT_MODE,
        });
        shadowRoots.set(this, shadow);
        styleSheets.set(this, this.$addStyles(this.$sharedStyle));
        if (this.$style) {
            this.$addStyles(this.$style);
        }
        if (this.$template) {
            const template = document.createElement('template');
            template.innerHTML = this.$template;
            shadow.appendChild(template.content);
        }
        if (this.slottable) {
            const slot = document.createElement('slot');
            shadow.appendChild(slot);
        }
    }
    disconnectedCallback() {
        if (styleSheets.has(this)) {
            styleSheets.delete(this);
        }
        if (shadowRoots.has(this)) {
            shadowRoots.delete(this);
        }
    }
    // eslint-disable-next-line class-methods-use-this
    $getTagNameOf(name) {
        var _a;
        return (_a = tagNames.get(name)) !== null && _a !== void 0 ? _a : name;
    }
    $setStyles(properties) {
        Object.keys(properties).forEach((property) => {
            let value = properties[property];
            if (isNumber(value)) {
                if (value !== 0 && REGEXP_SUFFIX.test(property)) {
                    value = `${value}px`;
                }
                else {
                    value = String(value);
                }
            }
            this.style[property] = value;
        });
        return this;
    }
    /**
     * Outputs the shadow root of the element.
     * @returns {ShadowRoot} Returns the shadow root.
     */
    $getShadowRoot() {
        return this.shadowRoot || shadowRoots.get(this);
    }
    /**
     * Adds styles to the shadow root.
     * @param {string} styles The styles to add.
     * @returns {CSSStyleSheet|HTMLStyleElement} Returns the generated style sheet.
     */
    $addStyles(styles) {
        let styleSheet;
        const shadow = this.$getShadowRoot();
        if (supportsAdoptedStyleSheets) {
            styleSheet = new CSSStyleSheet();
            styleSheet.replaceSync(styles);
            shadow.adoptedStyleSheets = shadow.adoptedStyleSheets.concat(styleSheet);
        }
        else {
            styleSheet = document.createElement('style');
            styleSheet.textContent = styles;
            shadow.appendChild(styleSheet);
        }
        return styleSheet;
    }
    /**
     * Dispatches an event at the element.
     * @param {string} type The name of the event.
     * @param {*} [detail] The data passed when initializing the event.
     * @param {CustomEventInit} [options] The other event options.
     * @returns {boolean} Returns the result value.
     */
    $emit(type, detail, options) {
        return emit(this, type, detail, options);
    }
    /**
     * Defers the callback to be executed after the next DOM update cycle.
     * @param {Function} [callback] The callback to execute after the next DOM update cycle.
     * @returns {Promise} A promise that resolves to nothing.
     */
    $nextTick(callback) {
        return nextTick(this, callback);
    }
    /**
     * Defines the constructor as a new custom element.
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define}
     * @param {string|object} [name] The element name.
     * @param {object} [options] The element definition options.
     */
    static $define(name, options) {
        if (isObject(name)) {
            options = name;
            name = '';
        }
        if (!name) {
            name = this.$name || this.name;
        }
        name = toKebabCase(name);
        if (IS_BROWSER && WINDOW.customElements && !WINDOW.customElements.get(name)) {
            customElements.define(name, this, options);
        }
    }
}
CropperElement.$version = '2.2.0';

var style$7 = `:host{display:block;min-height:100px;min-width:200px;overflow:hidden;position:relative;touch-action:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}:host([background]){background-color:#fff;background-image:repeating-linear-gradient(45deg,#ccc 25%,transparent 0,transparent 75%,#ccc 0,#ccc),repeating-linear-gradient(45deg,#ccc 25%,transparent 0,transparent 75%,#ccc 0,#ccc);background-image:repeating-conic-gradient(#ccc 0 25%,#fff 0 50%);background-position:0 0,.5rem .5rem;background-size:1rem 1rem}:host([disabled]){pointer-events:none}:host([disabled]):after{bottom:0;content:"";cursor:not-allowed;display:block;left:0;pointer-events:none;position:absolute;right:0;top:0}`;

class CropperCanvas extends CropperElement {
    constructor() {
        super(...arguments);
        this.$onPointerDown = null;
        this.$onPointerMove = null;
        this.$onPointerUp = null;
        this.$onWheel = null;
        this.$wheeling = false;
        this.$pointers = new Map();
        this.$style = style$7;
        this.$action = ACTION_NONE;
        this.background = false;
        this.disabled = false;
        this.scaleStep = 0.1;
        this.themeColor = '#39f';
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'background',
            'disabled',
            'scale-step',
        ]);
    }
    connectedCallback() {
        super.connectedCallback();
        if (!this.disabled) {
            this.$bind();
        }
    }
    disconnectedCallback() {
        this.$unbind();
        super.disconnectedCallback();
    }
    $propertyChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        super.$propertyChangedCallback(name, oldValue, newValue);
        switch (name) {
            case 'disabled':
                if (newValue) {
                    this.$unbind();
                }
                else {
                    this.$bind();
                }
                break;
        }
    }
    $bind() {
        if (!this.$onPointerDown) {
            this.$onPointerDown = this.$handlePointerDown.bind(this);
            on(this, EVENT_POINTER_DOWN, this.$onPointerDown);
        }
        if (!this.$onPointerMove) {
            this.$onPointerMove = this.$handlePointerMove.bind(this);
            on(this.ownerDocument, EVENT_POINTER_MOVE, this.$onPointerMove);
        }
        if (!this.$onPointerUp) {
            this.$onPointerUp = this.$handlePointerUp.bind(this);
            on(this.ownerDocument, EVENT_POINTER_UP, this.$onPointerUp);
        }
        if (!this.$onWheel) {
            this.$onWheel = this.$handleWheel.bind(this);
            on(this, EVENT_WHEEL, this.$onWheel, {
                passive: false,
                capture: true,
            });
        }
    }
    $unbind() {
        if (this.$onPointerDown) {
            off(this, EVENT_POINTER_DOWN, this.$onPointerDown);
            this.$onPointerDown = null;
        }
        if (this.$onPointerMove) {
            off(this.ownerDocument, EVENT_POINTER_MOVE, this.$onPointerMove);
            this.$onPointerMove = null;
        }
        if (this.$onPointerUp) {
            off(this.ownerDocument, EVENT_POINTER_UP, this.$onPointerUp);
            this.$onPointerUp = null;
        }
        if (this.$onWheel) {
            off(this, EVENT_WHEEL, this.$onWheel, {
                capture: true,
            });
            this.$onWheel = null;
        }
        this.$pointers.clear();
        this.style.willChange = '';
        this.$action = ACTION_NONE;
    }
    $addPointers(event) {
        const { $pointers } = this;
        if (event.changedTouches) {
            Array.from(event.changedTouches).forEach(({ identifier, pageX, pageY, }) => {
                $pointers.set(identifier, {
                    startX: pageX,
                    startY: pageY,
                    endX: pageX,
                    endY: pageY,
                });
            });
        }
        else {
            const { pointerId = 0, pageX, pageY } = event;
            $pointers.set(pointerId, {
                startX: pageX,
                startY: pageY,
                endX: pageX,
                endY: pageY,
            });
        }
    }
    $removePointers(event) {
        const { $pointers } = this;
        if (event.changedTouches) {
            Array.from(event.changedTouches).forEach(({ identifier }) => {
                $pointers.delete(identifier);
            });
        }
        else {
            const { pointerId = 0 } = event;
            $pointers.delete(pointerId);
        }
        if ($pointers.size === 0) {
            this.style.willChange = '';
            this.$action = ACTION_NONE;
        }
    }
    $handlePointerDown(event) {
        const { buttons, button, type } = event;
        if (this.disabled || (
        // Handle pointer or mouse event, and ignore touch event
        ((type === 'pointerdown' && event.pointerType === 'mouse') || type === 'mousedown') && (
        // No primary button (Usually the left button)
        (isNumber(buttons) && buttons !== 1) || (isNumber(button) && button !== 0)
            // Open context menu
            || event.ctrlKey))) {
            return;
        }
        this.$addPointers(event);
        const { $pointers } = this;
        let action = ACTION_NONE;
        if ($pointers.size > 1) {
            action = ACTION_TRANSFORM;
        }
        else if (isElement(event.target)) {
            action = event.target.action
                || event.target.getAttribute(ATTRIBUTE_ACTION)
                || ACTION_NONE;
        }
        if (this.$emit(EVENT_ACTION_START, {
            action,
            relatedEvent: event,
        }) === false) {
            this.$removePointers(event);
            return;
        }
        // Prevent page zooming in the browsers for iOS.
        event.preventDefault();
        this.$action = action;
        this.style.willChange = 'transform';
    }
    $handlePointerMove(event) {
        const { $action, $pointers } = this;
        if (this.disabled || $action === ACTION_NONE || $pointers.size === 0) {
            return;
        }
        if (this.$emit(EVENT_ACTION_MOVE, {
            action: $action,
            relatedEvent: event,
        }) === false) {
            return;
        }
        // Prevent page scrolling.
        event.preventDefault();
        if (event.changedTouches) {
            Array.from(event.changedTouches).forEach(({ identifier, pageX, pageY, }) => {
                const pointer = $pointers.get(identifier);
                if (pointer) {
                    Object.assign(pointer, {
                        endX: pageX,
                        endY: pageY,
                    });
                }
            });
        }
        else {
            const { pointerId = 0, pageX, pageY } = event;
            const pointer = $pointers.get(pointerId);
            if (pointer) {
                Object.assign(pointer, {
                    endX: pageX,
                    endY: pageY,
                });
            }
        }
        const detail = {
            action: $action,
            relatedEvent: event,
        };
        if ($action === ACTION_TRANSFORM) {
            const pointers2 = new Map($pointers);
            let maxRotateRate = 0;
            let maxScaleRate = 0;
            let rotate = 0;
            let scale = 0;
            let centerX = event.pageX;
            let centerY = event.pageY;
            $pointers.forEach((pointer, pointerId) => {
                pointers2.delete(pointerId);
                pointers2.forEach((pointer2) => {
                    let x1 = pointer2.startX - pointer.startX;
                    let y1 = pointer2.startY - pointer.startY;
                    let x2 = pointer2.endX - pointer.endX;
                    let y2 = pointer2.endY - pointer.endY;
                    let z1 = 0;
                    let z2 = 0;
                    let a1 = 0;
                    let a2 = 0;
                    if (x1 === 0) {
                        if (y1 < 0) {
                            a1 = Math.PI * 2;
                        }
                        else if (y1 > 0) {
                            a1 = Math.PI;
                        }
                    }
                    else if (x1 > 0) {
                        a1 = (Math.PI / 2) + Math.atan(y1 / x1);
                    }
                    else if (x1 < 0) {
                        a1 = (Math.PI * 1.5) + Math.atan(y1 / x1);
                    }
                    if (x2 === 0) {
                        if (y2 < 0) {
                            a2 = Math.PI * 2;
                        }
                        else if (y2 > 0) {
                            a2 = Math.PI;
                        }
                    }
                    else if (x2 > 0) {
                        a2 = (Math.PI / 2) + Math.atan(y2 / x2);
                    }
                    else if (x2 < 0) {
                        a2 = (Math.PI * 1.5) + Math.atan(y2 / x2);
                    }
                    if (a2 > 0 || a1 > 0) {
                        const rotateRate = a2 - a1;
                        const absRotateRate = Math.abs(rotateRate);
                        if (absRotateRate > maxRotateRate) {
                            maxRotateRate = absRotateRate;
                            rotate = rotateRate;
                            centerX = (pointer.startX + pointer2.startX) / 2;
                            centerY = (pointer.startY + pointer2.startY) / 2;
                        }
                    }
                    x1 = Math.abs(x1);
                    y1 = Math.abs(y1);
                    x2 = Math.abs(x2);
                    y2 = Math.abs(y2);
                    if (x1 > 0 && y1 > 0) {
                        z1 = Math.sqrt((x1 * x1) + (y1 * y1));
                    }
                    else if (x1 > 0) {
                        z1 = x1;
                    }
                    else if (y1 > 0) {
                        z1 = y1;
                    }
                    if (x2 > 0 && y2 > 0) {
                        z2 = Math.sqrt((x2 * x2) + (y2 * y2));
                    }
                    else if (x2 > 0) {
                        z2 = x2;
                    }
                    else if (y2 > 0) {
                        z2 = y2;
                    }
                    if (z1 > 0 && z2 > 0) {
                        const scaleRate = (z2 - z1) / z1;
                        const absScaleRate = Math.abs(scaleRate);
                        if (absScaleRate > maxScaleRate) {
                            maxScaleRate = absScaleRate;
                            scale = scaleRate;
                            centerX = (pointer.startX + pointer2.startX) / 2;
                            centerY = (pointer.startY + pointer2.startY) / 2;
                        }
                    }
                });
            });
            const rotatable = maxRotateRate > 0;
            const scalable = maxScaleRate > 0;
            if (rotatable && scalable) {
                detail.rotate = rotate;
                detail.scale = scale;
                detail.centerX = centerX;
                detail.centerY = centerY;
            }
            else if (rotatable) {
                detail.action = ACTION_ROTATE;
                detail.rotate = rotate;
                detail.centerX = centerX;
                detail.centerY = centerY;
            }
            else if (scalable) {
                detail.action = ACTION_SCALE;
                detail.scale = scale;
                detail.centerX = centerX;
                detail.centerY = centerY;
            }
            else {
                detail.action = ACTION_NONE;
            }
        }
        else {
            const [pointer] = Array.from($pointers.values());
            Object.assign(detail, pointer);
        }
        // Override the starting coordinate
        $pointers.forEach((pointer) => {
            pointer.startX = pointer.endX;
            pointer.startY = pointer.endY;
        });
        if (detail.action !== ACTION_NONE) {
            this.$emit(EVENT_ACTION, detail);
        }
    }
    $handlePointerUp(event) {
        const { $action } = this;
        if (this.disabled) {
            this.$removePointers(event);
            return;
        }
        if ($action === ACTION_NONE) {
            this.$removePointers(event);
            return;
        }
        if (this.$emit(EVENT_ACTION_END, {
            action: $action,
            relatedEvent: event,
        }) === false) {
            this.$removePointers(event);
            return;
        }
        event.preventDefault();
        this.$removePointers(event);
    }
    $handleWheel(event) {
        if (this.disabled) {
            return;
        }
        event.preventDefault();
        // Limit wheel speed to prevent zoom too fast (#21)
        if (this.$wheeling) {
            return;
        }
        this.$wheeling = true;
        // Debounce by 50ms
        setTimeout(() => {
            this.$wheeling = false;
        }, 50);
        const delta = event.deltaY > 0 ? -1 : 1;
        const scale = delta * this.scaleStep;
        this.$emit(EVENT_ACTION, {
            action: ACTION_SCALE,
            scale,
            relatedEvent: event,
        });
    }
    /**
     * Changes the current action to a new one.
     * @param {string} action The new action.
     * @returns {CropperCanvas} Returns `this` for chaining.
     */
    $setAction(action) {
        if (isString(action)) {
            this.$action = action;
        }
        return this;
    }
    /**
     * Generates a real canvas element, with the image draw into if there is one.
     * @param {object} [options] The available options.
     * @param {number} [options.width] The width of the canvas.
     * @param {number} [options.height] The height of the canvas.
     * @param {Function} [options.beforeDraw] The function called before drawing the image onto the canvas.
     * @returns {Promise} Returns a promise that resolves to the generated canvas element.
     */
    $toCanvas(options) {
        return new Promise((resolve, reject) => {
            if (!this.isConnected) {
                reject(new Error('The current element is not connected to the DOM.'));
                return;
            }
            const canvas = document.createElement('canvas');
            let width = this.offsetWidth;
            let height = this.offsetHeight;
            let scale = 1;
            if (isPlainObject(options)
                && (isPositiveNumber(options.width) || isPositiveNumber(options.height))) {
                ({ width, height } = getAdjustedSizes({
                    aspectRatio: width / height,
                    width: options.width,
                    height: options.height,
                }));
                scale = width / this.offsetWidth;
            }
            canvas.width = width;
            canvas.height = height;
            const cropperImage = this.querySelector(this.$getTagNameOf(CROPPER_IMAGE));
            if (!cropperImage) {
                resolve(canvas);
                return;
            }
            cropperImage.$ready().then((image) => {
                const context = canvas.getContext('2d');
                if (context) {
                    const [a, b, c, d, e, f] = cropperImage.$getTransform();
                    let newE = e;
                    let newF = f;
                    let destWidth = image.naturalWidth;
                    let destHeight = image.naturalHeight;
                    if (scale !== 1) {
                        newE *= scale;
                        newF *= scale;
                        destWidth *= scale;
                        destHeight *= scale;
                    }
                    const centerX = destWidth / 2;
                    const centerY = destHeight / 2;
                    context.fillStyle = 'transparent';
                    context.fillRect(0, 0, width, height);
                    if (isPlainObject(options) && isFunction(options.beforeDraw)) {
                        options.beforeDraw.call(this, context, canvas);
                    }
                    context.save();
                    // Move the transform origin to the center of the image.
                    // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin
                    context.translate(centerX, centerY);
                    context.transform(a, b, c, d, newE, newF);
                    // Reset the transform origin to the top-left of the image.
                    context.translate(-centerX, -centerY);
                    context.drawImage(image, 0, 0, destWidth, destHeight);
                    context.restore();
                }
                resolve(canvas);
            }).catch(reject);
        });
    }
}
CropperCanvas.$name = CROPPER_CANVAS;
CropperCanvas.$version = '2.2.0';

var style$6 = `:host{display:inline-block}img{display:block;height:100%;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}`;

const canvasCache$3 = new WeakMap();
const NATIVE_ATTRIBUTES = [
    'alt',
    'crossorigin',
    'decoding',
    'elementtiming',
    'fetchpriority',
    'loading',
    'referrerpolicy',
    'sizes',
    'src',
    'srcset',
];
class CropperImage extends CropperElement {
    constructor() {
        super(...arguments);
        this.$isReady = false;
        this.$matrix = [1, 0, 0, 1, 0, 0];
        this.$onLoad = null;
        this.$onCanvasAction = null;
        this.$onCanvasActionEnd = null;
        this.$onCanvasActionStart = null;
        this.$actionStartTarget = null;
        this.$style = style$6;
        this.$image = new Image();
        /**
         * @deprecated since version 2.2.0, use `initialFit` instead.
         */
        this.initialCenterSize = '';
        this.initialFit = OBJECT_FIT_CONTAIN;
        this.maxFit = '';
        this.minFit = '';
        this.rotatable = false;
        this.scalable = false;
        this.skewable = false;
        this.slottable = false;
        this.translatable = false;
        // Native attributes
        this.alt = '';
        this.crossorigin = '';
        this.decoding = '';
        this.elementtiming = '';
        this.fetchpriority = '';
        this.loading = '';
        this.referrerpolicy = '';
        this.sizes = '';
        this.src = '';
        this.srcset = '';
    }
    set $canvas(element) {
        canvasCache$3.set(this, element);
    }
    get $canvas() {
        return canvasCache$3.get(this);
    }
    static get observedAttributes() {
        return super.observedAttributes.concat(NATIVE_ATTRIBUTES, [
            'initial-center-size',
            'initial-fit',
            'max-fit',
            'min-fit',
            'rotatable',
            'scalable',
            'skewable',
            'translatable',
        ]);
    }
    attributeChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        super.attributeChangedCallback(name, oldValue, newValue);
        // Inherits the native attributes
        if (NATIVE_ATTRIBUTES.includes(name)) {
            if (newValue === null) {
                this.$image.removeAttribute(name);
            }
            else {
                this.$image.setAttribute(name, newValue);
            }
        }
    }
    $propertyChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        super.$propertyChangedCallback(name, oldValue, newValue);
        switch (name) {
            case 'initialCenterSize':
            case 'initialFit':
                this.$nextTick(() => {
                    if (this.$isReady && this.$canvas) {
                        this.$center(newValue);
                    }
                });
                break;
            case 'maxFit':
            case 'minFit':
                this.$nextTick(() => {
                    if (this.$isReady && this.$canvas) {
                        this.$resetTransform();
                        this.$center(this.initialCenterSize || this.initialFit);
                    }
                });
                break;
            case 'src':
                this.$isReady = false;
                break;
        }
    }
    connectedCallback() {
        super.connectedCallback();
        const { $image } = this;
        const $canvas = this.closest(this.$getTagNameOf(CROPPER_CANVAS));
        if ($canvas) {
            this.$canvas = $canvas;
            this.$setStyles({
                // Make it a block element to avoid side effects (#1074).
                display: 'block',
                position: 'absolute',
            });
            this.$onCanvasActionStart = (event) => {
                var _a, _b;
                if (event.defaultPrevented) {
                    return;
                }
                this.$actionStartTarget = (_b = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.relatedEvent) === null || _b === void 0 ? void 0 : _b.target;
            };
            this.$onCanvasActionEnd = () => {
                this.$actionStartTarget = null;
            };
            this.$onCanvasAction = this.$handleAction.bind(this);
            on($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);
            on($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);
            on($canvas, EVENT_ACTION, this.$onCanvasAction);
        }
        this.$onLoad = this.$handleLoad.bind(this);
        on($image, EVENT_LOAD, this.$onLoad);
        this.$getShadowRoot().appendChild($image);
    }
    disconnectedCallback() {
        const { $image, $canvas } = this;
        if ($canvas) {
            if (this.$onCanvasActionStart) {
                off($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);
                this.$onCanvasActionStart = null;
            }
            if (this.$onCanvasActionEnd) {
                off($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);
                this.$onCanvasActionEnd = null;
            }
            if (this.$onCanvasAction) {
                off($canvas, EVENT_ACTION, this.$onCanvasAction);
                this.$onCanvasAction = null;
            }
        }
        if ($image && this.$onLoad) {
            off($image, EVENT_LOAD, this.$onLoad);
            this.$onLoad = null;
        }
        this.$getShadowRoot().removeChild($image);
        super.disconnectedCallback();
    }
    $handleLoad() {
        const { $image } = this;
        this.$setStyles({
            width: $image.naturalWidth,
            height: $image.naturalHeight,
        });
        if (this.$canvas) {
            this.$center(this.initialCenterSize || this.initialFit);
        }
        this.$isReady = true;
    }
    $handleAction(event) {
        if (event.defaultPrevented
            || this.hidden
            || !(this.rotatable || this.scalable || this.translatable)) {
            return;
        }
        const { $canvas } = this;
        const { detail } = event;
        if (detail) {
            const { relatedEvent } = detail;
            let { action } = detail;
            if (action === ACTION_TRANSFORM && (!this.rotatable || !this.scalable)) {
                if (this.rotatable) {
                    action = ACTION_ROTATE;
                }
                else if (this.scalable) {
                    action = ACTION_SCALE;
                }
                else {
                    action = ACTION_NONE;
                }
            }
            switch (action) {
                case ACTION_MOVE:
                    if (this.translatable) {
                        let $selection = null;
                        if (relatedEvent) {
                            $selection = relatedEvent.target.closest(this.$getTagNameOf(CROPPER_SELECTION));
                        }
                        if (!$selection) {
                            $selection = $canvas.querySelector(this.$getTagNameOf(CROPPER_SELECTION));
                        }
                        if ($selection && $selection.multiple && !$selection.active) {
                            $selection = $canvas.querySelector(`${this.$getTagNameOf(CROPPER_SELECTION)}[active]`);
                        }
                        if (!$selection || $selection.hidden || !$selection.movable || $selection.dynamic
                            || !(this.$actionStartTarget && $selection.contains(this.$actionStartTarget))) {
                            this.$move(detail.endX - detail.startX, detail.endY - detail.startY);
                        }
                    }
                    break;
                case ACTION_ROTATE:
                    if (this.rotatable) {
                        if (relatedEvent) {
                            const { x, y } = this.getBoundingClientRect();
                            this.$rotate(detail.rotate, relatedEvent.clientX - x, relatedEvent.clientY - y);
                        }
                        else {
                            this.$rotate(detail.rotate);
                        }
                    }
                    break;
                case ACTION_SCALE:
                    if (this.scalable) {
                        if (relatedEvent) {
                            const $selection = relatedEvent.target.closest(this.$getTagNameOf(CROPPER_SELECTION));
                            if (!$selection
                                || !$selection.zoomable
                                || ($selection.zoomable && $selection.dynamic)) {
                                const { x, y } = this.getBoundingClientRect();
                                this.$zoom(detail.scale, relatedEvent.clientX - x, relatedEvent.clientY - y);
                            }
                        }
                        else {
                            this.$zoom(detail.scale);
                        }
                    }
                    break;
                case ACTION_TRANSFORM:
                    if (this.rotatable && this.scalable) {
                        const { rotate } = detail;
                        let { scale } = detail;
                        if (scale < 0) {
                            scale = 1 / (1 - scale);
                        }
                        else {
                            scale += 1;
                        }
                        const cos = Math.cos(rotate);
                        const sin = Math.sin(rotate);
                        const [scaleX, skewY, skewX, scaleY] = [
                            cos * scale,
                            sin * scale,
                            -sin * scale,
                            cos * scale,
                        ];
                        if (relatedEvent) {
                            const clientRect = this.getBoundingClientRect();
                            const x = relatedEvent.clientX - clientRect.x;
                            const y = relatedEvent.clientY - clientRect.y;
                            const [a, b, c, d] = this.$matrix;
                            const originX = clientRect.width / 2;
                            const originY = clientRect.height / 2;
                            const moveX = x - originX;
                            const moveY = y - originY;
                            const translateX = ((moveX * d) - (c * moveY)) / ((a * d) - (c * b));
                            const translateY = ((moveY * a) - (b * moveX)) / ((a * d) - (c * b));
                            /**
                             * Equals to
                             * this.$rotate(rotate, x, y);
                             * this.$scale(scale, x, y);
                             */
                            this.$transform(scaleX, skewY, skewX, scaleY, translateX * (1 - scaleX) + translateY * skewX, translateY * (1 - scaleY) + translateX * skewY);
                        }
                        else {
                            /**
                             * Equals to
                             * this.$rotate(rotate);
                             * this.$scale(scale);
                             */
                            this.$transform(scaleX, skewY, skewX, scaleY, 0, 0);
                        }
                    }
                    break;
            }
        }
    }
    /**
     * Defers the callback to execute after successfully loading the image.
     * @param {Function} [callback] The callback to execute after successfully loading the image.
     * @returns {Promise} Returns a promise that resolves to the image element.
     */
    $ready(callback) {
        const { $image } = this;
        const promise = new Promise((resolve, reject) => {
            const error = new Error('Failed to load the image source');
            if ($image.complete) {
                if ($image.naturalWidth > 0 && $image.naturalHeight > 0) {
                    resolve($image);
                }
                else {
                    reject(error);
                }
            }
            else {
                const onLoad = () => {
                    // eslint-disable-next-line @typescript-eslint/no-use-before-define
                    off($image, EVENT_ERROR, onError);
                    // Ensure the image is fully rendered.
                    setTimeout(() => {
                        resolve($image);
                    });
                };
                const onError = () => {
                    off($image, EVENT_LOAD, onLoad);
                    reject(error);
                };
                once($image, EVENT_LOAD, onLoad);
                once($image, EVENT_ERROR, onError);
            }
        });
        if (isFunction(callback)) {
            promise.then((image) => {
                callback(image);
                return image;
            });
        }
        return promise;
    }
    /**
     * Aligns the image to the center of its parent element.
     * @param {string} [size] The size of the image.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $center(size) {
        const { parentElement } = this;
        if (!parentElement) {
            return this;
        }
        const container = parentElement.getBoundingClientRect();
        const containerWidth = container.width;
        const containerHeight = container.height;
        const { x, y, width, height, } = this.getBoundingClientRect();
        const startX = x + (width / 2);
        const startY = y + (height / 2);
        const endX = container.x + (containerWidth / 2);
        const endY = container.y + (containerHeight / 2);
        const { translatable } = this;
        if (!translatable && !this.$isReady) {
            this.translatable = true;
            this.$nextTick(() => {
                this.translatable = translatable;
            });
        }
        this.$move(endX - startX, endY - startY);
        const { maxFit, minFit } = this;
        if (size || maxFit || minFit) {
            const { naturalWidth, naturalHeight } = this.$image;
            switch (size) {
                case OBJECT_FIT_COVER:
                    if ([
                        OBJECT_FIT_FILL,
                        OBJECT_FIT_CONTAIN,
                        OBJECT_FIT_SCALE_DOWN,
                    ].includes(maxFit) || (maxFit === OBJECT_FIT_NONE
                        && (naturalWidth < containerWidth || naturalHeight < containerHeight))) {
                        size = maxFit;
                    }
                    else if (minFit === OBJECT_FIT_NONE
                        && naturalWidth > containerWidth
                        && naturalHeight > containerHeight) {
                        size = minFit;
                    }
                    break;
                case OBJECT_FIT_FILL:
                    if ([OBJECT_FIT_CONTAIN, OBJECT_FIT_SCALE_DOWN].includes(maxFit) || (maxFit === OBJECT_FIT_NONE
                        && (naturalWidth < containerWidth || naturalHeight < containerHeight))) {
                        size = maxFit;
                    }
                    else if (minFit === OBJECT_FIT_COVER || (minFit === OBJECT_FIT_NONE
                        && naturalWidth > containerWidth
                        && naturalHeight > containerHeight)) {
                        size = minFit;
                    }
                    break;
                case OBJECT_FIT_CONTAIN:
                    if (maxFit === OBJECT_FIT_SCALE_DOWN || (maxFit === OBJECT_FIT_NONE
                        && naturalWidth < containerWidth
                        && naturalHeight < containerHeight)) {
                        size = maxFit;
                    }
                    else if ([OBJECT_FIT_COVER, OBJECT_FIT_FILL].includes(minFit) || (minFit === OBJECT_FIT_NONE
                        && (naturalWidth > containerWidth || naturalHeight > containerHeight))) {
                        size = minFit;
                    }
                    break;
                case OBJECT_FIT_SCALE_DOWN:
                    if (maxFit === OBJECT_FIT_NONE
                        && naturalWidth < containerWidth
                        && naturalHeight < containerHeight) {
                        size = maxFit;
                    }
                    else if ([OBJECT_FIT_COVER, OBJECT_FIT_FILL, OBJECT_FIT_CONTAIN].includes(minFit) || (minFit === OBJECT_FIT_NONE
                        && (naturalWidth > containerWidth && naturalHeight > containerHeight))) {
                        size = minFit;
                    }
                    break;
                // case OBJECT_FIT_NONE:
                default:
                    if ([
                        OBJECT_FIT_COVER,
                        OBJECT_FIT_FILL,
                        OBJECT_FIT_CONTAIN,
                        OBJECT_FIT_SCALE_DOWN,
                    ].includes(maxFit)
                        && (naturalWidth > containerWidth || naturalHeight > containerHeight)) {
                        size = maxFit;
                    }
                    else if ([
                        OBJECT_FIT_COVER,
                        OBJECT_FIT_FILL,
                        OBJECT_FIT_CONTAIN,
                        OBJECT_FIT_SCALE_DOWN,
                    ].includes(minFit) && naturalWidth < containerWidth && naturalHeight < containerHeight) {
                        size = minFit;
                    }
            }
            const scaleX = containerWidth / width;
            const scaleY = containerHeight / height;
            const { scalable } = this;
            if (!scalable && !this.$isReady) {
                this.scalable = true;
                this.$nextTick(() => {
                    this.scalable = scalable;
                });
            }
            switch (size) {
                case OBJECT_FIT_COVER:
                    this.$scale(Math.max(scaleX, scaleY));
                    break;
                case OBJECT_FIT_CONTAIN:
                    this.$scale(Math.min(scaleX, scaleY));
                    break;
                case OBJECT_FIT_FILL:
                    this.$scale(scaleX, scaleY);
                    break;
                case OBJECT_FIT_SCALE_DOWN:
                    this.$scale(Math.min(scaleX, scaleY, 1));
                    break;
                case OBJECT_FIT_NONE:
                    this.$scale(1);
                    break;
            }
        }
        return this;
    }
    /**
     * Moves the image.
     * @param {number} x The moving distance in the horizontal direction.
     * @param {number} [y] The moving distance in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $move(x, y = x) {
        if (this.translatable && isNumber(x) && isNumber(y)) {
            const [a, b, c, d] = this.$matrix;
            const e = ((x * d) - (c * y)) / ((a * d) - (c * b));
            const f = ((y * a) - (b * x)) / ((a * d) - (c * b));
            this.$translate(e, f);
        }
        return this;
    }
    /**
     * Moves the image to a specific position.
     * @param {number} x The new position in the horizontal direction.
     * @param {number} [y] The new position in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $moveTo(x, y = x) {
        if (this.translatable && isNumber(x) && isNumber(y)) {
            const [a, b, c, d] = this.$matrix;
            const e = ((x * d) - (c * y)) / ((a * d) - (c * b));
            const f = ((y * a) - (b * x)) / ((a * d) - (c * b));
            this.$setTransform(a, b, c, d, e, f);
        }
        return this;
    }
    /**
     * Rotates the image.
     * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate}
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate}
     * @param {number|string} angle The rotation angle (in radians).
     * @param {number} [x] The rotation origin in the horizontal, defaults to the center of the image.
     * @param {number} [y] The rotation origin in the vertical, defaults to the center of the image.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $rotate(angle, x, y) {
        if (this.rotatable) {
            const radian = toAngleInRadian(angle);
            const cos = Math.cos(radian);
            const sin = Math.sin(radian);
            const [scaleX, skewY, skewX, scaleY] = [cos, sin, -sin, cos];
            if (isNumber(x) && isNumber(y)) {
                const [a, b, c, d] = this.$matrix;
                const { width, height } = this.getBoundingClientRect();
                const originX = width / 2;
                const originY = height / 2;
                const moveX = x - originX;
                const moveY = y - originY;
                const translateX = ((moveX * d) - (c * moveY)) / ((a * d) - (c * b));
                const translateY = ((moveY * a) - (b * moveX)) / ((a * d) - (c * b));
                /**
                 * Equals to
                 * this.$translate(translateX, translateX);
                 * this.$rotate(angle);
                 * this.$translate(-translateX, -translateX);
                 */
                this.$transform(scaleX, skewY, skewX, scaleY, translateX * (1 - scaleX) - translateY * skewX, translateY * (1 - scaleY) - translateX * skewY);
            }
            else {
                this.$transform(scaleX, skewY, skewX, scaleY, 0, 0);
            }
        }
        return this;
    }
    /**
     * Zooms the image.
     * @param {number} scale The zoom factor. Positive numbers for zooming in, and negative numbers for zooming out.
     * @param {number} [x] The zoom origin in the horizontal, defaults to the center of the image.
     * @param {number} [y] The zoom origin in the vertical, defaults to the center of the image.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $zoom(scale, x, y) {
        if (!this.scalable || scale === 0) {
            return this;
        }
        if (scale < 0) {
            scale = 1 / (1 - scale);
        }
        else {
            scale += 1;
        }
        if (isNumber(x) && isNumber(y)) {
            const [a, b, c, d] = this.$matrix;
            const { width, height } = this.getBoundingClientRect();
            const originX = width / 2;
            const originY = height / 2;
            const moveX = x - originX;
            const moveY = y - originY;
            const translateX = ((moveX * d) - (c * moveY)) / ((a * d) - (c * b));
            const translateY = ((moveY * a) - (b * moveX)) / ((a * d) - (c * b));
            /**
             * Equals to
             * this.$translate(translateX, translateX);
             * this.$scale(scale);
             * this.$translate(-translateX, -translateX);
             */
            this.$transform(scale, 0, 0, scale, translateX * (1 - scale), translateY * (1 - scale));
        }
        else {
            this.$scale(scale);
        }
        return this;
    }
    /**
     * Scales the image.
     * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale}
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/scale}
     * @param {number} x The scaling factor in the horizontal direction.
     * @param {number} [y] The scaling factor in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $scale(x, y = x) {
        if (this.scalable) {
            this.$transform(x, 0, 0, y, 0, 0);
        }
        return this;
    }
    /**
     * Skews the image.
     * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew}
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform}
     * @param {number|string} x The skewing angle in the horizontal direction.
     * @param {number|string} [y] The skewing angle in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $skew(x, y = 0) {
        if (this.skewable) {
            const radianX = toAngleInRadian(x);
            const radianY = toAngleInRadian(y);
            this.$transform(1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0);
        }
        return this;
    }
    /**
     * Translates the image.
     * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate}
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate}
     * @param {number} x The translating distance in the horizontal direction.
     * @param {number} [y] The translating distance in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $translate(x, y = x) {
        if (this.translatable && isNumber(x) && isNumber(y)) {
            this.$transform(1, 0, 0, 1, x, y);
        }
        return this;
    }
    /**
     * Transforms the image.
     * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix}
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform}
     * @param {number} a The scaling factor in the horizontal direction.
     * @param {number} b The skewing angle in the vertical direction.
     * @param {number} c The skewing angle in the horizontal direction.
     * @param {number} d The scaling factor in the vertical direction.
     * @param {number} e The translating distance in the horizontal direction.
     * @param {number} f The translating distance in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $transform(a, b, c, d, e, f) {
        if (isNumber(a)
            && isNumber(b)
            && isNumber(c)
            && isNumber(d)
            && isNumber(e)
            && isNumber(f)) {
            return this.$setTransform(multiplyMatrices(this.$matrix, [a, b, c, d, e, f]));
        }
        return this;
    }
    /**
     * Resets (overrides) the current transform to the specific identity matrix.
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform}
     * @param {number|Array} a The scaling factor in the horizontal direction.
     * @param {number} b The skewing angle in the vertical direction.
     * @param {number} c The skewing angle in the horizontal direction.
     * @param {number} d The scaling factor in the vertical direction.
     * @param {number} e The translating distance in the horizontal direction.
     * @param {number} f The translating distance in the vertical direction.
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $setTransform(a, b, c, d, e, f) {
        if (this.rotatable || this.scalable || this.skewable || this.translatable) {
            if (Array.isArray(a)) {
                [a, b, c, d, e, f] = a;
            }
            if (isNumber(a)
                && isNumber(b)
                && isNumber(c)
                && isNumber(d)
                && isNumber(e)
                && isNumber(f)) {
                const oldMatrix = [...this.$matrix];
                const newMatrix = [a, b, c, d, e, f];
                if (this.$isReady && this.$canvas) {
                    const { $canvas } = this;
                    const canvasRect = $canvas.getBoundingClientRect();
                    this.style.transform = `matrix(${newMatrix.join(', ')})`;
                    const imageRect = this.$image.getBoundingClientRect();
                    this.style.transform = `matrix(${oldMatrix.join(', ')})`;
                    let { maxFit, minFit } = this;
                    if (maxFit || minFit) {
                        // Check if the image is completely outside the canvas after transformation,
                        // if so, skip the transformation to avoid losing the image.
                        if (imageRect.top > canvasRect.bottom
                            || imageRect.right < canvasRect.left
                            || imageRect.bottom < canvasRect.top
                            || imageRect.left > canvasRect.right) {
                            return this;
                        }
                        const { naturalWidth, naturalHeight } = this.$image;
                        if (maxFit === OBJECT_FIT_SCALE_DOWN) {
                            if (naturalWidth >= canvasRect.width || naturalHeight >= canvasRect.height) {
                                maxFit = OBJECT_FIT_CONTAIN;
                            }
                            else {
                                maxFit = OBJECT_FIT_NONE;
                            }
                        }
                        switch (maxFit) {
                            case OBJECT_FIT_COVER:
                                if (imageRect.width > canvasRect.width && imageRect.height > canvasRect.height) {
                                    return this;
                                }
                                break;
                            case OBJECT_FIT_FILL:
                            case OBJECT_FIT_CONTAIN:
                                if (imageRect.width > canvasRect.width || imageRect.height > canvasRect.height) {
                                    return this;
                                }
                                break;
                            case OBJECT_FIT_NONE:
                                if (imageRect.width > naturalWidth || imageRect.height > naturalHeight) {
                                    return this;
                                }
                                break;
                        }
                        if (minFit === OBJECT_FIT_SCALE_DOWN) {
                            if (naturalWidth >= canvasRect.width || naturalHeight >= canvasRect.height) {
                                minFit = OBJECT_FIT_CONTAIN;
                            }
                            else {
                                minFit = OBJECT_FIT_NONE;
                            }
                        }
                        switch (minFit) {
                            case OBJECT_FIT_COVER:
                            case OBJECT_FIT_FILL:
                                if (imageRect.width < canvasRect.width || imageRect.height < canvasRect.height) {
                                    return this;
                                }
                                break;
                            case OBJECT_FIT_CONTAIN:
                                if (imageRect.width < canvasRect.width && imageRect.height < canvasRect.height) {
                                    return this;
                                }
                                break;
                            case OBJECT_FIT_NONE:
                                if (imageRect.width < naturalWidth || imageRect.height < naturalHeight) {
                                    return this;
                                }
                                break;
                        }
                    }
                    if (this.$emit(EVENT_CHANGE, {
                        x: imageRect.x - canvasRect.x,
                        y: imageRect.y - canvasRect.y,
                        width: imageRect.width,
                        height: imageRect.height,
                    }) === false) {
                        return this;
                    }
                }
                if (this.$emit(EVENT_TRANSFORM, {
                    matrix: newMatrix,
                    oldMatrix,
                }) === false) {
                    return this;
                }
                this.$matrix = newMatrix;
                this.style.transform = `matrix(${newMatrix.join(', ')})`;
            }
        }
        return this;
    }
    /**
     * Retrieves the current transformation matrix being applied to the element.
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getTransform}
     * @returns {Array} Returns the readonly transformation matrix.
     */
    $getTransform() {
        return this.$matrix.slice();
    }
    /**
     * Resets the current transform to the initial identity matrix.
     * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform}
     * @returns {CropperImage} Returns `this` for chaining.
     */
    $resetTransform() {
        return this.$setTransform([1, 0, 0, 1, 0, 0]);
    }
}
CropperImage.$name = CROPPER_IMAGE;
CropperImage.$version = '2.2.0';

var style$5 = `:host{display:block;height:0;left:0;outline:var(--theme-color) solid 1px;position:relative;top:0;width:0}:host([transparent]){outline-color:transparent}`;

const canvasCache$2 = new WeakMap();
class CropperShade extends CropperElement {
    constructor() {
        super(...arguments);
        this.$onWindowResize = null;
        this.$onCanvasActionEnd = null;
        this.$onCanvasActionStart = null;
        this.$onSelectionChange = null;
        this.$style = style$5;
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
        this.slottable = false;
        this.themeColor = 'rgba(0, 0, 0, 0.65)';
    }
    set $canvas(element) {
        canvasCache$2.set(this, element);
    }
    get $canvas() {
        return canvasCache$2.get(this);
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'height',
            'width',
            'x',
            'y',
        ]);
    }
    connectedCallback() {
        super.connectedCallback();
        const $canvas = this.closest(this.$getTagNameOf(CROPPER_CANVAS));
        if ($canvas) {
            this.$canvas = $canvas;
            this.style.position = 'absolute';
            const $selection = $canvas.querySelector(this.$getTagNameOf(CROPPER_SELECTION));
            if ($selection) {
                this.$onWindowResize = this.$render.bind(this);
                this.$onCanvasActionStart = (event) => {
                    if ($selection.hidden && event.detail.action === ACTION_SELECT) {
                        this.hidden = false;
                    }
                };
                this.$onCanvasActionEnd = (event) => {
                    if ($selection.hidden && event.detail.action === ACTION_SELECT) {
                        this.hidden = true;
                    }
                };
                this.$onSelectionChange = (event) => {
                    var _a, _b;
                    if (((_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.$name) !== CROPPER_SELECTION) {
                        return;
                    }
                    const { x, y, width, height, } = event.defaultPrevented ? $selection : event.detail;
                    this.$change(x, y, width, height);
                    if ($selection.hidden || (x === 0 && y === 0 && width === 0 && height === 0)) {
                        this.hidden = true;
                    }
                };
                on(window, EVENT_RESIZE, this.$onWindowResize);
                on($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);
                on($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);
                on($canvas, EVENT_CHANGE, this.$onSelectionChange);
            }
        }
        this.$render();
    }
    disconnectedCallback() {
        const { $canvas } = this;
        if ($canvas) {
            if (this.$onWindowResize) {
                off(window, EVENT_RESIZE, this.$onWindowResize);
                this.$onWindowResize = null;
            }
            if (this.$onCanvasActionStart) {
                off($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);
                this.$onCanvasActionStart = null;
            }
            if (this.$onCanvasActionEnd) {
                off($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);
                this.$onCanvasActionEnd = null;
            }
            if (this.$onSelectionChange) {
                off($canvas, EVENT_CHANGE, this.$onSelectionChange);
                this.$onSelectionChange = null;
            }
        }
        super.disconnectedCallback();
    }
    /**
     * Changes the position and/or size of the shade.
     * @param {number} x The new position in the horizontal direction.
     * @param {number} y The new position in the vertical direction.
     * @param {number} [width] The new width.
     * @param {number} [height] The new height.
     * @returns {CropperShade} Returns `this` for chaining.
     */
    $change(x, y, width = this.width, height = this.height) {
        if (!isNumber(x)
            || !isNumber(y)
            || !isNumber(width)
            || !isNumber(height)
            || (x === this.x && y === this.y && width === this.width && height === this.height)) {
            return this;
        }
        if (this.hidden) {
            this.hidden = false;
        }
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        return this.$render();
    }
    /**
     * Resets the shade to its initial position and size.
     * @returns {CropperShade} Returns `this` for chaining.
     */
    $reset() {
        return this.$change(0, 0, 0, 0);
    }
    /**
     * Refreshes the position or size of the shade.
     * @returns {CropperShade} Returns `this` for chaining.
     */
    $render() {
        return this.$setStyles({
            transform: `translate(${this.x}px, ${this.y}px)`,
            width: this.width,
            height: this.height,
            outlineWidth: WINDOW.innerWidth * WINDOW.devicePixelRatio,
        });
    }
}
CropperShade.$name = CROPPER_SHADE;
CropperShade.$version = '2.2.0';

var style$4 = `:host{background-color:var(--theme-color);display:block}:host([action=move]),:host([action=select]){height:100%;left:0;position:absolute;top:0;width:100%}:host([action=move]){cursor:move}:host([action=select]){cursor:crosshair}:host([action$=-resize]){background-color:transparent;height:15px;position:absolute;width:15px}:host([action$=-resize]):after{background-color:var(--theme-color);content:"";display:block;height:5px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:5px}:host([action=n-resize]),:host([action=s-resize]){cursor:ns-resize;left:50%;transform:translateX(-50%);width:100%}:host([action=n-resize]){top:-8px}:host([action=s-resize]){bottom:-8px}:host([action=e-resize]),:host([action=w-resize]){cursor:ew-resize;height:100%;top:50%;transform:translateY(-50%)}:host([action=e-resize]){right:-8px}:host([action=w-resize]){left:-8px}:host([action=ne-resize]){cursor:nesw-resize;right:-8px;top:-8px}:host([action=nw-resize]){cursor:nwse-resize;left:-8px;top:-8px}:host([action=se-resize]){bottom:-8px;cursor:nwse-resize;right:-8px}:host([action=se-resize]):after{height:15px;width:15px}@media (pointer:coarse){:host([action=se-resize]):after{height:10px;width:10px}}@media (pointer:fine){:host([action=se-resize]):after{height:5px;width:5px}}:host([action=sw-resize]){bottom:-8px;cursor:nesw-resize;left:-8px}:host([plain]){background-color:transparent}`;

class CropperHandle extends CropperElement {
    constructor() {
        super(...arguments);
        this.$onCanvasCropEnd = null;
        this.$onCanvasCropStart = null;
        this.$style = style$4;
        this.action = ACTION_NONE;
        this.plain = false;
        this.slottable = false;
        this.themeColor = 'rgba(51, 153, 255, 0.5)';
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'action',
            'plain',
        ]);
    }
}
CropperHandle.$name = CROPPER_HANDLE;
CropperHandle.$version = '2.2.0';

var style$3 = `:host{display:block;left:0;position:relative;right:0}:host([outlined]){outline:1px solid var(--theme-color)}:host([multiple]){outline:1px dashed hsla(0,0%,100%,.5)}:host([multiple]):after{bottom:0;content:"";cursor:pointer;display:block;left:0;position:absolute;right:0;top:0}:host([multiple][active]){outline-color:var(--theme-color);z-index:1}:host([multiple])>*{visibility:hidden}:host([multiple][active])>*{visibility:visible}:host([multiple][active]):after{display:none}`;

const canvasCache$1 = new WeakMap();
class CropperSelection extends CropperElement {
    constructor() {
        super(...arguments);
        this.$onCanvasAction = null;
        this.$onCanvasActionStart = null;
        this.$onCanvasActionEnd = null;
        this.$onDocumentKeyDown = null;
        this.$action = '';
        this.$actionStartTarget = null;
        this.$changing = false;
        this.$style = style$3;
        this.$initialSelection = {
            x: 0,
            y: 0,
            width: 0,
            height: 0,
        };
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
        this.aspectRatio = NaN;
        this.initialAspectRatio = NaN;
        this.initialCoverage = NaN;
        this.active = false;
        // Deprecated as of v2.0.0-rc.0, use `dynamic` instead.
        this.linked = false;
        this.dynamic = false;
        this.movable = false;
        this.resizable = false;
        this.zoomable = false;
        this.multiple = false;
        this.keyboard = false;
        this.outlined = false;
        this.precise = false;
    }
    set $canvas(element) {
        canvasCache$1.set(this, element);
    }
    get $canvas() {
        return canvasCache$1.get(this);
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'active',
            'aspect-ratio',
            'dynamic',
            'height',
            'initial-aspect-ratio',
            'initial-coverage',
            'keyboard',
            'linked',
            'movable',
            'multiple',
            'outlined',
            'precise',
            'resizable',
            'width',
            'x',
            'y',
            'zoomable',
        ]);
    }
    $propertyChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        super.$propertyChangedCallback(name, oldValue, newValue);
        switch (name) {
            case 'x':
            case 'y':
            case 'width':
            case 'height':
                if (!this.$changing) {
                    this.$nextTick(() => {
                        this.$change(this.x, this.y, this.width, this.height, this.aspectRatio, true);
                    });
                }
                break;
            case 'aspectRatio':
            case 'initialAspectRatio':
                this.$nextTick(() => {
                    this.$initSelection();
                });
                break;
            case 'initialCoverage':
                this.$nextTick(() => {
                    if (isPositiveNumber(newValue) && newValue <= 1) {
                        this.$initSelection(true, true);
                    }
                });
                break;
            case 'keyboard':
                this.$nextTick(() => {
                    if (this.$canvas) {
                        if (newValue) {
                            if (!this.$onDocumentKeyDown) {
                                this.$onDocumentKeyDown = this.$handleKeyDown.bind(this);
                                on(this.ownerDocument, EVENT_KEYDOWN, this.$onDocumentKeyDown);
                            }
                        }
                        else if (this.$onDocumentKeyDown) {
                            off(this.ownerDocument, EVENT_KEYDOWN, this.$onDocumentKeyDown);
                            this.$onDocumentKeyDown = null;
                        }
                    }
                });
                break;
            case 'multiple':
                this.$nextTick(() => {
                    if (this.$canvas) {
                        const selections = this.$getSelections();
                        if (newValue) {
                            selections.forEach((selection) => {
                                selection.active = false;
                            });
                            this.active = true;
                            this.$emit(EVENT_CHANGE, {
                                x: this.x,
                                y: this.y,
                                width: this.width,
                                height: this.height,
                            });
                        }
                        else {
                            this.active = false;
                            selections.slice(1).forEach((selection) => {
                                this.$removeSelection(selection);
                            });
                        }
                    }
                });
                break;
            case 'precise':
                this.$nextTick(() => {
                    this.$change(this.x, this.y);
                });
                break;
            // Backwards compatible with 2.0.0-rc
            case 'linked':
                if (newValue) {
                    this.dynamic = true;
                }
                break;
        }
    }
    connectedCallback() {
        super.connectedCallback();
        const $canvas = this.closest(this.$getTagNameOf(CROPPER_CANVAS));
        if ($canvas) {
            this.$canvas = $canvas;
            this.$setStyles({
                position: 'absolute',
                transform: `translate(${this.x}px, ${this.y}px)`,
            });
            if (!this.hidden) {
                this.$render();
            }
            this.$initSelection(true);
            this.$onCanvasActionStart = this.$handleActionStart.bind(this);
            this.$onCanvasActionEnd = this.$handleActionEnd.bind(this);
            this.$onCanvasAction = this.$handleAction.bind(this);
            on($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);
            on($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);
            on($canvas, EVENT_ACTION, this.$onCanvasAction);
        }
        else {
            this.$render();
        }
    }
    disconnectedCallback() {
        const { $canvas } = this;
        if ($canvas) {
            if (this.$onCanvasActionStart) {
                off($canvas, EVENT_ACTION_START, this.$onCanvasActionStart);
                this.$onCanvasActionStart = null;
            }
            if (this.$onCanvasActionEnd) {
                off($canvas, EVENT_ACTION_END, this.$onCanvasActionEnd);
                this.$onCanvasActionEnd = null;
            }
            if (this.$onCanvasAction) {
                off($canvas, EVENT_ACTION, this.$onCanvasAction);
                this.$onCanvasAction = null;
            }
        }
        super.disconnectedCallback();
    }
    $getSelections() {
        let selections = [];
        if (this.parentElement) {
            selections = Array.from(this.parentElement.querySelectorAll(this.$getTagNameOf(CROPPER_SELECTION)));
        }
        return selections;
    }
    $initSelection(center = false, resize = false) {
        const { initialCoverage, parentElement } = this;
        if (isPositiveNumber(initialCoverage) && parentElement) {
            const aspectRatio = this.aspectRatio || this.initialAspectRatio;
            let width = (resize ? 0 : this.width) || parentElement.offsetWidth * initialCoverage;
            let height = (resize ? 0 : this.height) || parentElement.offsetHeight * initialCoverage;
            if (isPositiveNumber(aspectRatio)) {
                ({ width, height } = getAdjustedSizes({ aspectRatio, width, height }));
            }
            this.$change(this.x, this.y, width, height);
            if (center) {
                this.$center();
            }
            // Overrides the initial position and size
            this.$initialSelection = {
                x: this.x,
                y: this.y,
                width: this.width,
                height: this.height,
            };
        }
    }
    $createSelection() {
        const newSelection = this.cloneNode(true);
        if (this.hasAttribute('id')) {
            newSelection.removeAttribute('id');
        }
        newSelection.initialCoverage = NaN;
        this.active = false;
        if (this.parentElement) {
            this.parentElement.insertBefore(newSelection, this.nextSibling);
        }
        return newSelection;
    }
    $removeSelection(selection = this) {
        if (this.parentElement) {
            const selections = this.$getSelections();
            if (selections.length > 1) {
                const index = selections.indexOf(selection);
                const activeSelection = selections[index + 1] || selections[index - 1];
                if (activeSelection) {
                    selection.active = false;
                    this.parentElement.removeChild(selection);
                    activeSelection.active = true;
                    activeSelection.$emit(EVENT_CHANGE, {
                        x: activeSelection.x,
                        y: activeSelection.y,
                        width: activeSelection.width,
                        height: activeSelection.height,
                    });
                }
            }
            else {
                this.$clear();
            }
        }
    }
    $handleActionStart(event) {
        var _a, _b;
        if (event.defaultPrevented) {
            return;
        }
        const relatedTarget = (_b = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.relatedEvent) === null || _b === void 0 ? void 0 : _b.target;
        this.$action = '';
        this.$actionStartTarget = relatedTarget;
        if (!this.hidden
            && this.multiple
            && !this.active
            && relatedTarget === this
            && this.parentElement) {
            this.$getSelections().forEach((selection) => {
                selection.active = false;
            });
            this.active = true;
            this.$emit(EVENT_CHANGE, {
                x: this.x,
                y: this.y,
                width: this.width,
                height: this.height,
            });
        }
    }
    $handleAction(event) {
        const { currentTarget, detail } = event;
        if (event.defaultPrevented || !currentTarget || !detail) {
            return;
        }
        const { relatedEvent } = detail;
        let { action } = detail;
        const relatedTarget = relatedEvent
            ? getComposedPathTarget(relatedEvent)
            : null;
        // Switching to another selection
        if (!action && this.multiple) {
            // Get the `action` property from the focusing in selection
            action = this.$action || (relatedTarget === null || relatedTarget === void 0 ? void 0 : relatedTarget.action);
            this.$action = action;
        }
        if (!action
            || (this.hidden && action !== ACTION_SELECT)
            || (this.multiple && !this.active && action !== ACTION_SCALE)) {
            return;
        }
        const { width, height } = this;
        let moveX = detail.endX - detail.startX;
        let moveY = detail.endY - detail.startY;
        let { aspectRatio } = this;
        // Locking aspect ratio by holding shift key
        if (!isPositiveNumber(aspectRatio) && relatedEvent.shiftKey) {
            aspectRatio = isPositiveNumber(width) && isPositiveNumber(height) ? width / height : 1;
        }
        switch (action) {
            case ACTION_SELECT:
                if (moveX !== 0 || moveY !== 0) {
                    // Force to create a square selection for better user experience
                    if (moveX === 0) {
                        moveX = moveY;
                    }
                    else if (moveY === 0) {
                        moveY = moveX;
                    }
                    const { $canvas } = this;
                    const offset = getOffset(currentTarget);
                    (this.multiple && !this.hidden ? this.$createSelection() : this).$change(detail.startX - offset.left, detail.startY - offset.top, Math.abs(moveX), Math.abs(moveY), aspectRatio);
                    if (moveX < 0) {
                        if (moveY < 0) {
                            // ↖️
                            action = ACTION_RESIZE_NORTHWEST;
                        }
                        else if (moveY > 0) {
                            // ↙️
                            action = ACTION_RESIZE_SOUTHWEST;
                        }
                    }
                    else if (moveX > 0) {
                        if (moveY < 0) {
                            // ↗️
                            action = ACTION_RESIZE_NORTHEAST;
                        }
                        else if (moveY > 0) {
                            // ↘️
                            action = ACTION_RESIZE_SOUTHEAST;
                        }
                    }
                    if ($canvas) {
                        $canvas.$action = action;
                    }
                }
                break;
            case ACTION_MOVE:
                if (this.movable && (this.dynamic
                    || (this.$actionStartTarget && this.contains(this.$actionStartTarget)))) {
                    this.$move(moveX, moveY);
                }
                break;
            case ACTION_SCALE:
            case ACTION_TRANSFORM:
                if (relatedEvent && this.zoomable && (this.dynamic
                    || this.contains(relatedEvent.target))) {
                    const offset = getOffset(currentTarget);
                    this.$zoom(detail.scale, relatedEvent.pageX - offset.left, relatedEvent.pageY - offset.top);
                }
                break;
            default:
                this.$resize(action, moveX, moveY, aspectRatio);
        }
    }
    $handleActionEnd() {
        this.$action = '';
        this.$actionStartTarget = null;
    }
    $handleKeyDown(event) {
        if (event.defaultPrevented
            || this.hidden
            || !this.keyboard
            || (this.multiple && !this.active)) {
            return;
        }
        const { activeElement } = document;
        // Disable keyboard control when input something
        if (activeElement && (['INPUT', 'TEXTAREA'].includes(activeElement.tagName)
            || ['true', 'plaintext-only'].includes(activeElement.contentEditable))) {
            return;
        }
        switch (event.key) {
            case 'Backspace':
                if (event.metaKey) {
                    event.preventDefault();
                    this.$removeSelection();
                }
                break;
            case 'Delete':
                event.preventDefault();
                this.$removeSelection();
                break;
            // Move to the left
            case 'ArrowLeft':
                event.preventDefault();
                this.$move(-1, 0);
                break;
            // Move to the right
            case 'ArrowRight':
                event.preventDefault();
                this.$move(1, 0);
                break;
            // Move to the top
            case 'ArrowUp':
                event.preventDefault();
                this.$move(0, -1);
                break;
            // Move to the bottom
            case 'ArrowDown':
                event.preventDefault();
                this.$move(0, 1);
                break;
            case '+':
                event.preventDefault();
                this.$zoom(0.1);
                break;
            case '-':
                event.preventDefault();
                this.$zoom(-0.1);
                break;
        }
    }
    /**
     * Aligns the selection to the center of its parent element.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $center() {
        const { parentElement } = this;
        if (!parentElement) {
            return this;
        }
        const x = (parentElement.offsetWidth - this.width) / 2;
        const y = (parentElement.offsetHeight - this.height) / 2;
        return this.$change(x, y);
    }
    /**
     * Moves the selection.
     * @param {number} x The moving distance in the horizontal direction.
     * @param {number} [y] The moving distance in the vertical direction.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $move(x, y = x) {
        return this.$moveTo(this.x + x, this.y + y);
    }
    /**
     * Moves the selection to a specific position.
     * @param {number} x The new position in the horizontal direction.
     * @param {number} [y] The new position in the vertical direction.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $moveTo(x, y = x) {
        if (!this.movable) {
            return this;
        }
        return this.$change(x, y);
    }
    /**
     * Adjusts the size the selection on a specific side or corner.
     * @param {string} action Indicates the side or corner to resize.
     * @param {number} [offsetX] The horizontal offset of the specific side or corner.
     * @param {number} [offsetY] The vertical offset of the specific side or corner.
     * @param {number} [aspectRatio] The aspect ratio for computing the new size if it is necessary.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $resize(action, offsetX = 0, offsetY = 0, aspectRatio = this.aspectRatio) {
        if (!this.resizable) {
            return this;
        }
        const hasValidAspectRatio = isPositiveNumber(aspectRatio);
        const { $canvas } = this;
        let { x, y, width, height, } = this;
        switch (action) {
            case ACTION_RESIZE_NORTH:
                y += offsetY;
                height -= offsetY;
                if (height < 0) {
                    action = ACTION_RESIZE_SOUTH;
                    height = -height;
                    y -= height;
                }
                if (hasValidAspectRatio) {
                    offsetX = offsetY * aspectRatio;
                    x += offsetX / 2;
                    width -= offsetX;
                    if (width < 0) {
                        width = -width;
                        x -= width;
                    }
                }
                break;
            case ACTION_RESIZE_EAST:
                width += offsetX;
                if (width < 0) {
                    action = ACTION_RESIZE_WEST;
                    width = -width;
                    x -= width;
                }
                if (hasValidAspectRatio) {
                    offsetY = offsetX / aspectRatio;
                    y -= offsetY / 2;
                    height += offsetY;
                    if (height < 0) {
                        height = -height;
                        y -= height;
                    }
                }
                break;
            case ACTION_RESIZE_SOUTH:
                height += offsetY;
                if (height < 0) {
                    action = ACTION_RESIZE_NORTH;
                    height = -height;
                    y -= height;
                }
                if (hasValidAspectRatio) {
                    offsetX = offsetY * aspectRatio;
                    x -= offsetX / 2;
                    width += offsetX;
                    if (width < 0) {
                        width = -width;
                        x -= width;
                    }
                }
                break;
            case ACTION_RESIZE_WEST:
                x += offsetX;
                width -= offsetX;
                if (width < 0) {
                    action = ACTION_RESIZE_EAST;
                    width = -width;
                    x -= width;
                }
                if (hasValidAspectRatio) {
                    offsetY = offsetX / aspectRatio;
                    y += offsetY / 2;
                    height -= offsetY;
                    if (height < 0) {
                        height = -height;
                        y -= height;
                    }
                }
                break;
            case ACTION_RESIZE_NORTHEAST:
                if (hasValidAspectRatio) {
                    offsetY = -offsetX / aspectRatio;
                }
                y += offsetY;
                height -= offsetY;
                width += offsetX;
                if (width < 0 && height < 0) {
                    action = ACTION_RESIZE_SOUTHWEST;
                    width = -width;
                    height = -height;
                    x -= width;
                    y -= height;
                }
                else if (width < 0) {
                    action = ACTION_RESIZE_NORTHWEST;
                    width = -width;
                    x -= width;
                }
                else if (height < 0) {
                    action = ACTION_RESIZE_SOUTHEAST;
                    height = -height;
                    y -= height;
                }
                break;
            case ACTION_RESIZE_NORTHWEST:
                if (hasValidAspectRatio) {
                    offsetY = offsetX / aspectRatio;
                }
                x += offsetX;
                y += offsetY;
                width -= offsetX;
                height -= offsetY;
                if (width < 0 && height < 0) {
                    action = ACTION_RESIZE_SOUTHEAST;
                    width = -width;
                    height = -height;
                    x -= width;
                    y -= height;
                }
                else if (width < 0) {
                    action = ACTION_RESIZE_NORTHEAST;
                    width = -width;
                    x -= width;
                }
                else if (height < 0) {
                    action = ACTION_RESIZE_SOUTHWEST;
                    height = -height;
                    y -= height;
                }
                break;
            case ACTION_RESIZE_SOUTHEAST:
                if (hasValidAspectRatio) {
                    offsetY = offsetX / aspectRatio;
                }
                width += offsetX;
                height += offsetY;
                if (width < 0 && height < 0) {
                    action = ACTION_RESIZE_NORTHWEST;
                    width = -width;
                    height = -height;
                    x -= width;
                    y -= height;
                }
                else if (width < 0) {
                    action = ACTION_RESIZE_SOUTHWEST;
                    width = -width;
                    x -= width;
                }
                else if (height < 0) {
                    action = ACTION_RESIZE_NORTHEAST;
                    height = -height;
                    y -= height;
                }
                break;
            case ACTION_RESIZE_SOUTHWEST:
                if (hasValidAspectRatio) {
                    offsetY = -offsetX / aspectRatio;
                }
                x += offsetX;
                width -= offsetX;
                height += offsetY;
                if (width < 0 && height < 0) {
                    action = ACTION_RESIZE_NORTHEAST;
                    width = -width;
                    height = -height;
                    x -= width;
                    y -= height;
                }
                else if (width < 0) {
                    action = ACTION_RESIZE_SOUTHEAST;
                    width = -width;
                    x -= width;
                }
                else if (height < 0) {
                    action = ACTION_RESIZE_NORTHWEST;
                    height = -height;
                    y -= height;
                }
                break;
        }
        if ($canvas) {
            $canvas.$setAction(action);
        }
        return this.$change(x, y, width, height);
    }
    /**
     * Zooms the selection.
     * @param {number} scale The zoom factor. Positive numbers for zooming in, and negative numbers for zooming out.
     * @param {number} [x] The zoom origin in the horizontal, defaults to the center of the selection.
     * @param {number} [y] The zoom origin in the vertical, defaults to the center of the selection.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $zoom(scale, x, y) {
        if (!this.zoomable || scale === 0) {
            return this;
        }
        if (scale < 0) {
            scale = 1 / (1 - scale);
        }
        else {
            scale += 1;
        }
        const { width, height } = this;
        const newWidth = width * scale;
        const newHeight = height * scale;
        let newX = this.x;
        let newY = this.y;
        if (isNumber(x) && isNumber(y)) {
            newX -= (newWidth - width) * ((x - this.x) / width);
            newY -= (newHeight - height) * ((y - this.y) / height);
        }
        else {
            // Zoom from the center of the selection
            newX -= (newWidth - width) / 2;
            newY -= (newHeight - height) / 2;
        }
        return this.$change(newX, newY, newWidth, newHeight);
    }
    /**
     * Changes the position and/or size of the selection.
     * @param {number} x The new position in the horizontal direction.
     * @param {number} y The new position in the vertical direction.
     * @param {number} [width] The new width.
     * @param {number} [height] The new height.
     * @param {number} [aspectRatio] The new aspect ratio for this change only.
     * @param {number} [_force] Force change.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $change(x, y, width = this.width, height = this.height, aspectRatio = this.aspectRatio, _force = false) {
        if (this.$changing
            || !isNumber(x)
            || !isNumber(y)
            || !isNumber(width)
            || !isNumber(height)
            || width < 0
            || height < 0) {
            return this;
        }
        if (isPositiveNumber(aspectRatio)) {
            ({ width, height } = getAdjustedSizes({ aspectRatio, width, height }, 'cover'));
        }
        if (!this.precise) {
            x = Math.round(x);
            y = Math.round(y);
            width = Math.round(width);
            height = Math.round(height);
        }
        if (x === this.x
            && y === this.y
            && width === this.width
            && height === this.height
            && Object.is(aspectRatio, this.aspectRatio)
            && !_force) {
            return this;
        }
        if (this.hidden) {
            this.hidden = false;
        }
        if (this.$emit(EVENT_CHANGE, {
            x,
            y,
            width,
            height,
        }) === false) {
            return this;
        }
        this.$changing = true;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.$changing = false;
        return this.$render();
    }
    /**
     * Resets the selection to its initial position and size.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $reset() {
        const { x, y, width, height, } = this.$initialSelection;
        return this.$change(x, y, width, height);
    }
    /**
     * Clears the selection.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $clear() {
        this.$change(0, 0, 0, 0, NaN, true);
        this.hidden = true;
        return this;
    }
    /**
     * Refreshes the position or size of the selection.
     * @returns {CropperSelection} Returns `this` for chaining.
     */
    $render() {
        return this.$setStyles({
            transform: `translate(${this.x}px, ${this.y}px)`,
            width: this.width,
            height: this.height,
        });
    }
    /**
     * Generates a real canvas element, with the image (selected area only) draw into if there is one.
     * @param {object} [options] The available options.
     * @param {number} [options.width] The width of the canvas.
     * @param {number} [options.height] The height of the canvas.
     * @param {Function} [options.beforeDraw] The function called before drawing the image onto the canvas.
     * @returns {Promise} Returns a promise that resolves to the generated canvas element.
     */
    $toCanvas(options) {
        return new Promise((resolve, reject) => {
            if (!this.isConnected) {
                reject(new Error('The current element is not connected to the DOM.'));
                return;
            }
            const canvas = document.createElement('canvas');
            let { width, height } = this;
            let scale = 1;
            if (isPlainObject(options)
                && (isPositiveNumber(options.width) || isPositiveNumber(options.height))) {
                ({ width, height } = getAdjustedSizes({
                    aspectRatio: width / height,
                    width: options.width,
                    height: options.height,
                }));
                scale = width / this.width;
            }
            canvas.width = width;
            canvas.height = height;
            if (!this.$canvas) {
                resolve(canvas);
                return;
            }
            const cropperImage = this.$canvas.querySelector(this.$getTagNameOf(CROPPER_IMAGE));
            if (!cropperImage) {
                resolve(canvas);
                return;
            }
            cropperImage.$ready().then((image) => {
                const context = canvas.getContext('2d');
                if (context) {
                    const [a, b, c, d, e, f] = cropperImage.$getTransform();
                    const offsetX = -this.x;
                    const offsetY = -this.y;
                    const translateX = ((offsetX * d) - (c * offsetY)) / ((a * d) - (c * b));
                    const translateY = ((offsetY * a) - (b * offsetX)) / ((a * d) - (c * b));
                    let newE = a * translateX + c * translateY + e;
                    let newF = b * translateX + d * translateY + f;
                    let destWidth = image.naturalWidth;
                    let destHeight = image.naturalHeight;
                    if (scale !== 1) {
                        newE *= scale;
                        newF *= scale;
                        destWidth *= scale;
                        destHeight *= scale;
                    }
                    const centerX = destWidth / 2;
                    const centerY = destHeight / 2;
                    context.fillStyle = 'transparent';
                    context.fillRect(0, 0, width, height);
                    if (isPlainObject(options) && isFunction(options.beforeDraw)) {
                        options.beforeDraw.call(this, context, canvas);
                    }
                    context.save();
                    // Move the transform origin to the center of the image.
                    // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin
                    context.translate(centerX, centerY);
                    context.transform(a, b, c, d, newE, newF);
                    // Move the transform origin to the top-left of the image.
                    context.translate(-centerX, -centerY);
                    context.drawImage(image, 0, 0, destWidth, destHeight);
                    context.restore();
                }
                resolve(canvas);
            }).catch(reject);
        });
    }
}
CropperSelection.$name = CROPPER_SELECTION;
CropperSelection.$version = '2.2.0';

var style$2 = `:host{display:flex;flex-direction:column;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}:host([bordered]){border:1px dashed var(--theme-color)}:host([covered]){bottom:0;left:0;position:absolute;right:0;top:0}:host>span{display:flex;flex:1}:host>span+span{border-top:1px dashed var(--theme-color)}:host>span>span{flex:1}:host>span>span+span{border-left:1px dashed var(--theme-color)}`;

class CropperGrid extends CropperElement {
    constructor() {
        super(...arguments);
        this.$style = style$2;
        this.bordered = false;
        this.columns = 3;
        this.covered = false;
        this.rows = 3;
        this.slottable = false;
        this.themeColor = 'rgba(238, 238, 238, 0.5)';
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'bordered',
            'columns',
            'covered',
            'rows',
        ]);
    }
    $propertyChangedCallback(name, oldValue, newValue) {
        if (Object.is(newValue, oldValue)) {
            return;
        }
        super.$propertyChangedCallback(name, oldValue, newValue);
        if (name === 'rows' || name === 'columns') {
            this.$nextTick(() => {
                this.$render();
            });
        }
    }
    connectedCallback() {
        super.connectedCallback();
        this.$render();
    }
    $render() {
        const shadow = this.$getShadowRoot();
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < this.rows; i += 1) {
            const row = document.createElement('span');
            row.setAttribute('data-cropper-grid-row', '');
            row.setAttribute('role', 'row');
            for (let j = 0; j < this.columns; j += 1) {
                const column = document.createElement('span');
                column.setAttribute('role', 'gridcell');
                row.appendChild(column);
            }
            fragment.appendChild(row);
        }
        if (shadow) {
            shadow.querySelectorAll('[data-cropper-grid-row]').forEach((row) => row.remove());
            shadow.appendChild(fragment);
        }
    }
}
CropperGrid.$name = CROPPER_GIRD;
CropperGrid.$version = '2.2.0';

var style$1 = `:host{display:inline-block;height:1em;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1em}:host:after,:host:before{background-color:var(--theme-color);content:"";display:block;position:absolute}:host:before{height:1px;left:0;top:50%;transform:translateY(-50%);width:100%}:host:after{height:100%;left:50%;top:0;transform:translateX(-50%);width:1px}:host([centered]){left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}`;

class CropperCrosshair extends CropperElement {
    constructor() {
        super(...arguments);
        this.$style = style$1;
        this.centered = false;
        this.slottable = false;
        this.themeColor = 'rgba(238, 238, 238, 0.5)';
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'centered',
        ]);
    }
}
CropperCrosshair.$name = CROPPER_CROSSHAIR;
CropperCrosshair.$version = '2.2.0';

var style = `:host{display:block;height:100%;overflow:hidden;position:relative;width:100%}`;

const canvasCache = new WeakMap();
const imageCache = new WeakMap();
const selectionCache = new WeakMap();
const sourceImageCache = new WeakMap();
const RESIZE_BOTH = 'both';
const RESIZE_HORIZONTAL = 'horizontal';
const RESIZE_VERTICAL = 'vertical';
const RESIZE_NONE = 'none';
class CropperViewer extends CropperElement {
    constructor() {
        super(...arguments);
        this.$onSelectionChange = null;
        this.$onSourceImageLoad = null;
        this.$onSourceImageTransform = null;
        this.$scale = 1;
        this.$style = style;
        this.resize = RESIZE_VERTICAL;
        this.selection = '';
        this.slottable = false;
    }
    set $image(element) {
        imageCache.set(this, element);
    }
    get $image() {
        return imageCache.get(this);
    }
    set $sourceImage(element) {
        sourceImageCache.set(this, element);
    }
    get $sourceImage() {
        return sourceImageCache.get(this);
    }
    set $canvas(element) {
        canvasCache.set(this, element);
    }
    get $canvas() {
        return canvasCache.get(this);
    }
    set $selection(element) {
        selectionCache.set(this, element);
    }
    get $selection() {
        return selectionCache.get(this);
    }
    static get observedAttributes() {
        return super.observedAttributes.concat([
            'resize',
            'selection',
        ]);
    }
    connectedCallback() {
        var _a, _b;
        super.connectedCallback();
        let $selection = null;
        if (this.selection) {
            $selection = (_b = (_a = getRootDocument(this)) === null || _a === void 0 ? void 0 : _a.querySelector(this.selection)) !== null && _b !== void 0 ? _b : null;
        }
        else {
            $selection = this.closest(this.$getTagNameOf(CROPPER_SELECTION));
        }
        if (isElement($selection)) {
            this.$selection = $selection;
            this.$onSelectionChange = this.$handleSelectionChange.bind(this);
            on($selection, EVENT_CHANGE, this.$onSelectionChange);
            const $canvas = $selection.closest(this.$getTagNameOf(CROPPER_CANVAS));
            if ($canvas) {
                this.$canvas = $canvas;
                const $sourceImage = $canvas.querySelector(this.$getTagNameOf(CROPPER_IMAGE));
                if ($sourceImage) {
                    this.$sourceImage = $sourceImage;
                    this.$image = $sourceImage.cloneNode(true);
                    this.$getShadowRoot().appendChild(this.$image);
                    this.$onSourceImageLoad = this.$handleSourceImageLoad.bind(this);
                    this.$onSourceImageTransform = this.$handleSourceImageTransform.bind(this);
                    on($sourceImage.$image, EVENT_LOAD, this.$onSourceImageLoad);
                    on($sourceImage, EVENT_TRANSFORM, this.$onSourceImageTransform);
                }
            }
            this.$render();
        }
    }
    disconnectedCallback() {
        const { $image, $selection, $sourceImage } = this;
        if ($selection && this.$onSelectionChange) {
            off($selection, EVENT_CHANGE, this.$onSelectionChange);
            this.$onSelectionChange = null;
        }
        if ($sourceImage && this.$onSourceImageLoad) {
            off($sourceImage.$image, EVENT_LOAD, this.$onSourceImageLoad);
            this.$onSourceImageLoad = null;
        }
        if ($sourceImage && this.$onSourceImageTransform) {
            off($sourceImage, EVENT_TRANSFORM, this.$onSourceImageTransform);
            this.$onSourceImageTransform = null;
        }
        const shadow = this.$getShadowRoot();
        if ($image && (shadow === null || shadow === void 0 ? void 0 : shadow.contains($image))) {
            shadow.removeChild($image);
        }
        super.disconnectedCallback();
    }
    $handleSelectionChange(event) {
        this.$render(event.defaultPrevented ? this.$selection : event.detail);
    }
    $handleSourceImageLoad() {
        const { $image, $sourceImage } = this;
        const oldSrc = $image.getAttribute('src');
        const newSrc = $sourceImage.getAttribute('src');
        if (newSrc && newSrc !== oldSrc) {
            $image.setAttribute('src', newSrc);
            $image.$ready(() => {
                this.$render();
            });
        }
    }
    $handleSourceImageTransform(event) {
        this.$render(undefined, event.detail.matrix);
    }
    $render(selection, matrix) {
        const { $canvas, $selection } = this;
        if (!selection && !$selection.hidden) {
            selection = $selection;
        }
        if (!selection || (selection.x === 0
            && selection.y === 0
            && selection.width === 0
            && selection.height === 0)) {
            selection = {
                x: 0,
                y: 0,
                width: $canvas.offsetWidth,
                height: $canvas.offsetHeight,
            };
        }
        const { x, y, width, height, } = selection;
        const styles = {};
        const { clientWidth, clientHeight } = this;
        let newWidth = clientWidth;
        let newHeight = clientHeight;
        let scale = NaN;
        switch (this.resize) {
            case RESIZE_BOTH:
                scale = 1;
                newWidth = width;
                newHeight = height;
                styles.width = width;
                styles.height = height;
                break;
            case RESIZE_HORIZONTAL:
                scale = height > 0 ? clientHeight / height : 0;
                newWidth = width * scale;
                styles.width = newWidth;
                break;
            case RESIZE_VERTICAL:
                scale = width > 0 ? clientWidth / width : 0;
                newHeight = height * scale;
                styles.height = newHeight;
                break;
            case RESIZE_NONE:
            default:
                if (clientWidth > 0) {
                    scale = width > 0 ? clientWidth / width : 0;
                }
                else if (clientHeight > 0) {
                    scale = height > 0 ? clientHeight / height : 0;
                }
        }
        this.$scale = scale;
        this.$setStyles(styles);
        if (this.$sourceImage) {
            // Transform the image by the selection offset after the next DOM update cycle
            setTimeout(() => {
                this.$transformImageByOffset(matrix !== null && matrix !== void 0 ? matrix : this.$sourceImage.$getTransform(), -x, -y);
            });
        }
    }
    $transformImageByOffset(matrix, x, y) {
        const { $image, $scale, $sourceImage, } = this;
        if ($sourceImage && $image && $scale >= 0) {
            const [a, b, c, d, e, f] = matrix;
            const translateX = ((x * d) - (c * y)) / ((a * d) - (c * b));
            const translateY = ((y * a) - (b * x)) / ((a * d) - (c * b));
            const newE = a * translateX + c * translateY + e;
            const newF = b * translateX + d * translateY + f;
            // Do not use `$image` here, because it is just a clone of the source image
            // and may not have the correct size when using SVG image format in Safari, see #1290.
            $sourceImage.$ready((image) => {
                this.$setStyles.call($image, {
                    width: image.naturalWidth * $scale,
                    height: image.naturalHeight * $scale,
                });
            });
            $image.$setTransform(a, b, c, d, newE * $scale, newF * $scale);
        }
    }
}
CropperViewer.$name = CROPPER_VIEWER;
CropperViewer.$version = '2.2.0';

var DEFAULT_TEMPLATE = ('<cropper-canvas background>'
    + '<cropper-image rotatable scalable skewable translatable></cropper-image>'
    + '<cropper-shade hidden></cropper-shade>'
    + '<cropper-handle action="select" plain></cropper-handle>'
    + '<cropper-selection initial-coverage="0.5" movable resizable>'
    + '<cropper-grid role="grid" bordered covered></cropper-grid>'
    + '<cropper-crosshair centered></cropper-crosshair>'
    + '<cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>'
    + '<cropper-handle action="n-resize"></cropper-handle>'
    + '<cropper-handle action="e-resize"></cropper-handle>'
    + '<cropper-handle action="s-resize"></cropper-handle>'
    + '<cropper-handle action="w-resize"></cropper-handle>'
    + '<cropper-handle action="ne-resize"></cropper-handle>'
    + '<cropper-handle action="nw-resize"></cropper-handle>'
    + '<cropper-handle action="se-resize"></cropper-handle>'
    + '<cropper-handle action="sw-resize"></cropper-handle>'
    + '</cropper-selection>'
    + '</cropper-canvas>');

const REGEXP_ALLOWED_ELEMENTS = /^(img|canvas)$/;
const REGEXP_BLOCKED_TAGS = /<(\/?(?:script|style)[^>]*)>/gi;
const DEFAULT_OPTIONS = {
    template: DEFAULT_TEMPLATE,
};
CropperCanvas.$define();
CropperCrosshair.$define();
CropperGrid.$define();
CropperHandle.$define();
CropperImage.$define();
CropperSelection.$define();
CropperShade.$define();
CropperViewer.$define();
class Cropper {
    constructor(element, options) {
        var _a;
        this.options = DEFAULT_OPTIONS;
        if (isString(element)) {
            element = document.querySelector(element);
        }
        if (!isElement(element) || !REGEXP_ALLOWED_ELEMENTS.test(element.localName)) {
            throw new Error('The first argument is required and must be an <img> or <canvas> element.');
        }
        this.element = element;
        options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
        this.options = options;
        let { container } = options;
        if (container) {
            if (isString(container)) {
                container = (_a = getRootDocument(element)) === null || _a === void 0 ? void 0 : _a.querySelector(container);
            }
            if (!isElement(container)) {
                throw new Error('The `container` option must be an element or a valid selector.');
            }
        }
        if (!isElement(container)) {
            if (element.parentElement) {
                container = element.parentElement;
            }
            else {
                container = element.ownerDocument.body;
            }
        }
        this.container = container;
        const tagName = element.localName;
        let src = '';
        if (tagName === 'img') {
            ({ src } = element);
        }
        else if (tagName === 'canvas' && window.HTMLCanvasElement) {
            src = element.toDataURL();
        }
        const { template } = options;
        if (template && isString(template)) {
            const templateElement = document.createElement('template');
            const documentFragment = document.createDocumentFragment();
            templateElement.innerHTML = template.replace(REGEXP_BLOCKED_TAGS, '&lt;$1&gt;');
            documentFragment.appendChild(templateElement.content);
            Array.from(documentFragment.querySelectorAll(CROPPER_IMAGE)).forEach((image) => {
                image.setAttribute('src', src);
                image.setAttribute('alt', element.alt || 'The image to crop');
                // Inherit additional attributes from HTMLImageElement
                if (tagName === 'img') {
                    [
                        'crossorigin',
                        'decoding',
                        'elementtiming',
                        'fetchpriority',
                        'loading',
                        'referrerpolicy',
                        'sizes',
                        'srcset',
                    ].forEach((attribute) => {
                        if (element.hasAttribute(attribute)) {
                            image.setAttribute(attribute, element.getAttribute(attribute) || '');
                        }
                    });
                }
            });
            if (element.parentElement) {
                element.style.display = 'none';
                container.insertBefore(documentFragment, element.nextSibling);
            }
            else {
                container.appendChild(documentFragment);
            }
        }
    }
    getCropperCanvas() {
        return this.container.querySelector(CROPPER_CANVAS);
    }
    getCropperImage() {
        return this.container.querySelector(CROPPER_IMAGE);
    }
    getCropperSelection() {
        return this.container.querySelector(CROPPER_SELECTION);
    }
    getCropperSelections() {
        return this.container.querySelectorAll(CROPPER_SELECTION);
    }
    destroy() {
        var _a;
        const cropperCanvas = this.getCropperCanvas();
        if (cropperCanvas) {
            (_a = cropperCanvas.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(cropperCanvas);
        }
        if (this.element) {
            this.element.style.display = '';
        }
    }
}
Cropper.version = '2.2.0';

export { ACTION_MOVE, ACTION_NONE, ACTION_RESIZE_EAST, ACTION_RESIZE_NORTH, ACTION_RESIZE_NORTHEAST, ACTION_RESIZE_NORTHWEST, ACTION_RESIZE_SOUTH, ACTION_RESIZE_SOUTHEAST, ACTION_RESIZE_SOUTHWEST, ACTION_RESIZE_WEST, ACTION_ROTATE, ACTION_SCALE, ACTION_SELECT, ACTION_TRANSFORM, ATTRIBUTE_ACTION, CROPPER_CANVAS, CROPPER_CROSSHAIR, CROPPER_GIRD, CROPPER_HANDLE, CROPPER_IMAGE, CROPPER_SELECTION, CROPPER_SHADE, CROPPER_VIEWER, CropperCanvas, CropperCrosshair, CropperElement, CropperGrid, CropperHandle, CropperImage, CropperSelection, CropperShade, CropperViewer, DEFAULT_TEMPLATE, EVENT_ACTION, EVENT_ACTION_END, EVENT_ACTION_MOVE, EVENT_ACTION_START, EVENT_CHANGE, EVENT_ERROR, EVENT_KEYDOWN, EVENT_LOAD, EVENT_POINTER_DOWN, EVENT_POINTER_MOVE, EVENT_POINTER_UP, EVENT_RESIZE, EVENT_TOUCH_END, EVENT_TOUCH_MOVE, EVENT_TOUCH_START, EVENT_TRANSFORM, EVENT_WHEEL, HAS_POINTER_EVENT, IS_BROWSER, IS_TOUCH_DEVICE, NAMESPACE, OBJECT_FIT_CONTAIN, OBJECT_FIT_COVER, OBJECT_FIT_FILL, OBJECT_FIT_NONE, OBJECT_FIT_SCALE_DOWN, WINDOW, Cropper as default, emit, getAdjustedSizes, getComposedPathTarget, getOffset, getRootDocument, isElement, isFunction, isNaN, isNumber, isObject, isPlainObject, isPositiveNumber, isString, isUndefined, multiplyMatrices, nextTick, off, on, once, toAngleInRadian, toCamelCase, toKebabCase };