import "core-js/modules/es.symbol"; import "core-js/modules/es.symbol.description"; import "core-js/modules/es.symbol.iterator"; import "core-js/modules/es.array.filter"; import "core-js/modules/es.array.find"; import "core-js/modules/es.array.index-of"; import "core-js/modules/es.array.iterator"; import "core-js/modules/es.array.sort"; import "core-js/modules/es.object.get-own-property-descriptor"; import "core-js/modules/es.object.get-prototype-of"; import "core-js/modules/es.object.set-prototype-of"; import "core-js/modules/es.object.to-string"; import "core-js/modules/es.reflect.get"; import "core-js/modules/es.regexp.exec"; import "core-js/modules/es.string.iterator"; import "core-js/modules/es.string.replace"; import "core-js/modules/es.weak-map"; import "core-js/modules/web.dom-collections.iterator"; function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } import { KEY_CODES, isPrintableChar } from './../helpers/unicode'; import { stringify, isDefined } from './../helpers/mixed'; import { stripTags } from './../helpers/string'; import { pivot, arrayMap } from './../helpers/array'; import { addClass, getCaretPosition, getScrollbarWidth, getSelectionEndPosition, outerWidth, outerHeight, offset, getTrimmingContainer, setCaretPosition } from './../helpers/dom/element'; import HandsontableEditor from './handsontableEditor'; var privatePool = new WeakMap(); /** * @private * @editor AutocompleteEditor * @class AutocompleteEditor * @dependencies HandsontableEditor */ var AutocompleteEditor = /*#__PURE__*/ function (_HandsontableEditor) { _inherits(AutocompleteEditor, _HandsontableEditor); function AutocompleteEditor(instance) { var _this; _classCallCheck(this, AutocompleteEditor); _this = _possibleConstructorReturn(this, _getPrototypeOf(AutocompleteEditor).call(this, instance)); /** * Query string to turn available values over. * * @type {String} */ _this.query = null; /** * Contains stripped choices. * * @type {String[]} */ _this.strippedChoices = []; /** * Contains raw choices. * * @type {Array} */ _this.rawChoices = []; privatePool.set(_assertThisInitialized(_this), { skipOne: false }); return _this; } /** * Gets current value from editable element. * * @returns {String} */ _createClass(AutocompleteEditor, [{ key: "getValue", value: function getValue() { var _this2 = this; var selectedValue = this.rawChoices.find(function (value) { var strippedValue = _this2.stripValueIfNeeded(value); return strippedValue === _this2.TEXTAREA.value; }); if (isDefined(selectedValue)) { return selectedValue; } return this.TEXTAREA.value; } /** * Creates an editor's elements and adds necessary CSS classnames. */ }, { key: "createElements", value: function createElements() { _get(_getPrototypeOf(AutocompleteEditor.prototype), "createElements", this).call(this); addClass(this.htContainer, 'autocompleteEditor'); addClass(this.htContainer, this.hot.rootWindow.navigator.platform.indexOf('Mac') === -1 ? '' : 'htMacScroll'); } /** * Opens the editor and adjust its size and internal Handsontable's instance. */ }, { key: "open", value: function open() { var _this3 = this; var priv = privatePool.get(this); // this.addHook('beforeKeyDown', event => this.onBeforeKeyDown(event)); // Ugly fix for handsontable which grab window object for autocomplete scroll listener instead table element. this.TEXTAREA_PARENT.style.overflow = 'auto'; _get(_getPrototypeOf(AutocompleteEditor.prototype), "open", this).call(this); this.TEXTAREA_PARENT.style.overflow = ''; var choicesListHot = this.htEditor.getInstance(); var trimDropdown = this.cellProperties.trimDropdown === void 0 ? true : this.cellProperties.trimDropdown; this.showEditableElement(); this.focus(); var scrollbarWidth = getScrollbarWidth(this.hot.rootDocument); choicesListHot.updateSettings({ colWidths: trimDropdown ? [outerWidth(this.TEXTAREA) - 2] : void 0, width: trimDropdown ? outerWidth(this.TEXTAREA) + scrollbarWidth + 2 : void 0, afterRenderer: function afterRenderer(TD, row, col, prop, value) { var _this3$cellProperties = _this3.cellProperties, filteringCaseSensitive = _this3$cellProperties.filteringCaseSensitive, allowHtml = _this3$cellProperties.allowHtml; var query = _this3.query; var cellValue = stringify(value); var indexOfMatch; var match; if (cellValue && !allowHtml) { indexOfMatch = filteringCaseSensitive === true ? cellValue.indexOf(query) : cellValue.toLowerCase().indexOf(query.toLowerCase()); if (indexOfMatch !== -1) { match = cellValue.substr(indexOfMatch, query.length); cellValue = cellValue.replace(match, "".concat(match, "")); } } TD.innerHTML = cellValue; }, autoColumnSize: true, modifyColWidth: function modifyColWidth(width, col) { // workaround for text overlapping the dropdown, not really accurate var autoColumnSize = this.getPlugin('autoColumnSize'); var columnWidth = width; if (autoColumnSize) { var autoWidths = autoColumnSize.widths; if (autoWidths[col]) { columnWidth = autoWidths[col]; } } return trimDropdown ? columnWidth : columnWidth + 15; } }); // Add additional space for autocomplete holder this.htEditor.view.wt.wtTable.holder.parentNode.style['padding-right'] = "".concat(scrollbarWidth + 2, "px"); if (priv.skipOne) { priv.skipOne = false; } this.hot._registerTimeout(function () { _this3.queryChoices(_this3.TEXTAREA.value); }); } /** * Closes the editor. */ }, { key: "close", value: function close() { this.removeHooksByKey('beforeKeyDown'); _get(_getPrototypeOf(AutocompleteEditor.prototype), "close", this).call(this); } /** * Verifies result of validation or closes editor if user's cancelled changes. Re-renders WalkOnTable. * * @param {Boolean|undefined} result */ }, { key: "discardEditor", value: function discardEditor(result) { _get(_getPrototypeOf(AutocompleteEditor.prototype), "discardEditor", this).call(this, result); this.hot.view.render(); } /** * Prepares choices list based on applied argument. * * @private * @param {String} query */ }, { key: "queryChoices", value: function queryChoices(query) { var _this4 = this; var source = this.cellProperties.source; this.query = query; if (typeof source === 'function') { source.call(this.cellProperties, query, function (choices) { _this4.rawChoices = choices; _this4.updateChoicesList(_this4.stripValuesIfNeeded(choices)); }); } else if (Array.isArray(source)) { this.rawChoices = source; this.updateChoicesList(this.stripValuesIfNeeded(source)); } else { this.updateChoicesList([]); } } /** * Updates list of the possible completions to choose. * * @private * @param {Array} choicesList */ }, { key: "updateChoicesList", value: function updateChoicesList(choicesList) { var pos = getCaretPosition(this.TEXTAREA); var endPos = getSelectionEndPosition(this.TEXTAREA); var sortByRelevanceSetting = this.cellProperties.sortByRelevance; var filterSetting = this.cellProperties.filter; var orderByRelevance = null; var highlightIndex = null; var choices = choicesList; if (sortByRelevanceSetting) { orderByRelevance = AutocompleteEditor.sortByRelevance(this.stripValueIfNeeded(this.getValue()), choices, this.cellProperties.filteringCaseSensitive); } var orderByRelevanceLength = Array.isArray(orderByRelevance) ? orderByRelevance.length : 0; if (filterSetting === false) { if (orderByRelevanceLength) { highlightIndex = orderByRelevance[0]; } } else { var sorted = []; for (var i = 0, choicesCount = choices.length; i < choicesCount; i++) { if (sortByRelevanceSetting && orderByRelevanceLength <= i) { break; } if (orderByRelevanceLength) { sorted.push(choices[orderByRelevance[i]]); } else { sorted.push(choices[i]); } } highlightIndex = 0; choices = sorted; } this.strippedChoices = choices; this.htEditor.loadData(pivot([choices])); this.updateDropdownHeight(); this.flipDropdownIfNeeded(); if (this.cellProperties.strict === true) { this.highlightBestMatchingChoice(highlightIndex); } this.hot.listen(false); setCaretPosition(this.TEXTAREA, pos, pos === endPos ? void 0 : endPos); } /** * Checks where is enough place to open editor. * * @private * @returns {Boolean} */ }, { key: "flipDropdownIfNeeded", value: function flipDropdownIfNeeded() { var textareaOffset = offset(this.TEXTAREA); var textareaHeight = outerHeight(this.TEXTAREA); var dropdownHeight = this.getDropdownHeight(); var trimmingContainer = getTrimmingContainer(this.hot.view.wt.wtTable.TABLE); var trimmingContainerScrollTop = trimmingContainer.scrollTop; var headersHeight = outerHeight(this.hot.view.wt.wtTable.THEAD); var containerOffset = { row: 0, col: 0 }; if (trimmingContainer !== this.hot.rootWindow) { containerOffset = offset(trimmingContainer); } var spaceAbove = textareaOffset.top - containerOffset.top - headersHeight + trimmingContainerScrollTop; var spaceBelow = trimmingContainer.scrollHeight - spaceAbove - headersHeight - textareaHeight; var flipNeeded = dropdownHeight > spaceBelow && spaceAbove > spaceBelow; if (flipNeeded) { this.flipDropdown(dropdownHeight); } else { this.unflipDropdown(); } this.limitDropdownIfNeeded(flipNeeded ? spaceAbove : spaceBelow, dropdownHeight); return flipNeeded; } /** * Checks if the internal table should generate scrollbar or could be rendered without it. * * @private * @param {Number} spaceAvailable * @param {Number} dropdownHeight */ }, { key: "limitDropdownIfNeeded", value: function limitDropdownIfNeeded(spaceAvailable, dropdownHeight) { if (dropdownHeight > spaceAvailable) { var tempHeight = 0; var i = 0; var lastRowHeight = 0; var height = null; do { lastRowHeight = this.htEditor.getRowHeight(i) || this.htEditor.view.wt.wtSettings.settings.defaultRowHeight; tempHeight += lastRowHeight; i += 1; } while (tempHeight < spaceAvailable); height = tempHeight - lastRowHeight; if (this.htEditor.flipped) { this.htEditor.rootElement.style.top = "".concat(parseInt(this.htEditor.rootElement.style.top, 10) + dropdownHeight - height, "px"); } this.setDropdownHeight(tempHeight - lastRowHeight); } } /** * Configures editor to open it at the top. * * @private * @param {Number} dropdownHeight */ }, { key: "flipDropdown", value: function flipDropdown(dropdownHeight) { var dropdownStyle = this.htEditor.rootElement.style; dropdownStyle.position = 'absolute'; dropdownStyle.top = "".concat(-dropdownHeight, "px"); this.htEditor.flipped = true; } /** * Configures editor to open it at the bottom. * * @private */ }, { key: "unflipDropdown", value: function unflipDropdown() { var dropdownStyle = this.htEditor.rootElement.style; if (dropdownStyle.position === 'absolute') { dropdownStyle.position = ''; dropdownStyle.top = ''; } this.htEditor.flipped = void 0; } /** * Updates width and height of the internal Handsontable's instance. * * @private */ }, { key: "updateDropdownHeight", value: function updateDropdownHeight() { var currentDropdownWidth = this.htEditor.getColWidth(0) + getScrollbarWidth(this.hot.rootDocument) + 2; var trimDropdown = this.cellProperties.trimDropdown; this.htEditor.updateSettings({ height: this.getDropdownHeight(), width: trimDropdown ? void 0 : currentDropdownWidth }); this.htEditor.view.wt.wtTable.alignOverlaysWithTrimmingContainer(); } /** * Sets new height of the internal Handsontable's instance. * * @private * @param {Number} height */ }, { key: "setDropdownHeight", value: function setDropdownHeight(height) { this.htEditor.updateSettings({ height: height }); } /** * Creates new selection on specified row index, or deselects selected cells. * * @private * @param {Number|undefined} index */ }, { key: "highlightBestMatchingChoice", value: function highlightBestMatchingChoice(index) { if (typeof index === 'number') { this.htEditor.selectCell(index, 0, void 0, void 0, void 0, false); } else { this.htEditor.deselectCell(); } } /** * Calculates and return the internal Handsontable's height. * * @private * @returns {Number} */ }, { key: "getDropdownHeight", value: function getDropdownHeight() { var firstRowHeight = this.htEditor.getInstance().getRowHeight(0) || 23; var visibleRows = this.cellProperties.visibleRows; return this.strippedChoices.length >= visibleRows ? visibleRows * firstRowHeight : this.strippedChoices.length * firstRowHeight + 8; } /** * Sanitizes value from potential dangerous tags. * * @private * @param {String} value * @returns {String} */ }, { key: "stripValueIfNeeded", value: function stripValueIfNeeded(value) { return this.stripValuesIfNeeded([value])[0]; } /** * Sanitizes an array of the values from potential dangerous tags. * * @private * @param {String[]} values * @returns {String[]} */ }, { key: "stripValuesIfNeeded", value: function stripValuesIfNeeded(values) { var allowHtml = this.cellProperties.allowHtml; var stringifiedValues = arrayMap(values, function (value) { return stringify(value); }); var strippedValues = arrayMap(stringifiedValues, function (value) { return allowHtml ? value : stripTags(value); }); return strippedValues; } /** * Captures use of arrow down and up to control their behaviour. * * @private * @param {Number} keyCode * @returns {Boolean} */ }, { key: "allowKeyEventPropagation", value: function allowKeyEventPropagation(keyCode) { var selectedRange = this.htEditor.getSelectedRangeLast(); var selected = { row: selectedRange ? selectedRange.from.row : -1 }; var allowed = false; if (keyCode === KEY_CODES.ARROW_DOWN && selected.row > 0 && selected.row < this.htEditor.countRows() - 1) { allowed = true; } if (keyCode === KEY_CODES.ARROW_UP && selected.row > -1) { allowed = true; } return allowed; } /** * onBeforeKeyDown callback. * * @private * @param {KeyboardEvent} event */ }, { key: "onBeforeKeyDown", value: function onBeforeKeyDown(event) { var _this5 = this; var priv = privatePool.get(this); priv.skipOne = false; if (isPrintableChar(event.keyCode) || event.keyCode === KEY_CODES.BACKSPACE || event.keyCode === KEY_CODES.DELETE || event.keyCode === KEY_CODES.INSERT) { var timeOffset = 0; // on ctl+c / cmd+c don't update suggestion list if (event.keyCode === KEY_CODES.C && (event.ctrlKey || event.metaKey)) { return; } if (!this.isOpened()) { timeOffset += 10; } if (this.htEditor) { this.hot._registerTimeout(function () { _this5.queryChoices(_this5.TEXTAREA.value); priv.skipOne = true; }, timeOffset); } } _get(_getPrototypeOf(AutocompleteEditor.prototype), "onBeforeKeyDown", this).call(this, event); } }]); return AutocompleteEditor; }(HandsontableEditor); /** * Filters and sorts by relevance. * * @param value * @param choices * @param caseSensitive * @returns {Number[]} array of indexes in original choices array */ AutocompleteEditor.sortByRelevance = function (value, choices, caseSensitive) { var choicesRelevance = []; var currentItem; var valueLength = value.length; var valueIndex; var charsLeft; var result = []; var i; var choicesCount = choices.length; if (valueLength === 0) { for (i = 0; i < choicesCount; i++) { result.push(i); } return result; } for (i = 0; i < choicesCount; i++) { currentItem = stripTags(stringify(choices[i])); if (caseSensitive) { valueIndex = currentItem.indexOf(value); } else { valueIndex = currentItem.toLowerCase().indexOf(value.toLowerCase()); } if (valueIndex !== -1) { charsLeft = currentItem.length - valueIndex - valueLength; choicesRelevance.push({ baseIndex: i, index: valueIndex, charsLeft: charsLeft, value: currentItem }); } } choicesRelevance.sort(function (a, b) { if (b.index === -1) { return -1; } if (a.index === -1) { return 1; } if (a.index < b.index) { return -1; } else if (b.index < a.index) { return 1; } else if (a.index === b.index) { if (a.charsLeft < b.charsLeft) { return -1; } else if (a.charsLeft > b.charsLeft) { return 1; } } return 0; }); for (i = 0, choicesCount = choicesRelevance.length; i < choicesCount; i++) { result.push(choicesRelevance[i].baseIndex); } return result; }; export default AutocompleteEditor;