From dfffc0f1b80de99cf95a9d4a6ad94757abd26a5a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 11 Mar 2022 11:01:35 +0200 Subject: [PATCH] Squashed commit of the following: commit ddda27385d8745aed19fce1379b2544564e7fc96 Author: ShaMan123 Date: Fri Mar 11 10:54:28 2022 +0200 fix selection cases commit fdba87ffccdb74975d0df71cd618701b49f396e2 Author: ShaMan123 Date: Fri Mar 11 10:54:14 2022 +0200 rename commit 0551fe07b45e2eb8c0298c8a5cd7d2ebe2640549 Author: ShaMan123 Date: Fri Mar 11 10:28:49 2022 +0200 forward/backward commit befdcd859452b322bf73b9a1de8371c9774c3512 Author: ShaMan123 Date: Fri Mar 11 10:00:48 2022 +0200 rtl support prep commit 36bbcb1118a5ed1239c9180714fe7cdaa3d3762a Author: ShaMan123 Date: Fri Mar 11 09:20:32 2022 +0200 Update itext.class.js commit c021097db4d9a5ba64b716d6f3854fdc0a7af3b7 Author: ShaMan123 Date: Fri Mar 11 09:03:07 2022 +0200 feat(IText): selectionDirection, selectionDirection commit 4f9529e2e73a3544cc42523c1daa18f87696c2a4 Author: amir hossein Date: Wed Feb 16 09:46:27 2022 +0330 fix(fabric.Text): consider justify text alignment in RTL text commit fb3054b5475c23bc4ca8f52de3140b378838647b Author: amir hossein Date: Sat Feb 12 13:23:07 2022 +0330 fix(fabric.Text): support RTL different text alignments commit 07f597d49db11bb0b8df70e883cd3d3eca8652ed Author: ShaMan123 Date: Thu Mar 10 20:28:48 2022 +0200 Update text.js commit 28596f5b08c3620deadc5fd3d56d66d1ce68ca68 Author: ShaMan123 Date: Thu Mar 10 20:17:45 2022 +0200 fix(tests) commit 681c0198fa4861c24b44cf21768f7ecf3b328ade Author: ShaMan123 Date: Thu Mar 10 20:05:57 2022 +0200 pathSide commit 1a3f138f6bb11e7d9bd335763b708490d77ec183 Author: ShaMan123 Date: Thu Mar 10 19:40:40 2022 +0200 lint + rename commit 3b80c7bdfd78ae58ca387e370fc675441c8af9e4 Author: ShaMan123 Date: Thu Mar 10 19:26:21 2022 +0200 cleanup commit 76f94b4ec678105609a8fd53c6b85ad7e6d8de23 Author: ShaMan123 Date: Thu Mar 10 18:56:00 2022 +0200 feat(Text): textAlign `start`, `end` and a bit of tidying up commit 2e608dcdde612cbc63f50354a7ea9df7a9dcfd6c Author: Shachar <34343793+ShaMan123@users.noreply.github.com> Date: Wed Mar 9 09:37:43 2022 +0200 feat(): dataURL export - filter objects (#7788) * feat(): filter options * visual test commit 1ea246502f12775a0de0db13115c35d93baa71e7 Author: Amirhossein Mehrabi Date: Sat Feb 26 22:36:46 2022 +0330 fix(fabric.Text): add the previous code as comment --- src/mixins/itext_behavior.mixin.js | 47 ++++++----- src/mixins/itext_click_behavior.mixin.js | 2 +- src/mixins/itext_key_behavior.mixin.js | 100 ++++++++++++----------- src/shapes/itext.class.js | 48 +++++++++++ 4 files changed, 131 insertions(+), 66 deletions(-) diff --git a/src/mixins/itext_behavior.mixin.js b/src/mixins/itext_behavior.mixin.js index f50d1f67246..f04bd218b1e 100644 --- a/src/mixins/itext_behavior.mixin.js +++ b/src/mixins/itext_behavior.mixin.js @@ -205,7 +205,7 @@ * @param {Number} startFrom Current selection index * @return {Number} New selection index */ - findWordBoundaryLeft: function(startFrom) { + findWordBoundaryStart: function(startFrom) { var offset = 0, index = startFrom - 1; // remove space before cursor first @@ -220,7 +220,7 @@ index--; } - return startFrom - offset; + return Math.max(startFrom - offset, 0); }, /** @@ -228,7 +228,7 @@ * @param {Number} startFrom Current selection index * @return {Number} New selection index */ - findWordBoundaryRight: function(startFrom) { + findWordBoundaryEnd: function(startFrom) { var offset = 0, index = startFrom; // remove space after cursor first @@ -263,7 +263,7 @@ * @param {Number} startFrom Current selection index * @return {Number} New selection index */ - findLineBoundaryLeft: function(startFrom) { + findLineBoundaryStart: function(startFrom) { var offset = 0, index = startFrom - 1; while (!/\n/.test(this._text[index]) && index > -1) { @@ -271,7 +271,7 @@ index--; } - return startFrom - offset; + return Math.max(startFrom - offset, 0); }, /** @@ -279,7 +279,7 @@ * @param {Number} startFrom Current selection index * @return {Number} New selection index */ - findLineBoundaryRight: function(startFrom) { + findLineBoundaryEnd: function(startFrom) { var offset = 0, index = startFrom; while (!/\n/.test(this._text[index]) && index < this._text.length) { @@ -337,8 +337,8 @@ */ selectLine: function(selectionStart) { selectionStart = selectionStart || this.selectionStart; - var newSelectionStart = this.findLineBoundaryLeft(selectionStart), - newSelectionEnd = this.findLineBoundaryRight(selectionStart); + var newSelectionStart = this.findLineBoundaryStart(selectionStart), + newSelectionEnd = this.findLineBoundaryEnd(selectionStart); this.selectionStart = newSelectionStart; this.selectionEnd = newSelectionEnd; @@ -414,19 +414,19 @@ currentStart = this.selectionStart, currentEnd = this.selectionEnd; if ( - (newSelectionStart !== this.__selectionStartOnMouseDown || currentStart === currentEnd) + (newSelectionStart !== this.__selectionStartOrigin || currentStart === currentEnd) && (currentStart === newSelectionStart || currentEnd === newSelectionStart) ) { return; } - if (newSelectionStart > this.__selectionStartOnMouseDown) { - this.selectionStart = this.__selectionStartOnMouseDown; + if (newSelectionStart > this.__selectionStartOrigin) { + this.selectionStart = this.__selectionStartOrigin; this.selectionEnd = newSelectionStart; } else { this.selectionStart = newSelectionStart; - this.selectionEnd = this.__selectionStartOnMouseDown; + this.selectionEnd = this.__selectionStartOrigin; } if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) { this.restartCursorIfNeeded(); @@ -472,11 +472,15 @@ var smallerTextStart = _text.slice(0, start), graphemeStart = smallerTextStart.join('').length; if (start === end) { - return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; + return { selectionStart: graphemeStart, selectionEnd: graphemeStart, selectionDirection: 'forward' }; } var smallerTextEnd = _text.slice(start, end), graphemeEnd = smallerTextEnd.join('').length; - return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; + return { + selectionStart: graphemeStart, + selectionEnd: graphemeStart + graphemeEnd, + selectionDirection: graphemeStart < this.__selectionStartOrigin ? 'backward' : 'forward' + }; }, /** @@ -489,8 +493,12 @@ } if (!this.inCompositionMode) { var newSelection = this.fromGraphemeToStringSelection(this.selectionStart, this.selectionEnd, this._text); - this.hiddenTextarea.selectionStart = newSelection.selectionStart; - this.hiddenTextarea.selectionEnd = newSelection.selectionEnd; + this.hiddenTextarea.setSelectionRange( + newSelection.selectionStart, + newSelection.selectionEnd, + newSelection.selectionDirection + ); + this.selectionDirection = newSelection.selectionDirection; } this.updateTextareaPosition(); }, @@ -514,6 +522,7 @@ if (!this.inCompositionMode) { this.selectionStart = newSelection.selectionStart; } + this.selectionDirection = newSelection.selectionDirection; this.updateTextareaPosition(); }, @@ -915,14 +924,14 @@ if (end === start) { this._selectionDirection = 'left'; } - else if (this._selectionDirection === 'right') { + else if (this.selectionDirection === 'forward') { this._selectionDirection = 'left'; this.selectionEnd = start; } this.selectionStart = newSelection; } else if (newSelection > start && newSelection < end) { - if (this._selectionDirection === 'right') { + if (this.selectionDirection === 'forward') { this.selectionEnd = newSelection; } else { @@ -934,7 +943,7 @@ if (end === start) { this._selectionDirection = 'right'; } - else if (this._selectionDirection === 'left') { + else if (this.selectionDirection === 'backward') { this._selectionDirection = 'right'; this.selectionStart = end; } diff --git a/src/mixins/itext_click_behavior.mixin.js b/src/mixins/itext_click_behavior.mixin.js index 69b14ed9a13..f43b4069275 100644 --- a/src/mixins/itext_click_behavior.mixin.js +++ b/src/mixins/itext_click_behavior.mixin.js @@ -109,7 +109,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } if (this.isEditing) { - this.__selectionStartOnMouseDown = this.selectionStart; + this.__selectionStartOrigin = this.selectionStart; if (this.selectionStart === this.selectionEnd) { this.abortCursorAnimation(); } diff --git a/src/mixins/itext_key_behavior.mixin.js b/src/mixins/itext_key_behavior.mixin.js index 68d9371794b..1c426eb852f 100644 --- a/src/mixins/itext_key_behavior.mixin.js +++ b/src/mixins/itext_key_behavior.mixin.js @@ -57,11 +57,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot 27: 'exitEditing', 33: 'moveCursorUp', 34: 'moveCursorDown', - 35: 'moveCursorEndDir', - 36: 'moveCursorStartDir', - 37: 'moveCursorStartDir', + 35: 'moveCursorForward', + 36: 'moveCursorBackward', + 37: 'moveCursorBackward', 38: 'moveCursorUp', - 39: 'moveCursorEndDir', + 39: 'moveCursorForward', 40: 'moveCursorDown', }, @@ -70,11 +70,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot 27: 'exitEditing', 33: 'moveCursorUp', 34: 'moveCursorDown', - 35: 'moveCursorStartDir', - 36: 'moveCursorEndDir', - 37: 'moveCursorEndDir', + 35: 'moveCursorBackward', + 36: 'moveCursorForward', + 37: 'moveCursorForward', 38: 'moveCursorUp', - 39: 'moveCursorStartDir', + 39: 'moveCursorBackward', 40: 'moveCursorDown', }, @@ -470,7 +470,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * @param {Number} offset */ moveCursorWithShift: function(offset) { - var newSelection = this._selectionDirection === 'left' + var newSelection = this.selectionDirection === 'backward' ? this.selectionStart + offset : this.selectionEnd + offset; this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection); @@ -494,64 +494,75 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot }, /** - * Moves cursor left + * Moves cursor back * @param {Event} e Event object */ - moveCursorStartDir: function (e) { + moveCursorBackward: function (e) { if (this.selectionStart === 0 && this.selectionEnd === 0) { return; } var changed = false; if (e.shiftKey) { - if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { - changed = this._moveLeft(e, 'selectionEnd'); + if (this.selectionDirection === 'forward' && this.selectionStart !== this.selectionEnd) { + changed = this._move(e, 'selectionEnd', -1); } else if (this.selectionStart !== 0) { - this._selectionDirection = 'left'; - changed = this._moveLeft(e, 'selectionStart'); + //this._selectionDirection = 'left'; + this.__selectionStartOrigin = this.selectionEnd; + changed = this._move(e, 'selectionStart', -1); } } else { changed = true; - this._selectionDirection = 'left'; + //this._selectionDirection = 'left'; // only move cursor when there is no selection, // otherwise we discard it, and leave cursor on same place - if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) { - changed = this._moveLeft(e, 'selectionStart'); + if (this.selectionEnd === this.selectionStart) { + changed = this.selectionStart !== 0 && this._move(e, 'selectionStart', -1); + this.selectionEnd = this.selectionStart; + } + else if (this.selectionDirection === 'forward') { + this.selectionStart = this.selectionEnd; + } + else { + this.selectionEnd = this.selectionStart; } - this.selectionEnd = this.selectionStart; } this._invalidateCursor(changed); }, /** - * Moves cursor right + * Moves cursor forward * @param {Event} e Event object */ - moveCursorEndDir: function (e) { + moveCursorForward: function (e) { if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { return; } var changed = false; if (e.shiftKey) { - if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { - changed = this._moveRight(e, 'selectionStart'); + if (this.selectionDirection === 'backward' && this.selectionStart !== this.selectionEnd) { + changed = this._move(e, 'selectionStart', 1); } else if (this.selectionEnd !== this._text.length) { - this._selectionDirection = 'right'; - changed = this._moveRight(e, 'selectionEnd'); + //this._selectionDirection = 'right'; + this.__selectionStartOrigin = this.selectionStart; + changed = this._move(e, 'selectionEnd', 1); } } else { changed = true; - this._selectionDirection = 'right'; + //this._selectionDirection = 'right'; if (this.selectionStart === this.selectionEnd) { - changed = this._moveRight(e, 'selectionStart'); + changed = this._move(e, 'selectionStart', 1); this.selectionEnd = this.selectionStart; } - else { + else if (this.selectionDirection === 'forward') { this.selectionStart = this.selectionEnd; } + else { + this.selectionEnd = this.selectionStart; + } } this._invalidateCursor(changed); }, @@ -572,18 +583,29 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot /** * @private - * @return {Boolean} true if a change happened + * @param {Event} e + * @param {'selectionStart'|'selectionEnd'} prop + * @param {number} direction + * @returns {boolean} true if a change happened */ _move: function(e, prop, direction) { var newValue; + direction = Math.sign(direction); + if (direction === 0) { + return false; + } if (e.altKey) { - newValue = this.findWordBoundary(direction, this[prop]); + newValue = direction > 0 ? + this.findWordBoundaryEnd(this[prop]) : + this.findWordBoundaryStart(this[prop]); } else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { - newValue = this.findLineBoundary(direction, this[prop]); + newValue = direction > 0 ? + this.findLineBoundaryEnd(this[prop]) : + this.findLineBoundaryStart(this[prop]); } else { - this[prop] += direction === 'left' ? -1 : 1; + this[prop] = Math.min(Math.max(this[prop] + direction, 0), this.text.length); return true; } if (typeof newValue !== undefined && this[prop] !== newValue) { @@ -592,20 +614,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } }, - /** - * @private - */ - _moveLeft: function(e, prop) { - return this._move(e, prop, 'left'); - }, - - /** - * @private - */ - _moveRight: function(e, prop) { - return this._move(e, prop, 'right'); - }, - /** * Removes characters from start/end * start/end ar per grapheme position in _text array. diff --git a/src/shapes/itext.class.js b/src/shapes/itext.class.js index 351b2416c7e..677c6460f2d 100644 --- a/src/shapes/itext.class.js +++ b/src/shapes/itext.class.js @@ -68,6 +68,15 @@ */ selectionEnd: 0, + /** + * Selection direction relative to initial selection start. + * Same as HTMLTextareaElement#selectionDirection + * @typedef {'forward' | 'backward' | 'none'} SelectionDirection + * @type {SelectionDirection} + * @default + */ + selectionDirection: 'forward', + /** * Color of text selection * @type String @@ -204,6 +213,45 @@ this._updateAndFire('selectionEnd', index); }, + /** + * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange} + * @param {number} selectionStart + * @param {number} selectionEnd + * @param {SelectionDirection} [selectionDirection] + */ + setSelectionRange: function (selectionStart, selectionEnd, selectionDirection) { + this._setSelectionRange(selectionStart, selectionEnd, selectionDirection || 'none'); + }, + + /** + * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange} + * @private + * @param {number} selectionStart + * @param {number} selectionEnd + * @param {SelectionDirection|false} [selectionDirection] pass `false` to preserve current `selectionDirection` value + */ + _setSelectionRange: function (selectionStart, selectionEnd, selectionDirection) { + selectionStart = Math.max(selectionStart, 0); + selectionEnd = Math.min(selectionEnd, this.text.length); + if (selectionStart > selectionEnd) { + // mimic HTMLTextareaElement behavior + selectionStart = selectionEnd; + } + var changed = selectionStart !== this.selectionStart || selectionEnd !== this.selectionEnd; + this.selectionStart = selectionStart; + this.selectionEnd = selectionEnd; + if (selectionDirection !== false) { + // mimic HTMLTextareaElement behavior + this.selectionDirection = selectionDirection === 'backward' ? 'backward' : 'forward'; + // needed for future calcualtions of `selectionDirection` + this.__selectionStartOrigin = this.selectionDirection === 'forward' ? + this.selectionStart : + this.selectionEnd; + } + changed && this._fireSelectionChanged(); + this._updateTextarea(); + }, + /** * @private * @param {String} property 'selectionStart' or 'selectionEnd'