handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
337 lines (324 loc) • 12.4 kB
JavaScript
import "core-js/modules/es.error.cause.js";
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
import moment from 'moment';
import Pikaday from '@handsontable/pikaday';
import { EDITOR_STATE } from "../baseEditor/index.mjs";
import { TextEditor } from "../textEditor/index.mjs";
import { addClass, hasClass, outerHeight, outerWidth } from "../../helpers/dom/element.mjs";
import { deepExtend } from "../../helpers/object.mjs";
import { isFunctionKey } from "../../helpers/unicode.mjs";
import { isMobileBrowser } from "../../helpers/browser.mjs";
export const EDITOR_TYPE = 'date';
const SHORTCUTS_GROUP_EDITOR = 'dateEditor';
const DEFAULT_DATE_FORMAT = 'DD/MM/YYYY';
/**
* @private
* @class DateEditor
*/
var _DateEditor_brand = /*#__PURE__*/new WeakSet();
export class DateEditor extends TextEditor {
constructor() {
super(...arguments);
/**
* Gets the current date format for this cell.
*
* @returns {string}
*/
_classPrivateMethodInitSpec(this, _DateEditor_brand);
/**
* @type {boolean}
*/
_defineProperty(this, "parentDestroyed", false);
/**
* @type {Pikaday}
*/
_defineProperty(this, "$datePicker", null);
}
static get EDITOR_TYPE() {
return EDITOR_TYPE;
}
init() {
if (typeof moment !== 'function') {
throw new Error('You need to include moment.js to your project.');
}
if (typeof Pikaday !== 'function') {
throw new Error('You need to include Pikaday to your project.');
}
super.init();
this.hot.addHook('afterDestroy', () => {
this.parentDestroyed = true;
this.destroyElements();
});
this.hot.addHook('afterSetTheme', (themeName, firstRun) => {
if (!firstRun) {
this.close();
}
});
}
/**
* Create data picker instance.
*/
createElements() {
super.createElements();
this.datePicker = this.hot.rootDocument.createElement('DIV');
this.datePickerStyle = this.datePicker.style;
this.datePickerStyle.position = 'absolute';
this.datePickerStyle.top = 0;
this.datePickerStyle.left = 0;
this.datePickerStyle.zIndex = 9999;
this.datePicker.setAttribute('dir', this.hot.isRtl() ? 'rtl' : 'ltr');
addClass(this.datePicker, 'htDatepickerHolder');
this.hot.rootPortalElement.appendChild(this.datePicker);
/**
* Prevent recognizing clicking on datepicker as clicking outside of table.
*/
this.eventManager.addEventListener(this.datePicker, 'mousedown', event => {
if (hasClass(event.target, 'pika-day')) {
this.hideDatepicker();
}
event.stopPropagation();
});
}
/**
* Destroy data picker instance.
*/
destroyElements() {
const datePickerParentElement = this.datePicker.parentNode;
if (this.$datePicker) {
this.$datePicker.destroy();
}
if (datePickerParentElement) {
datePickerParentElement.removeChild(this.datePicker);
}
}
/**
* Prepare editor to appear.
*
* @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) {
super.prepare(row, col, prop, td, value, cellProperties);
}
/**
* Open editor.
*
* @param {Event} [event=null] The event object.
*/
open() {
let event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
const shortcutManager = this.hot.getShortcutManager();
const editorContext = shortcutManager.getContext('editor');
this.showDatepicker(event);
super.open();
editorContext.addShortcuts([{
keys: [['ArrowLeft']],
callback: () => {
this.$datePicker.adjustDate('subtract', 1);
}
}, {
keys: [['ArrowRight']],
callback: () => {
this.$datePicker.adjustDate('add', 1);
}
}, {
keys: [['ArrowUp']],
callback: () => {
this.$datePicker.adjustDate('subtract', 7);
}
}, {
keys: [['ArrowDown']],
callback: () => {
this.$datePicker.adjustDate('add', 7);
}
}], {
group: SHORTCUTS_GROUP_EDITOR
});
}
/**
* Close editor.
*/
close() {
var _this$$datePicker;
// If the date picker was never initialized (e.g. during autofill), there's nothing to destroy.
if ((_this$$datePicker = this.$datePicker) !== null && _this$$datePicker !== void 0 && _this$$datePicker.destroy) {
this.$datePicker.destroy();
}
this.hot._registerTimeout(() => {
const editorManager = this.hot._getEditorManager();
editorManager.closeEditor();
this.hot.view.render();
editorManager.prepareEditor();
});
const shortcutManager = this.hot.getShortcutManager();
const editorContext = shortcutManager.getContext('editor');
editorContext.removeShortcutsByGroup(SHORTCUTS_GROUP_EDITOR);
super.close();
}
/**
* Finishes editing and start saving or restoring process for editing cell or last selected range.
*
* @param {boolean} restoreOriginalValue If true, then closes editor without saving value from the editor into a cell.
* @param {boolean} ctrlDown If true, then saveValue will save editor's value to each cell in the last selected range.
*/
finishEditing() {
let restoreOriginalValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let ctrlDown = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
super.finishEditing(restoreOriginalValue, ctrlDown);
}
/**
* Show data picker.
*
* @param {Event} event The event object.
*/
showDatepicker(event) {
const dateFormat = _assertClassBrand(_DateEditor_brand, this, _getDateFormat).call(this);
const isMouseDown = this.hot.view.isMouseDown();
const isMeta = event ? isFunctionKey(event.keyCode) : false;
let dateStr;
this.datePicker.style.display = 'block';
this.$datePicker = new Pikaday(this.getDatePickerConfig());
if (typeof this.$datePicker.useMoment === 'function') {
this.$datePicker.useMoment(moment);
}
this.$datePicker._onInputFocus = function () {};
if (this.originalValue) {
dateStr = this.originalValue;
if (moment(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment(moment(dateStr, dateFormat), true);
}
// workaround for date/time cells - pikaday resets the cell value to 12:00 AM by default, this will overwrite the value.
if (this.getValue() !== this.originalValue) {
this.setValue(this.originalValue);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else if (this.cellProperties.defaultDate) {
dateStr = this.cellProperties.defaultDate;
if (moment(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment(moment(dateStr, dateFormat), true);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else {
// if a default date is not defined, set a soft-default-date: display the current day and month in the
// datepicker, but don't fill the editor input
this.$datePicker.gotoToday();
}
}
/**
* Hide data picker.
*/
hideDatepicker() {
this.datePickerStyle.display = 'none';
this.$datePicker.hide();
}
/**
* Get date picker options.
*
* @returns {object}
*/
getDatePickerConfig() {
var _options$format;
const htInput = this.TEXTAREA;
const options = {};
if (this.cellProperties && this.cellProperties.datePickerConfig) {
deepExtend(options, this.cellProperties.datePickerConfig);
}
const origOnSelect = options.onSelect;
const origOnClose = options.onClose;
options.field = htInput;
options.trigger = htInput;
options.container = this.datePicker;
options.bound = false;
options.keyboardInput = false;
options.format = (_options$format = options.format) !== null && _options$format !== void 0 ? _options$format : _assertClassBrand(_DateEditor_brand, this, _getDateFormat).call(this);
options.reposition = options.reposition || false;
// Set the RTL to `false`. Due to the https://github.com/Pikaday/Pikaday/issues/647 bug, the layout direction
// of the date picker is controlled by juggling the "dir" attribute of the root date picker element.
// See line @64 of this file.
options.isRTL = false;
options.onSelect = value => {
let dateStr = value;
if (!isNaN(dateStr.getTime())) {
dateStr = moment(dateStr).format(_assertClassBrand(_DateEditor_brand, this, _getDateFormat).call(this));
}
this.setValue(dateStr);
if (origOnSelect) {
origOnSelect();
}
if (isMobileBrowser()) {
this.hideDatepicker();
}
};
options.onClose = () => {
if (!this.parentDestroyed) {
this.finishEditing(false);
}
if (origOnClose) {
origOnClose();
}
};
return options;
}
/**
* Refreshes datepicker's size and position. The method is called internally by Handsontable.
*
* @private
* @param {boolean} force Indicates if the refreshing editor dimensions should be triggered.
*/
refreshDimensions(force) {
var _wtOverlays$getParent;
super.refreshDimensions(force);
if (this.state !== EDITOR_STATE.EDITING) {
return;
}
this.TD = this.getEditedCell();
if (!this.TD) {
this.hideDatepicker();
return;
}
const {
rowIndexMapper,
columnIndexMapper
} = this.hot;
const {
wtOverlays
} = this.hot.view._wt;
const {
wtTable
} = (_wtOverlays$getParent = wtOverlays.getParentOverlay(this.TD)) !== null && _wtOverlays$getParent !== void 0 ? _wtOverlays$getParent : this.hot.view._wt;
const firstVisibleRow = rowIndexMapper.getVisualFromRenderableIndex(wtTable.getFirstPartiallyVisibleRow());
const lastVisibleRow = rowIndexMapper.getVisualFromRenderableIndex(wtTable.getLastPartiallyVisibleRow());
const firstVisibleColumn = columnIndexMapper.getVisualFromRenderableIndex(wtTable.getFirstPartiallyVisibleColumn());
const lastVisibleColumn = columnIndexMapper.getVisualFromRenderableIndex(wtTable.getLastPartiallyVisibleColumn());
if (this.row >= firstVisibleRow && this.row <= lastVisibleRow && this.col >= firstVisibleColumn && this.col <= lastVisibleColumn) {
const offset = this.TD.getBoundingClientRect();
this.datePickerStyle.top = `${this.hot.rootWindow.pageYOffset + offset.top + outerHeight(this.TD)}px`;
let pickerLeftPosition = this.hot.rootWindow.pageXOffset;
if (this.hot.isRtl()) {
pickerLeftPosition += offset.right - outerWidth(this.datePicker);
} else {
pickerLeftPosition += offset.left;
}
this.datePickerStyle.left = `${pickerLeftPosition}px`;
} else {
this.hideDatepicker();
}
}
}
function _getDateFormat() {
var _this$cellProperties$;
return (_this$cellProperties$ = this.cellProperties.dateFormat) !== null && _this$cellProperties$ !== void 0 ? _this$cellProperties$ : DEFAULT_DATE_FORMAT;
}