// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Disableable } from '../../../Behaviors/Disableable';
import { Focusable } from '../../../Behaviors/Focusable';
import { Themeable } from '../../../Behaviors/Themeable';
import { ColorController } from '../../../Controllers/ColorController';
import { ResizeController } from '../../../Controllers/ResizeController';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Property } from '../../../Decorators/PropertyDecorator';
import { QueryChild } from '../../../Decorators/QueryChildDecorator';
import { FlowDirection } from '../../../Types/FlowDirection';
import { CustomElement } from '../../Abstracts/CustomElement';
import { ColorThumbElement } from '../ColorThumb/ColorThumbElement';
import { colorAreaElementCosmopolitanStyle } from './Themes/ColorAreaElement.Cosmopolitan';
import { colorAreaElementJoyStyle } from './Themes/ColorAreaElement.Joy';
import { colorAreaElementMemphisStyle } from './Themes/ColorAreaElement.Memphis';
// #endregion
/**
 * Color Area - A user interface component for displaying a colored region or background.
 *
 * @element mosaik-color-area
 *
 * @description
 * The Color Area component is designed to represent a specific area of a user interface filled with a particular color.
 * It is often used to create visual distinctions, highlight sections, or provide background colors for elements.
 * Color Areas are commonly employed to enhance the overall visual aesthetics of a user interface and improve readability.
 *
 * @category Media
 *
 * @example
 * Basic usage:
 * ```html
 * <mosaik-color-area></mosaik-color-area>
 * ```
 *
 * @example
 * With an initial color value:
 * ```html
 * <mosaik-color-area color="#ff0000"></mosaik-color-area>
 * ```
 *
 * @public
 */
let ColorAreaElement = class ColorAreaElement extends Themeable(Disableable(Focusable(CustomElement))) {
    // #region Fields
    _resizeController;
    _colorController;
    _thumb;
    _inputX;
    _inputY;
    _color;
    _x;
    _y;
    _step;
    _altered;
    _boundingClientRect;
    _pointerDown;
    _valueChanged = false;
    _activeAxis = 'x';
    _activeKeys = new Set();
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._color = '';
        this._x = 1;
        this._y = 1;
        this._step = 0.01;
        this._pointerDown = false;
        this._altered = 0;
        this._resizeController = new ResizeController(this, {
            callback: (entries) => {
                for (const entry of entries) {
                    this._boundingClientRect = entry.contentRect;
                }
            }
        });
        this._colorController = new ColorController(this, {
            extractColorFromState: () => ({
                h: this.hue,
                s: this.x,
                v: this.y
            }),
            applyColorToState: ({ s, v }) => {
                this.x = s;
                this.y = v;
            }
        });
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     * The `is` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-color-area';
    }
    /**
     * Gets or sets the `color` property.
     *
     * @public
     * @attr
     */
    get color() {
        return this._color;
    }
    set color(value) {
        if (this._color !== value) {
            this._color = value;
            this.requestUpdate('color');
        }
    }
    /**
     * Gets or sets the `x` property.
     *
     * @private
     * @template
     */
    get x() {
        return this._x;
    }
    set x(value) {
        if (this._x !== value) {
            const oldValue = this._x;
            if (this._inputX) {
                // Use the native `input[type='range']` control to validate this value after `firstUpdate`
                this._inputX.value = value.toString();
                this._x = this._inputX.valueAsNumber;
            }
            else {
                this._x = value;
            }
            this.requestUpdate('x', oldValue);
        }
    }
    /**
     * Gets or sets the `y` property.
     *
     * @private
     * @template
     */
    get y() {
        return this._y;
    }
    set y(value) {
        if (this._y !== value) {
            const oldValue = this._y;
            if (this._inputY) {
                // Use the native `input[type='range']` control to validate this value after `firstUpdate`
                this._inputY.value = value.toString();
                this._y = this._inputY.valueAsNumber;
            }
            else {
                this._y = value;
            }
            this.requestUpdate('y', oldValue);
        }
    }
    /**
     * Gets or sets the `hue` property.
     *
     * @private
     * @template
     */
    get hue() {
        return this._colorController.hue;
    }
    set hue(value) {
        if (this._colorController.hue !== value) {
            this._colorController.hue = value;
            this.requestUpdate('hue');
        }
    }
    /**
     * Gets or sets the `step` property.
     *
     * @private
     * @template
     */
    get step() {
        return this._step;
    }
    set step(value) {
        if (this._step !== value) {
            this._step = value;
            this.requestUpdate('step');
        }
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this.on('focus', () => this.handleFocus());
        this.on('blur', () => this.handleBlur());
        this.on('keyup', (e) => this.handleKeyup(e));
        this.on('keydown', (e) => this.handleKeydown(e));
    }
    /**
     * @protected
     * @override
     */
    onApplyTemplate() {
        super.onApplyTemplate();
        const thumb = this.getTemplatePart('thumb');
        thumb.on('pointerdown', (e) => this.handlePointerdown(e));
        thumb.on('pointermove', (e) => this.handlePointermove(e));
        thumb.on('pointerup', (e) => this.handlePointerup(e));
        thumb.on('pointercancel', (e) => this.handlePointerup(e));
        thumb.on('pointerleave', (e) => this.handlePointerup(e));
        this._boundingClientRect = this.getBoundingClientRect();
    }
    forwardFocus() {
        // this.focused = this.hasVisibleFocusInTree();
        if (this._activeAxis === 'x') {
            this._inputX.focus();
        }
        else {
            this._inputY.focus();
        }
    }
    handleFocus() {
        this.isFocused = true;
        this._valueChanged = false;
    }
    handleBlur() {
        if (this._pointerDown) {
            return;
        }
        this._altered = 0;
        this.isFocused = false;
        this._valueChanged = false;
    }
    handleKeydown(event) {
        this._altered = [event.shiftKey, event.ctrlKey, event.altKey].filter((key) => !!key).length;
        const isArrowKey = event.code.search('Arrow') === 0 ||
            event.code.search('Page') === 0 ||
            event.code.search('Home') === 0 ||
            event.code.search('End') === 0;
        if (isArrowKey) {
            event.preventDefault();
            this._activeKeys.add(event.code);
            this.handleKeypress();
        }
    }
    handleKeypress() {
        let deltaX = 0;
        let deltaY = 0;
        const step = Math.max(this.step, this._altered * 5 * this.step);
        this._activeKeys.forEach((code) => {
            switch (code) {
                case 'ArrowUp':
                    deltaY = step;
                    break;
                case 'ArrowDown':
                    deltaY = step * -1;
                    break;
                case 'ArrowLeft':
                    deltaX = this.step * (this.dir === FlowDirection.LeftToRight ? -1 : 1);
                    break;
                case 'ArrowRight':
                    deltaX = this.step * (this.dir === FlowDirection.LeftToRight ? 1 : -1);
                    break;
                case 'PageUp':
                    deltaY = step * 10;
                    break;
                case 'PageDown':
                    deltaY = step * -10;
                    break;
                case 'Home':
                    deltaX = step * (this.dir === FlowDirection.LeftToRight ? -10 : 10);
                    break;
                case 'End':
                    deltaX = step * (this.dir === FlowDirection.LeftToRight ? 10 : -10);
                    break;
                default:
                    break;
            }
        });
        if (deltaX !== 0) {
            this._activeAxis = 'x';
            this._inputX.focus();
        }
        else if (deltaY !== 0) {
            this._activeAxis = 'y';
            this._inputY.focus();
        }
        this.x = Math.min(1, Math.max(this.x + deltaX, 0));
        this.y = Math.min(1, Math.max(this.y + deltaY, 0));
        this._colorController.savePreviousColor();
        this._colorController.applyColorFromState();
        if (deltaX !== 0 || deltaY !== 0) {
            this._valueChanged = true;
            this.dispatchEvent(new Event('input', {
                bubbles: true,
                composed: true
            }));
            const shouldApplyDefault = this.dispatchEvent(new Event('change', {
                bubbles: true,
                composed: true,
                cancelable: true
            }));
            if (!shouldApplyDefault) {
                this._colorController.restorePreviousColor();
            }
        }
    }
    handleKeyup(event) {
        event.preventDefault();
        this._activeKeys.delete(event.code);
    }
    handleInput(event) {
        this[event.target.name] = event.target.valueAsNumber;
        this._colorController.applyColorFromState();
    }
    handleChange(event) {
        this.handleInput(event);
        this.emit('change', {
            bubbles: true,
            composed: true,
            cancelable: true
        });
    }
    handlePointerdown(event) {
        if (event.button !== 0) {
            event.preventDefault();
            return;
        }
        this._pointerDown = true;
        this._colorController.savePreviousColor();
        this._boundingClientRect = this.getBoundingClientRect();
        event.target.setPointerCapture(event.pointerId);
        if (event.pointerType === 'mouse') {
            this.isFocused = true;
        }
    }
    handlePointermove(event) {
        const [x, y] = this.calculateHandlePosition(event);
        this._valueChanged = false;
        this.x = x;
        this.y = 1 - y;
        this._colorController.applyColorFromState();
        this.dispatchEvent(new Event('input', {
            bubbles: true,
            composed: true,
            cancelable: true
        }));
    }
    handlePointerup(event) {
        event.preventDefault();
        this._pointerDown = false;
        event.target.releasePointerCapture(event.pointerId);
        const shouldApplyDefault = this.dispatchEvent(new Event('change', {
            bubbles: true,
            composed: true,
            cancelable: true
        }));
        this._inputX.focus();
        if (event.pointerType === 'mouse') {
            this.isFocused = false;
        }
        if (!shouldApplyDefault) {
            this._colorController.restorePreviousColor();
        }
    }
    handleAreaPointerdown(event) {
        if (event.button !== 0) {
            return;
        }
        event.stopPropagation();
        event.preventDefault();
        this._thumb.dispatchEvent(new PointerEvent('pointerdown', event));
        this.handlePointermove(event);
    }
    /**
     * Returns the value under the cursor
     * @param: PointerEvent on slider
     * @return: Slider value that correlates to the position under the pointer
     */
    calculateHandlePosition(event) {
        if (!this._boundingClientRect) {
            return [this.x, this.y];
        }
        const rect = this._boundingClientRect;
        const minOffsetX = rect.left;
        const minOffsetY = rect.top;
        const offsetX = event.clientX;
        const offsetY = event.clientY;
        const width = rect.width;
        const height = rect.height;
        const percentX = Math.max(0, Math.min(1, (offsetX - minOffsetX) / width));
        const percentY = Math.max(0, Math.min(1, (offsetY - minOffsetY) / height));
        return [this.dir === FlowDirection.LeftToRight ? percentX : 1 - percentX, percentY];
    }
};
__decorate([
    QueryChild('[part="thumb"]'),
    __metadata("design:type", ColorThumbElement)
], ColorAreaElement.prototype, "_thumb", void 0);
__decorate([
    QueryChild('[part="slider x"]'),
    __metadata("design:type", HTMLInputElement)
], ColorAreaElement.prototype, "_inputX", void 0);
__decorate([
    QueryChild('[part="slider y"]'),
    __metadata("design:type", HTMLInputElement)
], ColorAreaElement.prototype, "_inputY", void 0);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], ColorAreaElement.prototype, "color", null);
__decorate([
    Property({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], ColorAreaElement.prototype, "x", null);
__decorate([
    Property({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], ColorAreaElement.prototype, "y", null);
__decorate([
    Property({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], ColorAreaElement.prototype, "hue", null);
__decorate([
    Property({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], ColorAreaElement.prototype, "step", null);
ColorAreaElement = __decorate([
    Component({
        selector: 'mosaik-color-area',
        // template: colorAreaElementTemplate,
        themes: {
            joy: colorAreaElementJoyStyle,
            memphis: colorAreaElementMemphisStyle,
            cosmopolitan: colorAreaElementCosmopolitanStyle
        },
        imports: [
            ColorThumbElement
        ]
    }),
    __metadata("design:paramtypes", [])
], ColorAreaElement);
export { ColorAreaElement };
//# sourceMappingURL=ColorAreaElement.js.map