"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get EDITOR_TYPE () {
        return EDITOR_TYPE;
    },
    get TextEditor () {
        return TextEditor;
    }
});
const _baseEditor = require("../baseEditor");
const _eventManager = /*#__PURE__*/ _interop_require_default(require("../../eventManager"));
const _browser = require("../../helpers/browser");
const _element = require("../../helpers/dom/element");
const _number = require("../../helpers/number");
const _autoResize = require("../../utils/autoResize");
const _mixed = require("../../helpers/mixed");
const _caretPositioner = require("./caretPositioner");
const _a11y = require("../../helpers/a11y");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
const EDITOR_VISIBLE_CLASS_NAME = 'ht_editor_visible';
const EDITOR_HIDDEN_CLASS_NAME = 'ht_editor_hidden';
const SHORTCUTS_GROUP = 'textEditor';
const EDITOR_TYPE = 'text';
class TextEditor extends _baseEditor.BaseEditor {
    static get EDITOR_TYPE() {
        return EDITOR_TYPE;
    }
    /**
   * Gets current value from editable element.
   *
   * @returns {number}
   */ getValue() {
        return this.TEXTAREA.value;
    }
    /**
   * Sets new value into editable element.
   *
   * @param {*} newValue The editor value.
   */ setValue(newValue) {
        this.TEXTAREA.value = newValue;
    }
    /**
   * Opens the editor and adjust its size.
   */ open() {
        this._opened = true;
        this.refreshDimensions(); // need it instantly, to prevent https://github.com/handsontable/handsontable/issues/348
        this.showEditableElement();
        this.hot.getShortcutManager().setActiveContextName('editor');
        this.registerShortcuts();
    }
    /**
   * Closes the editor.
   */ close() {
        this._opened = false;
        this.autoResize.unObserve();
        if ((0, _element.isInternalElement)(this.hot.rootDocument.activeElement, this.hot.rootElement)) {
            this.hot.listen(); // don't refocus the table if user focused some cell outside of HT on purpose
        }
        this.hideEditableElement();
        this.unregisterShortcuts();
    }
    /**
   * Prepares editor's meta data.
   *
   * @param {number} row The visual row index.
   * @param {number} col The visual column index.
   * @param {number|string} prop The column property (passed when datasource is an array of objects).
   * @param {HTMLTableCellElement} td The rendered cell element.
   * @param {*} value The rendered value.
   * @param {object} cellProperties The cell meta object (see {@link Core#getCellMeta}).
   */ prepare(row, col, prop, td, value, cellProperties) {
        const previousState = this.state;
        super.prepare(row, col, prop, td, value, cellProperties);
        if (!cellProperties.readOnly) {
            this.refreshDimensions(true);
            const { allowInvalid } = cellProperties;
            if (allowInvalid && !this.isOpened()) {
                // Remove an empty space from textarea (added by copyPaste plugin to make copy/paste
                // functionality work with IME)
                this.TEXTAREA.value = '';
            }
            if (previousState !== _baseEditor.EDITOR_STATE.FINISHED && !this.isOpened()) {
                this.hideEditableElement();
            }
        }
    }
    /**
   * Begins editing on a highlighted cell and hides fillHandle corner if was present.
   *
   * @param {*} newInitialValue The editor initial value.
   * @param {Event} event The keyboard event object.
   */ beginEditing(newInitialValue, event) {
        if (this.state !== _baseEditor.EDITOR_STATE.VIRGIN) {
            return;
        }
        this.TEXTAREA.value = ''; // Remove an empty space from textarea (added by copyPaste plugin to make copy/paste functionality work with IME).
        super.beginEditing(newInitialValue, event);
    }
    /**
   * Sets focus state on the select element.
   */ focus() {
        // For IME editor textarea element must be focused using ".select" method.
        // Using ".focus" browser automatically scroll into the focused element which
        // is undesired effect.
        this.TEXTAREA.select();
        (0, _element.setCaretPosition)(this.TEXTAREA, this.TEXTAREA.value.length);
    }
    /**
   * Creates an editor's elements and adds necessary CSS classnames.
   *
   * @param {string} type The type of the element to create.
   */ createElements(type = 'textarea') {
        const { rootDocument } = this.hot;
        this.TEXTAREA = rootDocument.createElement(type);
        // Makes the element recognizable by Hot as its own
        // component's element.
        (0, _element.setAttribute)(this.TEXTAREA, [
            [
                'data-hot-input',
                ''
            ],
            (0, _a11y.A11Y_TABINDEX)(-1)
        ]);
        (0, _element.addClass)(this.TEXTAREA, 'handsontableInput');
        this.textareaStyle = this.TEXTAREA.style;
        this.textareaStyle.width = 0;
        this.textareaStyle.height = 0;
        this.textareaStyle.overflowY = 'visible';
        this.TEXTAREA_PARENT = rootDocument.createElement('DIV');
        (0, _element.addClass)(this.TEXTAREA_PARENT, 'handsontableInputHolder');
        if ((0, _element.hasClass)(this.TEXTAREA_PARENT, this.layerClass)) {
            (0, _element.removeClass)(this.TEXTAREA_PARENT, this.layerClass);
        }
        (0, _element.addClass)(this.TEXTAREA_PARENT, EDITOR_HIDDEN_CLASS_NAME);
        this.textareaParentStyle = this.TEXTAREA_PARENT.style;
        this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
        this.hot.rootElement.appendChild(this.TEXTAREA_PARENT);
    }
    /**
   * Moves an editable element out of the viewport, but element must be able to hold focus for IME support.
   *
   * @private
   */ hideEditableElement() {
        if ((0, _browser.isEdge)()) {
            this.textareaStyle.textIndent = '-99999px';
        }
        this.textareaStyle.overflowY = 'visible';
        this.textareaParentStyle.opacity = '0';
        this.textareaParentStyle.height = '1px';
        (0, _element.removeClass)(this.TEXTAREA_PARENT, this.layerClass);
        (0, _element.addClass)(this.TEXTAREA_PARENT, EDITOR_HIDDEN_CLASS_NAME);
    }
    /**
   * Resets an editable element position.
   *
   * @private
   */ showEditableElement() {
        this.textareaParentStyle.height = '';
        this.textareaParentStyle.overflow = '';
        this.textareaParentStyle.position = '';
        this.textareaParentStyle[this.hot.isRtl() ? 'left' : 'right'] = 'auto';
        this.textareaParentStyle.opacity = '1';
        this.textareaStyle.textIndent = '';
        const childNodes = this.TEXTAREA_PARENT.childNodes;
        let hasClassHandsontableEditor = false;
        (0, _number.rangeEach)(childNodes.length - 1, (index)=>{
            const childNode = childNodes[index];
            if ((0, _element.hasClass)(childNode, 'handsontableEditor')) {
                hasClassHandsontableEditor = true;
                return false;
            }
        });
        if ((0, _element.hasClass)(this.TEXTAREA_PARENT, EDITOR_HIDDEN_CLASS_NAME)) {
            (0, _element.removeClass)(this.TEXTAREA_PARENT, EDITOR_HIDDEN_CLASS_NAME);
        }
        if (hasClassHandsontableEditor) {
            this.layerClass = EDITOR_VISIBLE_CLASS_NAME;
            (0, _element.addClass)(this.TEXTAREA_PARENT, this.layerClass);
        } else {
            this.layerClass = this.getEditedCellsLayerClass();
            (0, _element.addClass)(this.TEXTAREA_PARENT, this.layerClass);
        }
    }
    /**
   * Refreshes editor's value using source data.
   *
   * @private
   */ refreshValue() {
        const physicalRow = this.hot.toPhysicalRow(this.row);
        const sourceData = this.hot.getSourceDataAtCell(physicalRow, this.col);
        this.originalValue = sourceData;
        this.setValue(sourceData);
        this.refreshDimensions();
    }
    /**
   * Refreshes editor's size and position.
   *
   * @private
   * @param {boolean} force Indicates if the refreshing editor dimensions should be triggered.
   */ refreshDimensions(force = false) {
        if (this.state !== _baseEditor.EDITOR_STATE.EDITING && !force) {
            return;
        }
        this.TD = this.getEditedCell();
        // TD is outside of the viewport.
        if (!this.TD) {
            if (!force) {
                this.close(); // TODO shouldn't it be this.finishEditing() ?
            }
            return;
        }
        const { top, start, width, maxWidth, height, maxHeight } = this.getEditedCellRect();
        this.textareaParentStyle.top = `${top}px`;
        this.textareaParentStyle[this.hot.isRtl() ? 'right' : 'left'] = `${start}px`;
        this.showEditableElement();
        const cellComputedStyle = this.hot.rootWindow.getComputedStyle(this.TD);
        this.TEXTAREA.style.fontSize = cellComputedStyle.fontSize;
        this.TEXTAREA.style.fontFamily = cellComputedStyle.fontFamily;
        this.TEXTAREA.style.backgroundColor = this.TD.style.backgroundColor;
        this.autoResize.init(this.TEXTAREA, {
            minWidth: Math.min(width, maxWidth),
            minHeight: Math.min(height, maxHeight),
            // TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
            maxWidth,
            maxHeight
        }, true);
    }
    /**
   * Binds events and hooks.
   *
   * @private
   */ bindEvents() {
        if ((0, _browser.isIOS)()) {
            // on iOS after click "Done" the edit isn't hidden by default, so we need to handle it manually.
            this.eventManager.addEventListener(this.TEXTAREA, 'focusout', ()=>this.finishEditing(false));
        }
        this.addHook('afterScrollHorizontally', ()=>this.refreshDimensions());
        this.addHook('afterScrollVertically', ()=>this.refreshDimensions());
        this.addHook('afterColumnResize', ()=>{
            this.refreshDimensions();
            if (this.state === _baseEditor.EDITOR_STATE.EDITING) {
                this.focus();
            }
        });
        this.addHook('afterRowResize', ()=>{
            this.refreshDimensions();
            if (this.state === _baseEditor.EDITOR_STATE.EDITING) {
                this.focus();
            }
        });
    }
    /**
   * Destroys the internal event manager and clears attached hooks.
   *
   * @private
   */ destroy() {
        this.eventManager.destroy();
        this.clearHooks();
    }
    /**
   * Register shortcuts responsible for handling editor.
   *
   * @private
   */ registerShortcuts() {
        const shortcutManager = this.hot.getShortcutManager();
        const editorContext = shortcutManager.getContext('editor');
        const contextConfig = {
            runOnlyIf: ()=>(0, _mixed.isDefined)(this.hot.getSelected()),
            group: SHORTCUTS_GROUP
        };
        const insertNewLine = ()=>{
            this.hot.rootDocument.execCommand('insertText', false, '\n');
        };
        editorContext.addShortcuts([
            {
                keys: [
                    [
                        'Control',
                        'Enter'
                    ]
                ],
                callback: ()=>{
                    insertNewLine();
                    return false; // Will block closing editor.
                },
                runOnlyIf: (event)=>!this.hot.selection.isMultiple() && // We trigger a data population for multiple selection.
                    // catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
                    !event.altKey
            },
            {
                keys: [
                    [
                        'Meta',
                        'Enter'
                    ]
                ],
                callback: ()=>{
                    insertNewLine();
                    return false; // Will block closing editor.
                },
                runOnlyIf: ()=>!this.hot.selection.isMultiple()
            },
            {
                keys: [
                    [
                        'Alt',
                        'Enter'
                    ]
                ],
                callback: ()=>{
                    insertNewLine();
                    return false; // Will block closing editor.
                }
            },
            {
                keys: [
                    [
                        'Home'
                    ]
                ],
                callback: (event, [keyName])=>{
                    (0, _caretPositioner.updateCaretPosition)(keyName, this.TEXTAREA);
                }
            },
            {
                keys: [
                    [
                        'End'
                    ]
                ],
                callback: (event, [keyName])=>{
                    (0, _caretPositioner.updateCaretPosition)(keyName, this.TEXTAREA);
                }
            }
        ], contextConfig);
    }
    /**
   * Unregister shortcuts responsible for handling editor.
   *
   * @private
   */ unregisterShortcuts() {
        const shortcutManager = this.hot.getShortcutManager();
        const editorContext = shortcutManager.getContext('editor');
        editorContext.removeShortcutsByGroup(SHORTCUTS_GROUP);
    }
    /**
   * @param {Core} hotInstance The Handsontable instance.
   */ constructor(hotInstance){
        super(hotInstance), /**
   * Instance of {@link EventManager}.
   *
   * @private
   * @type {EventManager}
   */ this.eventManager = new _eventManager.default(this), /**
   * Autoresize instance. Automagically resizes editor after changes.
   *
   * @private
   * @type {Function}
   */ this.autoResize = (0, _autoResize.createInputElementResizer)(this.hot.rootDocument);
        this.eventManager = new _eventManager.default(this);
        this.createElements();
        this.bindEvents();
        this.hot.addHookOnce('afterDestroy', ()=>this.destroy());
    }
}
