textEditor.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _element = require('./../helpers/dom/element');
  4. var _autoResize = require('./../../lib/autoResize/autoResize');
  5. var _autoResize2 = _interopRequireDefault(_autoResize);
  6. var _baseEditor = require('./_baseEditor');
  7. var _baseEditor2 = _interopRequireDefault(_baseEditor);
  8. var _eventManager = require('./../eventManager');
  9. var _eventManager2 = _interopRequireDefault(_eventManager);
  10. var _unicode = require('./../helpers/unicode');
  11. var _event = require('./../helpers/dom/event');
  12. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  13. var TextEditor = _baseEditor2.default.prototype.extend();
  14. /**
  15. * @private
  16. * @editor TextEditor
  17. * @class TextEditor
  18. * @dependencies autoResize
  19. */
  20. TextEditor.prototype.init = function () {
  21. var that = this;
  22. this.createElements();
  23. this.eventManager = new _eventManager2.default(this);
  24. this.bindEvents();
  25. this.autoResize = (0, _autoResize2.default)();
  26. this.instance.addHook('afterDestroy', function () {
  27. that.destroy();
  28. });
  29. };
  30. TextEditor.prototype.getValue = function () {
  31. return this.TEXTAREA.value;
  32. };
  33. TextEditor.prototype.setValue = function (newValue) {
  34. this.TEXTAREA.value = newValue;
  35. };
  36. var onBeforeKeyDown = function onBeforeKeyDown(event) {
  37. var instance = this,
  38. that = instance.getActiveEditor(),
  39. ctrlDown;
  40. // catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  41. ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;
  42. // Process only events that have been fired in the editor
  43. if (event.target !== that.TEXTAREA || (0, _event.isImmediatePropagationStopped)(event)) {
  44. return;
  45. }
  46. if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) {
  47. // when CTRL or its equivalent is pressed and cell is edited, don't prepare selectable text in textarea
  48. (0, _event.stopImmediatePropagation)(event);
  49. return;
  50. }
  51. switch (event.keyCode) {
  52. case _unicode.KEY_CODES.ARROW_RIGHT:
  53. if (that.isInFullEditMode()) {
  54. if (!that.isWaiting() && !that.allowKeyEventPropagation || !that.isWaiting() && that.allowKeyEventPropagation && !that.allowKeyEventPropagation(event.keyCode)) {
  55. (0, _event.stopImmediatePropagation)(event);
  56. }
  57. }
  58. break;
  59. case _unicode.KEY_CODES.ARROW_LEFT:
  60. if (that.isInFullEditMode()) {
  61. if (!that.isWaiting() && !that.allowKeyEventPropagation || !that.isWaiting() && that.allowKeyEventPropagation && !that.allowKeyEventPropagation(event.keyCode)) {
  62. (0, _event.stopImmediatePropagation)(event);
  63. }
  64. }
  65. break;
  66. case _unicode.KEY_CODES.ARROW_UP:
  67. case _unicode.KEY_CODES.ARROW_DOWN:
  68. if (that.isInFullEditMode()) {
  69. if (!that.isWaiting() && !that.allowKeyEventPropagation || !that.isWaiting() && that.allowKeyEventPropagation && !that.allowKeyEventPropagation(event.keyCode)) {
  70. (0, _event.stopImmediatePropagation)(event);
  71. }
  72. }
  73. break;
  74. case _unicode.KEY_CODES.ENTER:
  75. var selected = that.instance.getSelected();
  76. var isMultipleSelection = !(selected[0] === selected[2] && selected[1] === selected[3]);
  77. if (ctrlDown && !isMultipleSelection || event.altKey) {
  78. // if ctrl+enter or alt+enter, add new line
  79. if (that.isOpened()) {
  80. var caretPosition = (0, _element.getCaretPosition)(that.TEXTAREA),
  81. value = that.getValue();
  82. var newValue = value.slice(0, caretPosition) + '\n' + value.slice(caretPosition);
  83. that.setValue(newValue);
  84. (0, _element.setCaretPosition)(that.TEXTAREA, caretPosition + 1);
  85. } else {
  86. that.beginEditing(that.originalValue + '\n');
  87. }
  88. (0, _event.stopImmediatePropagation)(event);
  89. }
  90. event.preventDefault(); // don't add newline to field
  91. break;
  92. case _unicode.KEY_CODES.A:
  93. case _unicode.KEY_CODES.X:
  94. case _unicode.KEY_CODES.C:
  95. case _unicode.KEY_CODES.V:
  96. if (ctrlDown) {
  97. (0, _event.stopImmediatePropagation)(event); // CTRL+A, CTRL+C, CTRL+V, CTRL+X should only work locally when cell is edited (not in table context)
  98. }
  99. break;
  100. case _unicode.KEY_CODES.BACKSPACE:
  101. case _unicode.KEY_CODES.DELETE:
  102. case _unicode.KEY_CODES.HOME:
  103. case _unicode.KEY_CODES.END:
  104. (0, _event.stopImmediatePropagation)(event); // backspace, delete, home, end should only work locally when cell is edited (not in table context)
  105. break;
  106. default:
  107. break;
  108. }
  109. if ([_unicode.KEY_CODES.ARROW_UP, _unicode.KEY_CODES.ARROW_RIGHT, _unicode.KEY_CODES.ARROW_DOWN, _unicode.KEY_CODES.ARROW_LEFT].indexOf(event.keyCode) === -1) {
  110. that.autoResize.resize(String.fromCharCode(event.keyCode));
  111. }
  112. };
  113. TextEditor.prototype.open = function () {
  114. this.refreshDimensions(); // need it instantly, to prevent https://github.com/handsontable/handsontable/issues/348
  115. this.instance.addHook('beforeKeyDown', onBeforeKeyDown);
  116. };
  117. TextEditor.prototype.close = function (tdOutside) {
  118. this.textareaParentStyle.display = 'none';
  119. this.autoResize.unObserve();
  120. if (document.activeElement === this.TEXTAREA) {
  121. this.instance.listen(); // don't refocus the table if user focused some cell outside of HT on purpose
  122. }
  123. this.instance.removeHook('beforeKeyDown', onBeforeKeyDown);
  124. };
  125. TextEditor.prototype.focus = function () {
  126. this.TEXTAREA.focus();
  127. (0, _element.setCaretPosition)(this.TEXTAREA, this.TEXTAREA.value.length);
  128. };
  129. TextEditor.prototype.createElements = function () {
  130. // this.$body = $(document.body);
  131. this.TEXTAREA = document.createElement('TEXTAREA');
  132. (0, _element.addClass)(this.TEXTAREA, 'handsontableInput');
  133. this.textareaStyle = this.TEXTAREA.style;
  134. this.textareaStyle.width = 0;
  135. this.textareaStyle.height = 0;
  136. this.TEXTAREA_PARENT = document.createElement('DIV');
  137. (0, _element.addClass)(this.TEXTAREA_PARENT, 'handsontableInputHolder');
  138. this.textareaParentStyle = this.TEXTAREA_PARENT.style;
  139. this.textareaParentStyle.top = 0;
  140. this.textareaParentStyle.left = 0;
  141. this.textareaParentStyle.display = 'none';
  142. this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
  143. this.instance.rootElement.appendChild(this.TEXTAREA_PARENT);
  144. var that = this;
  145. this.instance._registerTimeout(setTimeout(function () {
  146. that.refreshDimensions();
  147. }, 0));
  148. };
  149. TextEditor.prototype.getEditedCell = function () {
  150. var editorSection = this.checkEditorSection(),
  151. editedCell;
  152. switch (editorSection) {
  153. case 'top':
  154. editedCell = this.instance.view.wt.wtOverlays.topOverlay.clone.wtTable.getCell({
  155. row: this.row,
  156. col: this.col
  157. });
  158. this.textareaParentStyle.zIndex = 101;
  159. break;
  160. case 'top-left-corner':
  161. editedCell = this.instance.view.wt.wtOverlays.topLeftCornerOverlay.clone.wtTable.getCell({
  162. row: this.row,
  163. col: this.col
  164. });
  165. this.textareaParentStyle.zIndex = 103;
  166. break;
  167. case 'bottom-left-corner':
  168. editedCell = this.instance.view.wt.wtOverlays.bottomLeftCornerOverlay.clone.wtTable.getCell({
  169. row: this.row,
  170. col: this.col
  171. });
  172. this.textareaParentStyle.zIndex = 103;
  173. break;
  174. case 'left':
  175. editedCell = this.instance.view.wt.wtOverlays.leftOverlay.clone.wtTable.getCell({
  176. row: this.row,
  177. col: this.col
  178. });
  179. this.textareaParentStyle.zIndex = 102;
  180. break;
  181. case 'bottom':
  182. editedCell = this.instance.view.wt.wtOverlays.bottomOverlay.clone.wtTable.getCell({
  183. row: this.row,
  184. col: this.col
  185. });
  186. this.textareaParentStyle.zIndex = 102;
  187. break;
  188. default:
  189. editedCell = this.instance.getCell(this.row, this.col);
  190. this.textareaParentStyle.zIndex = '';
  191. break;
  192. }
  193. return editedCell != -1 && editedCell != -2 ? editedCell : void 0;
  194. };
  195. TextEditor.prototype.refreshValue = function () {
  196. var sourceData = this.instance.getSourceDataAtCell(this.row, this.prop);
  197. this.originalValue = sourceData;
  198. this.setValue(sourceData);
  199. this.refreshDimensions();
  200. };
  201. TextEditor.prototype.refreshDimensions = function () {
  202. if (this.state !== _baseEditor.EditorState.EDITING) {
  203. return;
  204. }
  205. this.TD = this.getEditedCell();
  206. // TD is outside of the viewport.
  207. if (!this.TD) {
  208. this.close(true);
  209. return;
  210. }
  211. var currentOffset = (0, _element.offset)(this.TD),
  212. containerOffset = (0, _element.offset)(this.instance.rootElement),
  213. scrollableContainer = (0, _element.getScrollableElement)(this.TD),
  214. totalRowsCount = this.instance.countRows(),
  215. // If colHeaders is disabled, cells in the first row have border-top
  216. editTopModifier = currentOffset.top === containerOffset.top ? 0 : 1,
  217. editTop = currentOffset.top - containerOffset.top - editTopModifier - (scrollableContainer.scrollTop || 0),
  218. editLeft = currentOffset.left - containerOffset.left - 1 - (scrollableContainer.scrollLeft || 0),
  219. settings = this.instance.getSettings(),
  220. rowHeadersCount = this.instance.hasRowHeaders(),
  221. colHeadersCount = this.instance.hasColHeaders(),
  222. editorSection = this.checkEditorSection(),
  223. backgroundColor = this.TD.style.backgroundColor,
  224. cssTransformOffset;
  225. // TODO: Refactor this to the new instance.getCell method (from #ply-59), after 0.12.1 is released
  226. switch (editorSection) {
  227. case 'top':
  228. cssTransformOffset = (0, _element.getCssTransform)(this.instance.view.wt.wtOverlays.topOverlay.clone.wtTable.holder.parentNode);
  229. break;
  230. case 'left':
  231. cssTransformOffset = (0, _element.getCssTransform)(this.instance.view.wt.wtOverlays.leftOverlay.clone.wtTable.holder.parentNode);
  232. break;
  233. case 'top-left-corner':
  234. cssTransformOffset = (0, _element.getCssTransform)(this.instance.view.wt.wtOverlays.topLeftCornerOverlay.clone.wtTable.holder.parentNode);
  235. break;
  236. case 'bottom-left-corner':
  237. cssTransformOffset = (0, _element.getCssTransform)(this.instance.view.wt.wtOverlays.bottomLeftCornerOverlay.clone.wtTable.holder.parentNode);
  238. break;
  239. case 'bottom':
  240. cssTransformOffset = (0, _element.getCssTransform)(this.instance.view.wt.wtOverlays.bottomOverlay.clone.wtTable.holder.parentNode);
  241. break;
  242. default:
  243. break;
  244. }
  245. if (colHeadersCount && this.instance.getSelected()[0] === 0 || settings.fixedRowsBottom && this.instance.getSelected()[0] === totalRowsCount - settings.fixedRowsBottom) {
  246. editTop += 1;
  247. }
  248. if (this.instance.getSelected()[1] === 0) {
  249. editLeft += 1;
  250. }
  251. if (cssTransformOffset && cssTransformOffset != -1) {
  252. this.textareaParentStyle[cssTransformOffset[0]] = cssTransformOffset[1];
  253. } else {
  254. (0, _element.resetCssTransform)(this.TEXTAREA_PARENT);
  255. }
  256. this.textareaParentStyle.top = editTop + 'px';
  257. this.textareaParentStyle.left = editLeft + 'px';
  258. var firstRowOffset = this.instance.view.wt.wtViewport.rowsRenderCalculator.startPosition;
  259. var firstColumnOffset = this.instance.view.wt.wtViewport.columnsRenderCalculator.startPosition;
  260. var horizontalScrollPosition = this.instance.view.wt.wtOverlays.leftOverlay.getScrollPosition();
  261. var verticalScrollPosition = this.instance.view.wt.wtOverlays.topOverlay.getScrollPosition();
  262. var scrollbarWidth = (0, _element.getScrollbarWidth)();
  263. var cellTopOffset = this.TD.offsetTop + firstRowOffset - verticalScrollPosition;
  264. var cellLeftOffset = this.TD.offsetLeft + firstColumnOffset - horizontalScrollPosition;
  265. var width = (0, _element.innerWidth)(this.TD) - 8;
  266. var actualVerticalScrollbarWidth = (0, _element.hasVerticalScrollbar)(scrollableContainer) ? scrollbarWidth : 0;
  267. var actualHorizontalScrollbarWidth = (0, _element.hasHorizontalScrollbar)(scrollableContainer) ? scrollbarWidth : 0;
  268. var maxWidth = this.instance.view.maximumVisibleElementWidth(cellLeftOffset) - 9 - actualVerticalScrollbarWidth;
  269. var height = this.TD.scrollHeight + 1;
  270. var maxHeight = Math.max(this.instance.view.maximumVisibleElementHeight(cellTopOffset) - actualHorizontalScrollbarWidth, 23);
  271. var cellComputedStyle = (0, _element.getComputedStyle)(this.TD);
  272. this.TEXTAREA.style.fontSize = cellComputedStyle.fontSize;
  273. this.TEXTAREA.style.fontFamily = cellComputedStyle.fontFamily;
  274. this.TEXTAREA.style.backgroundColor = ''; // RESET STYLE
  275. this.TEXTAREA.style.backgroundColor = backgroundColor ? backgroundColor : (0, _element.getComputedStyle)(this.TEXTAREA).backgroundColor;
  276. this.autoResize.init(this.TEXTAREA, {
  277. minHeight: Math.min(height, maxHeight),
  278. maxHeight: maxHeight, // TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
  279. minWidth: Math.min(width, maxWidth),
  280. maxWidth: maxWidth // TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar)
  281. }, true);
  282. this.textareaParentStyle.display = 'block';
  283. };
  284. TextEditor.prototype.bindEvents = function () {
  285. var editor = this;
  286. this.eventManager.addEventListener(this.TEXTAREA, 'cut', function (event) {
  287. (0, _event.stopPropagation)(event);
  288. });
  289. this.eventManager.addEventListener(this.TEXTAREA, 'paste', function (event) {
  290. (0, _event.stopPropagation)(event);
  291. });
  292. this.instance.addHook('afterScrollHorizontally', function () {
  293. editor.refreshDimensions();
  294. });
  295. this.instance.addHook('afterScrollVertically', function () {
  296. editor.refreshDimensions();
  297. });
  298. this.instance.addHook('afterColumnResize', function () {
  299. editor.refreshDimensions();
  300. editor.focus();
  301. });
  302. this.instance.addHook('afterRowResize', function () {
  303. editor.refreshDimensions();
  304. editor.focus();
  305. });
  306. this.instance.addHook('afterDestroy', function () {
  307. editor.eventManager.destroy();
  308. });
  309. };
  310. TextEditor.prototype.destroy = function () {
  311. this.eventManager.destroy();
  312. };
  313. exports.default = TextEditor;