fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
1 lines • 49.6 kB
Source Map (JSON)
{"version":3,"file":"ITextBehavior.min.mjs","sources":["../../../../src/shapes/IText/ITextBehavior.ts"],"sourcesContent":["import type {\n ObjectEvents,\n TPointerEvent,\n TPointerEventInfo,\n} from '../../EventTypeDefs';\nimport { Point } from '../../Point';\nimport type { FabricObject } from '../Object/FabricObject';\nimport { FabricText } from '../Text/Text';\nimport { animate } from '../../util/animation/animate';\nimport type { TOnAnimationChangeCallback } from '../../util/animation/types';\nimport type { ValueAnimation } from '../../util/animation/ValueAnimation';\nimport type { TextStyleDeclaration } from '../Text/StyledText';\nimport type { SerializedTextProps, TextProps } from '../Text/Text';\nimport type { TOptions } from '../../typedefs';\nimport { getDocumentFromElement } from '../../util/dom_misc';\nimport { LEFT, MODIFIED, RIGHT, reNewline } from '../../constants';\nimport type { IText } from './IText';\n\n/**\n * extend this regex to support non english languages\n *\n * - ` ` Matches a SPACE character (char code 32).\n * - `\\n` Matches a LINE FEED character (char code 10).\n * - `\\.` Matches a \".\" character (char code 46).\n * - `,` Matches a \",\" character (char code 44).\n * - `;` Matches a \";\" character (char code 59).\n * - `!` Matches a \"!\" character (char code 33).\n * - `\\?` Matches a \"?\" character (char code 63).\n * - `\\-` Matches a \"-\" character (char code 45).\n */\n// eslint-disable-next-line no-useless-escape\nconst reNonWord = /[ \\n\\.,;!\\?\\-]/;\n\nexport type ITextEvents = ObjectEvents & {\n 'selection:changed': never;\n changed: never | { index: number; action: string };\n tripleclick: TPointerEventInfo;\n 'editing:entered': never | { e: TPointerEvent };\n 'editing:exited': never;\n};\n\nexport abstract class ITextBehavior<\n Props extends TOptions<TextProps> = Partial<TextProps>,\n SProps extends SerializedTextProps = SerializedTextProps,\n EventSpec extends ITextEvents = ITextEvents,\n> extends FabricText<Props, SProps, EventSpec> {\n declare abstract isEditing: boolean;\n declare abstract cursorDelay: number;\n declare abstract selectionStart: number;\n declare abstract selectionEnd: number;\n declare abstract cursorDuration: number;\n declare abstract editable: boolean;\n declare abstract editingBorderColor: string;\n\n declare abstract compositionStart: number;\n declare abstract compositionEnd: number;\n\n declare abstract hiddenTextarea: HTMLTextAreaElement | null;\n\n /**\n * Helps determining when the text is in composition, so that the cursor\n * rendering is altered.\n */\n protected declare inCompositionMode: boolean;\n\n protected declare _reSpace: RegExp;\n private declare _currentTickState?: ValueAnimation;\n private declare _currentTickCompleteState?: ValueAnimation;\n protected _currentCursorOpacity = 1;\n private declare _textBeforeEdit: string;\n protected declare __selectionStartOnMouseDown: number;\n\n protected declare selected: boolean;\n protected declare cursorOffsetCache: { left?: number; top?: number };\n protected declare _savedProps?: {\n hasControls: boolean;\n borderColor: string;\n lockMovementX: boolean;\n lockMovementY: boolean;\n selectable: boolean;\n hoverCursor: CSSStyleDeclaration['cursor'] | null;\n defaultCursor?: CSSStyleDeclaration['cursor'];\n moveCursor?: CSSStyleDeclaration['cursor'];\n };\n protected declare _selectionDirection: 'left' | 'right' | null;\n\n abstract initHiddenTextarea(): void;\n abstract _fireSelectionChanged(): void;\n abstract renderCursorOrSelection(): void;\n abstract getSelectionStartFromPointer(e: TPointerEvent): number;\n abstract _getCursorBoundaries(\n index: number,\n skipCaching?: boolean,\n ): {\n left: number;\n top: number;\n leftOffset: number;\n topOffset: number;\n };\n\n /**\n * Initializes all the interactive behavior of IText\n */\n initBehavior() {\n this._tick = this._tick.bind(this);\n this._onTickComplete = this._onTickComplete.bind(this);\n this.updateSelectionOnMouseMove =\n this.updateSelectionOnMouseMove.bind(this);\n }\n\n onDeselect(options?: { e?: TPointerEvent; object?: FabricObject }) {\n this.isEditing && this.exitEditing();\n this.selected = false;\n return super.onDeselect(options);\n }\n\n /**\n * @private\n */\n _animateCursor({\n toValue,\n duration,\n delay,\n onComplete,\n }: {\n toValue: number;\n duration: number;\n delay?: number;\n onComplete?: TOnAnimationChangeCallback<number, void>;\n }) {\n return animate({\n startValue: this._currentCursorOpacity,\n endValue: toValue,\n duration,\n delay,\n onComplete,\n abort: () =>\n !this.canvas ||\n // we do not want to animate a selection, only cursor\n this.selectionStart !== this.selectionEnd,\n onChange: (value) => {\n this._currentCursorOpacity = value;\n this.renderCursorOrSelection();\n },\n });\n }\n\n /**\n * changes the cursor from visible to invisible\n */\n private _tick(delay?: number) {\n this._currentTickState = this._animateCursor({\n toValue: 0,\n duration: this.cursorDuration / 2,\n delay: Math.max(delay || 0, 100),\n onComplete: this._onTickComplete,\n });\n }\n\n /**\n * Changes the cursor from invisible to visible\n */\n private _onTickComplete() {\n this._currentTickCompleteState?.abort();\n this._currentTickCompleteState = this._animateCursor({\n toValue: 1,\n duration: this.cursorDuration,\n onComplete: this._tick,\n });\n }\n\n /**\n * Initializes delayed cursor\n */\n initDelayedCursor(restart?: boolean) {\n this.abortCursorAnimation();\n this._tick(restart ? 0 : this.cursorDelay);\n }\n\n /**\n * Aborts cursor animation, clears all timeouts and clear textarea context if necessary\n */\n abortCursorAnimation() {\n let shouldClear = false;\n [this._currentTickState, this._currentTickCompleteState].forEach(\n (cursorAnimation) => {\n if (cursorAnimation && !cursorAnimation.isDone()) {\n shouldClear = true;\n cursorAnimation.abort();\n }\n },\n );\n\n this._currentCursorOpacity = 1;\n\n // make sure we clear context even if instance is not editing\n if (shouldClear) {\n this.clearContextTop();\n }\n }\n\n /**\n * Restart tue cursor animation if either is in complete state ( between animations )\n * or if it never started before\n */\n restartCursorIfNeeded() {\n if (\n [this._currentTickState, this._currentTickCompleteState].some(\n (cursorAnimation) => !cursorAnimation || cursorAnimation.isDone(),\n )\n ) {\n this.initDelayedCursor();\n }\n }\n\n /**\n * Selects entire text\n */\n selectAll() {\n this.selectionStart = 0;\n this.selectionEnd = this._text.length;\n this._fireSelectionChanged();\n this._updateTextarea();\n return this;\n }\n\n /**\n * Returns selected text\n * @return {String}\n */\n getSelectedText(): string {\n return this._text.slice(this.selectionStart, this.selectionEnd).join('');\n }\n\n /**\n * Find new selection index representing start of current word according to current selection index\n * @param {Number} startFrom Current selection index\n * @return {Number} New selection index\n */\n findWordBoundaryLeft(startFrom: number): number {\n let offset = 0,\n index = startFrom - 1;\n\n // remove space before cursor first\n if (this._reSpace.test(this._text[index])) {\n while (this._reSpace.test(this._text[index])) {\n offset++;\n index--;\n }\n }\n while (/\\S/.test(this._text[index]) && index > -1) {\n offset++;\n index--;\n }\n\n return startFrom - offset;\n }\n\n /**\n * Find new selection index representing end of current word according to current selection index\n * @param {Number} startFrom Current selection index\n * @return {Number} New selection index\n */\n findWordBoundaryRight(startFrom: number): number {\n let offset = 0,\n index = startFrom;\n\n // remove space after cursor first\n if (this._reSpace.test(this._text[index])) {\n while (this._reSpace.test(this._text[index])) {\n offset++;\n index++;\n }\n }\n while (/\\S/.test(this._text[index]) && index < this._text.length) {\n offset++;\n index++;\n }\n\n return startFrom + offset;\n }\n\n /**\n * Find new selection index representing start of current line according to current selection index\n * @param {Number} startFrom Current selection index\n * @return {Number} New selection index\n */\n findLineBoundaryLeft(startFrom: number): number {\n let offset = 0,\n index = startFrom - 1;\n\n while (!/\\n/.test(this._text[index]) && index > -1) {\n offset++;\n index--;\n }\n\n return startFrom - offset;\n }\n\n /**\n * Find new selection index representing end of current line according to current selection index\n * @param {Number} startFrom Current selection index\n * @return {Number} New selection index\n */\n findLineBoundaryRight(startFrom: number): number {\n let offset = 0,\n index = startFrom;\n\n while (!/\\n/.test(this._text[index]) && index < this._text.length) {\n offset++;\n index++;\n }\n\n return startFrom + offset;\n }\n\n /**\n * Finds index corresponding to beginning or end of a word\n * @param {Number} selectionStart Index of a character\n * @param {Number} direction 1 or -1\n * @return {Number} Index of the beginning or end of a word\n */\n searchWordBoundary(selectionStart: number, direction: 1 | -1): number {\n const text = this._text;\n // if we land on a space we move the cursor backwards\n // if we are searching boundary end we move the cursor backwards ONLY if we don't land on a line break\n let index =\n selectionStart > 0 &&\n this._reSpace.test(text[selectionStart]) &&\n (direction === -1 || !reNewline.test(text[selectionStart - 1]))\n ? selectionStart - 1\n : selectionStart,\n _char = text[index];\n while (index > 0 && index < text.length && !reNonWord.test(_char)) {\n index += direction;\n _char = text[index];\n }\n if (direction === -1 && reNonWord.test(_char)) {\n index++;\n }\n return index;\n }\n\n /**\n * TODO fix: selectionStart set as 0 will be ignored?\n * Selects a word based on the index\n * @param {Number} selectionStart Index of a character\n */\n selectWord(selectionStart?: number) {\n selectionStart = selectionStart || this.selectionStart;\n // search backwards\n const newSelectionStart = this.searchWordBoundary(selectionStart, -1),\n // search forward\n newSelectionEnd = Math.max(\n newSelectionStart,\n this.searchWordBoundary(selectionStart, 1),\n );\n\n this.selectionStart = newSelectionStart;\n this.selectionEnd = newSelectionEnd;\n this._fireSelectionChanged();\n this._updateTextarea();\n this.renderCursorOrSelection();\n }\n\n /**\n * TODO fix: selectionStart set as 0 will be ignored?\n * Selects a line based on the index\n * @param {Number} selectionStart Index of a character\n */\n selectLine(selectionStart?: number) {\n selectionStart = selectionStart || this.selectionStart;\n const newSelectionStart = this.findLineBoundaryLeft(selectionStart),\n newSelectionEnd = this.findLineBoundaryRight(selectionStart);\n\n this.selectionStart = newSelectionStart;\n this.selectionEnd = newSelectionEnd;\n this._fireSelectionChanged();\n this._updateTextarea();\n return this;\n }\n\n /**\n * Enters editing state\n */\n enterEditing(e?: TPointerEvent) {\n if (this.isEditing || !this.editable) {\n return;\n }\n if (this.canvas) {\n this.canvas.calcOffset();\n this.canvas.textEditingManager.exitTextEditing();\n }\n\n this.isEditing = true;\n\n this.initHiddenTextarea();\n this.hiddenTextarea!.focus();\n this.hiddenTextarea!.value = this.text;\n this._updateTextarea();\n this._saveEditingProps();\n this._setEditingProps();\n this._textBeforeEdit = this.text;\n\n this._tick();\n this.fire('editing:entered', e ? { e } : undefined);\n this._fireSelectionChanged();\n if (this.canvas) {\n this.canvas.fire('text:editing:entered', {\n target: this as unknown as IText,\n e,\n });\n this.canvas.requestRenderAll();\n }\n }\n\n /**\n * called by {@link Canvas#textEditingManager}\n */\n updateSelectionOnMouseMove(e: TPointerEvent) {\n if (this.getActiveControl()) {\n return;\n }\n\n const el = this.hiddenTextarea!;\n // regain focus\n getDocumentFromElement(el).activeElement !== el && el.focus();\n\n const newSelectionStart = this.getSelectionStartFromPointer(e),\n currentStart = this.selectionStart,\n currentEnd = this.selectionEnd;\n if (\n (newSelectionStart !== this.__selectionStartOnMouseDown ||\n currentStart === currentEnd) &&\n (currentStart === newSelectionStart || currentEnd === newSelectionStart)\n ) {\n return;\n }\n if (newSelectionStart > this.__selectionStartOnMouseDown) {\n this.selectionStart = this.__selectionStartOnMouseDown;\n this.selectionEnd = newSelectionStart;\n } else {\n this.selectionStart = newSelectionStart;\n this.selectionEnd = this.__selectionStartOnMouseDown;\n }\n if (\n this.selectionStart !== currentStart ||\n this.selectionEnd !== currentEnd\n ) {\n this._fireSelectionChanged();\n this._updateTextarea();\n this.renderCursorOrSelection();\n }\n }\n\n /**\n * @private\n */\n _setEditingProps() {\n this.hoverCursor = 'text';\n\n if (this.canvas) {\n this.canvas.defaultCursor = this.canvas.moveCursor = 'text';\n }\n\n this.borderColor = this.editingBorderColor;\n this.hasControls = this.selectable = false;\n this.lockMovementX = this.lockMovementY = true;\n }\n\n /**\n * convert from textarea to grapheme indexes\n */\n fromStringToGraphemeSelection(start: number, end: number, text: string) {\n const smallerTextStart = text.slice(0, start),\n graphemeStart = this.graphemeSplit(smallerTextStart).length;\n if (start === end) {\n return { selectionStart: graphemeStart, selectionEnd: graphemeStart };\n }\n const smallerTextEnd = text.slice(start, end),\n graphemeEnd = this.graphemeSplit(smallerTextEnd).length;\n return {\n selectionStart: graphemeStart,\n selectionEnd: graphemeStart + graphemeEnd,\n };\n }\n\n /**\n * convert from fabric to textarea values\n */\n fromGraphemeToStringSelection(\n start: number,\n end: number,\n graphemes: string[],\n ) {\n const smallerTextStart = graphemes.slice(0, start),\n graphemeStart = smallerTextStart.join('').length;\n if (start === end) {\n return { selectionStart: graphemeStart, selectionEnd: graphemeStart };\n }\n const smallerTextEnd = graphemes.slice(start, end),\n graphemeEnd = smallerTextEnd.join('').length;\n return {\n selectionStart: graphemeStart,\n selectionEnd: graphemeStart + graphemeEnd,\n };\n }\n\n /**\n * @private\n */\n _updateTextarea() {\n this.cursorOffsetCache = {};\n if (!this.hiddenTextarea) {\n return;\n }\n if (!this.inCompositionMode) {\n const newSelection = this.fromGraphemeToStringSelection(\n this.selectionStart,\n this.selectionEnd,\n this._text,\n );\n this.hiddenTextarea.selectionStart = newSelection.selectionStart;\n this.hiddenTextarea.selectionEnd = newSelection.selectionEnd;\n }\n this.updateTextareaPosition();\n }\n\n /**\n * @private\n */\n updateFromTextArea() {\n if (!this.hiddenTextarea) {\n return;\n }\n this.cursorOffsetCache = {};\n const textarea = this.hiddenTextarea;\n this.text = textarea.value;\n this.set('dirty', true);\n this.initDimensions();\n this.setCoords();\n const newSelection = this.fromStringToGraphemeSelection(\n textarea.selectionStart,\n textarea.selectionEnd,\n textarea.value,\n );\n this.selectionEnd = this.selectionStart = newSelection.selectionEnd;\n if (!this.inCompositionMode) {\n this.selectionStart = newSelection.selectionStart;\n }\n this.updateTextareaPosition();\n }\n\n /**\n * @private\n */\n updateTextareaPosition() {\n if (this.selectionStart === this.selectionEnd) {\n const style = this._calcTextareaPosition();\n this.hiddenTextarea!.style.left = style.left;\n this.hiddenTextarea!.style.top = style.top;\n }\n }\n\n /**\n * @private\n * @return {Object} style contains style for hiddenTextarea\n */\n _calcTextareaPosition() {\n if (!this.canvas) {\n return { left: '1px', top: '1px' };\n }\n const desiredPosition = this.inCompositionMode\n ? this.compositionStart\n : this.selectionStart,\n boundaries = this._getCursorBoundaries(desiredPosition),\n cursorLocation = this.get2DCursorLocation(desiredPosition),\n lineIndex = cursorLocation.lineIndex,\n charIndex = cursorLocation.charIndex,\n charHeight =\n this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize') *\n this.lineHeight,\n leftOffset = boundaries.leftOffset,\n retinaScaling = this.getCanvasRetinaScaling(),\n upperCanvas = this.canvas.upperCanvasEl,\n upperCanvasWidth = upperCanvas.width / retinaScaling,\n upperCanvasHeight = upperCanvas.height / retinaScaling,\n maxWidth = upperCanvasWidth - charHeight,\n maxHeight = upperCanvasHeight - charHeight;\n\n const p = new Point(\n boundaries.left + leftOffset,\n boundaries.top + boundaries.topOffset + charHeight,\n )\n .transform(this.calcTransformMatrix())\n .transform(this.canvas.viewportTransform)\n .multiply(\n new Point(\n upperCanvas.clientWidth / upperCanvasWidth,\n upperCanvas.clientHeight / upperCanvasHeight,\n ),\n );\n\n if (p.x < 0) {\n p.x = 0;\n }\n if (p.x > maxWidth) {\n p.x = maxWidth;\n }\n if (p.y < 0) {\n p.y = 0;\n }\n if (p.y > maxHeight) {\n p.y = maxHeight;\n }\n\n // add canvas offset on document\n p.x += this.canvas._offset.left;\n p.y += this.canvas._offset.top;\n\n return {\n left: `${p.x}px`,\n top: `${p.y}px`,\n fontSize: `${charHeight}px`,\n charHeight: charHeight,\n };\n }\n\n /**\n * @private\n */\n _saveEditingProps() {\n this._savedProps = {\n hasControls: this.hasControls,\n borderColor: this.borderColor,\n lockMovementX: this.lockMovementX,\n lockMovementY: this.lockMovementY,\n hoverCursor: this.hoverCursor,\n selectable: this.selectable,\n defaultCursor: this.canvas && this.canvas.defaultCursor,\n moveCursor: this.canvas && this.canvas.moveCursor,\n };\n }\n\n /**\n * @private\n */\n _restoreEditingProps() {\n if (!this._savedProps) {\n return;\n }\n\n this.hoverCursor = this._savedProps.hoverCursor;\n this.hasControls = this._savedProps.hasControls;\n this.borderColor = this._savedProps.borderColor;\n this.selectable = this._savedProps.selectable;\n this.lockMovementX = this._savedProps.lockMovementX;\n this.lockMovementY = this._savedProps.lockMovementY;\n\n if (this.canvas) {\n this.canvas.defaultCursor =\n this._savedProps.defaultCursor || this.canvas.defaultCursor;\n this.canvas.moveCursor =\n this._savedProps.moveCursor || this.canvas.moveCursor;\n }\n\n delete this._savedProps;\n }\n\n /**\n * runs the actual logic that exits from editing state, see {@link exitEditing}\n */\n protected _exitEditing() {\n const hiddenTextarea = this.hiddenTextarea;\n this.selected = false;\n this.isEditing = false;\n\n if (hiddenTextarea) {\n hiddenTextarea.blur && hiddenTextarea.blur();\n hiddenTextarea.parentNode &&\n hiddenTextarea.parentNode.removeChild(hiddenTextarea);\n }\n this.hiddenTextarea = null;\n this.abortCursorAnimation();\n this.selectionStart !== this.selectionEnd && this.clearContextTop();\n }\n\n /**\n * Exits from editing state and fires relevant events\n */\n exitEditing() {\n const isTextChanged = this._textBeforeEdit !== this.text;\n this._exitEditing();\n this.selectionEnd = this.selectionStart;\n this._restoreEditingProps();\n if (this._forceClearCache) {\n this.initDimensions();\n this.setCoords();\n }\n this.fire('editing:exited');\n isTextChanged && this.fire(MODIFIED);\n if (this.canvas) {\n this.canvas.fire('text:editing:exited', {\n target: this as unknown as IText,\n });\n // todo: evaluate add an action to this event\n isTextChanged && this.canvas.fire('object:modified', { target: this });\n }\n return this;\n }\n\n /**\n * @private\n */\n _removeExtraneousStyles() {\n for (const prop in this.styles) {\n if (!this._textLines[prop as unknown as number]) {\n delete this.styles[prop];\n }\n }\n }\n\n /**\n * remove and reflow a style block from start to end.\n * @param {Number} start linear start position for removal (included in removal)\n * @param {Number} end linear end position for removal ( excluded from removal )\n */\n removeStyleFromTo(start: number, end: number) {\n const { lineIndex: lineStart, charIndex: charStart } =\n this.get2DCursorLocation(start, true),\n { lineIndex: lineEnd, charIndex: charEnd } = this.get2DCursorLocation(\n end,\n true,\n );\n if (lineStart !== lineEnd) {\n // step1 remove the trailing of lineStart\n if (this.styles[lineStart]) {\n for (\n let i = charStart;\n i < this._unwrappedTextLines[lineStart].length;\n i++\n ) {\n delete this.styles[lineStart][i];\n }\n }\n // step2 move the trailing of lineEnd to lineStart if needed\n if (this.styles[lineEnd]) {\n for (\n let i = charEnd;\n i < this._unwrappedTextLines[lineEnd].length;\n i++\n ) {\n const styleObj = this.styles[lineEnd][i];\n if (styleObj) {\n this.styles[lineStart] || (this.styles[lineStart] = {});\n this.styles[lineStart][charStart + i - charEnd] = styleObj;\n }\n }\n }\n // step3 detects lines will be completely removed.\n for (let i = lineStart + 1; i <= lineEnd; i++) {\n delete this.styles[i];\n }\n // step4 shift remaining lines.\n this.shiftLineStyles(lineEnd, lineStart - lineEnd);\n } else {\n // remove and shift left on the same line\n if (this.styles[lineStart]) {\n const styleObj = this.styles[lineStart];\n const diff = charEnd - charStart;\n for (let i = charStart; i < charEnd; i++) {\n delete styleObj[i];\n }\n for (const char in this.styles[lineStart]) {\n const numericChar = parseInt(char, 10);\n if (numericChar >= charEnd) {\n styleObj[numericChar - diff] = styleObj[char];\n delete styleObj[char];\n }\n }\n }\n }\n }\n\n /**\n * Shifts line styles up or down\n * @param {Number} lineIndex Index of a line\n * @param {Number} offset Can any number?\n */\n shiftLineStyles(lineIndex: number, offset: number) {\n const clonedStyles = Object.assign({}, this.styles);\n for (const line in this.styles) {\n const numericLine = parseInt(line, 10);\n if (numericLine > lineIndex) {\n this.styles[numericLine + offset] = clonedStyles[numericLine];\n if (!clonedStyles[numericLine - offset]) {\n delete this.styles[numericLine];\n }\n }\n }\n }\n\n /**\n * Handle insertion of more consecutive style lines for when one or more\n * newlines gets added to the text. Since current style needs to be shifted\n * first we shift the current style of the number lines needed, then we add\n * new lines from the last to the first.\n * @param {Number} lineIndex Index of a line\n * @param {Number} charIndex Index of a char\n * @param {Number} qty number of lines to add\n * @param {Array} copiedStyle Array of objects styles\n */\n insertNewlineStyleObject(\n lineIndex: number,\n charIndex: number,\n qty: number,\n copiedStyle?: { [index: number]: TextStyleDeclaration },\n ) {\n const newLineStyles: { [index: number]: TextStyleDeclaration } = {};\n const originalLineLength = this._unwrappedTextLines[lineIndex].length;\n const isEndOfLine = originalLineLength === charIndex;\n\n let someStyleIsCarryingOver = false;\n qty || (qty = 1);\n this.shiftLineStyles(lineIndex, qty);\n const currentCharStyle = this.styles[lineIndex]\n ? this.styles[lineIndex][charIndex === 0 ? charIndex : charIndex - 1]\n : undefined;\n\n // we clone styles of all chars\n // after cursor onto the current line\n for (const index in this.styles[lineIndex]) {\n const numIndex = parseInt(index, 10);\n if (numIndex >= charIndex) {\n someStyleIsCarryingOver = true;\n newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index];\n // remove lines from the previous line since they're on a new line now\n if (!(isEndOfLine && charIndex === 0)) {\n delete this.styles[lineIndex][index];\n }\n }\n }\n let styleCarriedOver = false;\n if (someStyleIsCarryingOver && !isEndOfLine) {\n // if is end of line, the extra style we copied\n // is probably not something we want\n this.styles[lineIndex + qty] = newLineStyles;\n styleCarriedOver = true;\n }\n if (styleCarriedOver || originalLineLength > charIndex) {\n // skip the last line of since we already prepared it.\n // or contains text without style that we don't want to style\n // just because it changed lines\n qty--;\n }\n // for the all the lines or all the other lines\n // we clone current char style onto the next (otherwise empty) line\n while (qty > 0) {\n if (copiedStyle && copiedStyle[qty - 1]) {\n this.styles[lineIndex + qty] = {\n 0: { ...copiedStyle[qty - 1] },\n };\n } else if (currentCharStyle) {\n this.styles[lineIndex + qty] = {\n 0: { ...currentCharStyle },\n };\n } else {\n delete this.styles[lineIndex + qty];\n }\n qty--;\n }\n this._forceClearCache = true;\n }\n\n /**\n * Inserts style object for a given line/char index\n * @param {Number} lineIndex Index of a line\n * @param {Number} charIndex Index of a char\n * @param {Number} quantity number Style object to insert, if given\n * @param {Array} copiedStyle array of style objects\n */\n insertCharStyleObject(\n lineIndex: number,\n charIndex: number,\n quantity: number,\n copiedStyle?: TextStyleDeclaration[],\n ) {\n if (!this.styles) {\n this.styles = {};\n }\n const currentLineStyles = this.styles[lineIndex],\n currentLineStylesCloned = currentLineStyles\n ? { ...currentLineStyles }\n : {};\n\n quantity || (quantity = 1);\n // shift all char styles by quantity forward\n // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4\n for (const index in currentLineStylesCloned) {\n const numericIndex = parseInt(index, 10);\n if (numericIndex >= charIndex) {\n currentLineStyles[numericIndex + quantity] =\n currentLineStylesCloned[numericIndex];\n // only delete the style if there was nothing moved there\n if (!currentLineStylesCloned[numericIndex - quantity]) {\n delete currentLineStyles[numericIndex];\n }\n }\n }\n this._forceClearCache = true;\n if (copiedStyle) {\n while (quantity--) {\n if (!Object.keys(copiedStyle[quantity]).length) {\n continue;\n }\n if (!this.styles[lineIndex]) {\n this.styles[lineIndex] = {};\n }\n this.styles[lineIndex][charIndex + quantity] = {\n ...copiedStyle[quantity],\n };\n }\n return;\n }\n if (!currentLineStyles) {\n return;\n }\n const newStyle = currentLineStyles[charIndex ? charIndex - 1 : 1];\n while (newStyle && quantity--) {\n this.styles[lineIndex][charIndex + quantity] = { ...newStyle };\n }\n }\n\n /**\n * Inserts style object(s)\n * @param {Array} insertedText Characters at the location where style is inserted\n * @param {Number} start cursor index for inserting style\n * @param {Array} [copiedStyle] array of style objects to insert.\n */\n insertNewStyleBlock(\n insertedText: string[],\n start: number,\n copiedStyle?: TextStyleDeclaration[],\n ) {\n const cursorLoc = this.get2DCursorLocation(start, true),\n addedLines = [0];\n let linesLength = 0;\n // get an array of how many char per lines are being added.\n for (let i = 0; i < insertedText.length; i++) {\n if (insertedText[i] === '\\n') {\n linesLength++;\n addedLines[linesLength] = 0;\n } else {\n addedLines[linesLength]++;\n }\n }\n // for the first line copy the style from the current char position.\n if (addedLines[0] > 0) {\n this.insertCharStyleObject(\n cursorLoc.lineIndex,\n cursorLoc.charIndex,\n addedLines[0],\n copiedStyle,\n );\n copiedStyle = copiedStyle && copiedStyle.slice(addedLines[0] + 1);\n }\n linesLength &&\n this.insertNewlineStyleObject(\n cursorLoc.lineIndex,\n cursorLoc.charIndex + addedLines[0],\n linesLength,\n );\n let i;\n for (i = 1; i < linesLength; i++) {\n if (addedLines[i] > 0) {\n this.insertCharStyleObject(\n cursorLoc.lineIndex + i,\n 0,\n addedLines[i],\n copiedStyle,\n );\n } else if (copiedStyle) {\n // this test is required in order to close #6841\n // when a pasted buffer begins with a newline then\n // this.styles[cursorLoc.lineIndex + i] and copiedStyle[0]\n // may be undefined for some reason\n if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) {\n this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];\n }\n }\n copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1);\n }\n if (addedLines[i] > 0) {\n this.insertCharStyleObject(\n cursorLoc.lineIndex + i,\n 0,\n addedLines[i],\n copiedStyle,\n );\n }\n }\n\n /**\n * Removes characters from start/end\n * start/end ar per grapheme position in _text array.\n *\n * @param {Number} start\n * @param {Number} end default to start + 1\n */\n removeChars(start: number, end: number = start + 1) {\n this.removeStyleFromTo(start, end);\n this._text.splice(start, end - start);\n this.text = this._text.join('');\n this.set('dirty', true);\n this.initDimensions();\n this.setCoords();\n this._removeExtraneousStyles();\n }\n\n /**\n * insert characters at start position, before start position.\n * start equal 1 it means the text get inserted between actual grapheme 0 and 1\n * if style array is provided, it must be as the same length of text in graphemes\n * if end is provided and is bigger than start, old text is replaced.\n * start/end ar per grapheme position in _text array.\n *\n * @param {String} text text to insert\n * @param {Array} style array of style objects\n * @param {Number} start\n * @param {Number} end default to start + 1\n */\n insertChars(\n text: string,\n style: TextStyleDeclaration[] | undefined,\n start: number,\n end: number = start,\n ) {\n if (end > start) {\n this.removeStyleFromTo(start, end);\n }\n const graphemes = this.graphemeSplit(text);\n this.insertNewStyleBlock(graphemes, start, style);\n this._text = [\n ...this._text.slice(0, start),\n ...graphemes,\n ...this._text.slice(end),\n ];\n this.text = this._text.join('');\n this.set('dirty', true);\n this.initDimensions();\n this.setCoords();\n this._removeExtraneousStyles();\n }\n\n /**\n * Set the selectionStart and selectionEnd according to the new position of cursor\n * mimic the key - mouse navigation when shift is pressed.\n */\n setSelectionStartEndWithShift(\n start: number,\n end: number,\n newSelection: number,\n ) {\n if (newSelection <= start) {\n if (end === start) {\n this._selectionDirection = LEFT;\n } else if (this._selectionDirection === RIGHT) {\n this._selectionDirection = LEFT;\n this.selectionEnd = start;\n }\n this.selectionStart = newSelection;\n } else if (newSelection > start && newSelection < end) {\n if (this._selectionDirection === RIGHT) {\n this.selectionEnd = newSelection;\n } else {\n this.selectionStart = newSelection;\n }\n } else {\n // newSelection is > selection start and end\n if (end === start) {\n this._selectionDirection = RIGHT;\n } else if (this._selectionDirection === LEFT) {\n this._selectionDirection = RIGHT;\n this.selectionStart = end;\n }\n this.selectionEnd = newSelection;\n }\n }\n}\n"],"names":["reNonWord","ITextBehavior","FabricText","constructor","super","arguments","_defineProperty","initBehavior","this","_tick","bind","_onTickComplete","updateSelectionOnMouseMove","onDeselect","options","isEditing","exitEditing","selected","_animateCursor","_ref","toValue","duration","delay","onComplete","animate","startValue","_currentCursorOpacity","endValue","abort","canvas","selectionStart","selectionEnd","onChange","value","renderCursorOrSelection","_currentTickState","cursorDuration","Math","max","_this$_currentTickCom","_currentTickCompleteState","initDelayedCursor","restart","abortCursorAnimation","cursorDelay","shouldClear","forEach","cursorAnimation","isDone","clearContextTop","restartCursorIfNeeded","some","selectAll","_text","length","_fireSelectionChanged","_updateTextarea","getSelectedText","slice","join","findWordBoundaryLeft","startFrom","offset","index","_reSpace","test","findWordBoundaryRight","findLineBoundaryLeft","findLineBoundaryRight","searchWordBoundary","direction","text","reNewline","_char","selectWord","newSelectionStart","newSelectionEnd","selectLine","enterEditing","e","editable","calcOffset","textEditingManager","exitTextEditing","initHiddenTextarea","hiddenTextarea","focus","_saveEditingProps","_setEditingProps","_textBeforeEdit","fire","undefined","target","requestRenderAll","getActiveControl","el","getDocumentFromElement","activeElement","getSelectionStartFromPointer","currentStart","currentEnd","__selectionStartOnMouseDown","hoverCursor","defaultCursor","moveCursor","borderColor","editingBorderColor","hasControls","selectable","lockMovementX","lockMovementY","fromStringToGraphemeSelection","start","end","smallerTextStart","graphemeStart","graphemeSplit","smallerTextEnd","fromGraphemeToStringSelection","graphemes","cursorOffsetCache","inCompositionMode","newSelection","updateTextareaPosition","updateFromTextArea","textarea","set","initDimensions","setCoords","style","_calcTextareaPosition","left","top","desiredPosition","compositionStart","boundaries","_getCursorBoundaries","cursorLocation","get2DCursorLocation","lineIndex","charIndex","charHeight","getValueOfPropertyAt","lineHeight","leftOffset","retinaScaling","getCanvasRetinaScaling","upperCanvas","upperCanvasEl","upperCanvasWidth","width","upperCanvasHeight","height","maxWidth","maxHeight","p","Point","topOffset","transform","calcTransformMatrix","viewportTransform","multiply","clientWidth","clientHeight","x","y","_offset","concat","fontSize","_savedProps","_restoreEditingProps","_exitEditing","blur","parentNode","removeChild","isTextChanged","_forceClearCache","MODIFIED","_removeExtraneousStyles","prop","styles","_textLines","removeStyleFromTo","lineStart","charStart","lineEnd","charEnd","i","_unwrappedTextLines","styleObj","shiftLineStyles","diff","char","numericChar","parseInt","clonedStyles","Object","assign","line","numericLine","insertNewlineStyleObject","qty","copiedStyle","newLineStyles","originalLineLength","isEndOfLine","someStyleIsCarryingOver","currentCharStyle","numIndex","styleCarriedOver","_objectSpread","insertCharStyleObject","quantity","currentLineStyles","currentLineStylesCloned","numericIndex","keys","newStyle","insertNewStyleBlock","insertedText","cursorLoc","addedLines","linesLength","removeChars","splice","insertChars","setSelectionStartEndWithShift","_selectionDirection","LEFT","RIGHT"],"mappings":"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"}