UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

1 lines 28.5 kB
{"version":3,"file":"ITextKeyBehavior.mjs","names":[],"sources":["../../../../src/shapes/IText/ITextKeyBehavior.ts"],"sourcesContent":["import { config } from '../../config';\nimport { getFabricDocument, getEnv } from '../../env';\nimport { capValue } from '../../util/misc/capValue';\nimport type { ITextEvents } from './ITextBehavior';\nimport { ITextBehavior } from './ITextBehavior';\nimport type { TKeyMapIText } from './constants';\nimport type { TOptions } from '../../typedefs';\nimport type { TextProps, SerializedTextProps } from '../Text/Text';\nimport { getDocumentFromElement } from '../../util/dom_misc';\nimport { CHANGED, LEFT, RIGHT } from '../../constants';\nimport type { IText } from './IText';\nimport type { TextStyleDeclaration } from '../Text/StyledText';\n\nexport abstract class ITextKeyBehavior<\n Props extends TOptions<TextProps> = Partial<TextProps>,\n SProps extends SerializedTextProps = SerializedTextProps,\n EventSpec extends ITextEvents = ITextEvents,\n> extends ITextBehavior<Props, SProps, EventSpec> {\n /**\n * For functionalities on keyDown\n * Map a special key to a function of the instance/prototype\n * If you need different behavior for ESC or TAB or arrows, you have to change\n * this map setting the name of a function that you build on the IText or\n * your prototype.\n * the map change will affect all Instances unless you need for only some text Instances\n * in that case you have to clone this object and assign your Instance.\n * this.keysMap = Object.assign({}, this.keysMap);\n * The function must be in IText.prototype.myFunction And will receive event as args[0]\n */\n declare keysMap: TKeyMapIText;\n\n declare keysMapRtl: TKeyMapIText;\n\n /**\n * For functionalities on keyUp + ctrl || cmd\n */\n declare ctrlKeysMapUp: TKeyMapIText;\n\n /**\n * For functionalities on keyDown + ctrl || cmd\n */\n declare ctrlKeysMapDown: TKeyMapIText;\n\n declare hiddenTextarea: HTMLTextAreaElement | null;\n\n /**\n * DOM container to append the hiddenTextarea.\n * An alternative to attaching to the document.body.\n * Useful to reduce laggish redraw of the full document.body tree and\n * also with modals event capturing that won't let the textarea take focus.\n * @type HTMLElement\n */\n declare hiddenTextareaContainer?: HTMLElement | null;\n\n declare private _clickHandlerInitialized: boolean;\n declare private _copyDone: boolean;\n declare private fromPaste: boolean;\n\n /**\n * Initializes hidden textarea (needed to bring up keyboard in iOS)\n */\n initHiddenTextarea() {\n const doc =\n (this.canvas && getDocumentFromElement(this.canvas.getElement())) ||\n getFabricDocument();\n const textarea = doc.createElement('textarea');\n Object.entries({\n autocapitalize: 'off',\n autocorrect: 'off',\n autocomplete: 'off',\n spellcheck: 'false',\n 'data-fabric': 'textarea',\n wrap: 'off',\n name: 'fabricTextarea',\n }).map(([attribute, value]) => textarea.setAttribute(attribute, value));\n const { top, left, fontSize } = this._calcTextareaPosition();\n // line-height: 1px; was removed from the style to fix this:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=870966\n textarea.style.cssText = `position: absolute; top: ${top}; left: ${left}; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px; padding-top: ${fontSize};`;\n\n (this.hiddenTextareaContainer || doc.body).appendChild(textarea);\n\n Object.entries({\n blur: 'blur',\n keydown: 'onKeyDown',\n keyup: 'onKeyUp',\n input: 'onInput',\n copy: 'copy',\n cut: 'copy',\n paste: 'paste',\n compositionstart: 'onCompositionStart',\n compositionupdate: 'onCompositionUpdate',\n compositionend: 'onCompositionEnd',\n } as Record<string, keyof this>).map(([eventName, handler]) =>\n textarea.addEventListener(\n eventName,\n (this[handler] as EventListener).bind(this),\n ),\n );\n this.hiddenTextarea = textarea;\n }\n\n /**\n * Override this method to customize cursor behavior on textbox blur\n */\n blur() {\n this.abortCursorAnimation();\n }\n\n /**\n * Handles keydown event\n * only used for arrows and combination of modifier keys.\n * @param {KeyboardEvent} e Event object\n */\n onKeyDown(e: KeyboardEvent) {\n if (!this.isEditing) {\n return;\n }\n const keyMap = this.direction === 'rtl' ? this.keysMapRtl : this.keysMap;\n if (e.keyCode in keyMap) {\n (this[keyMap[e.keyCode] as keyof this] as (arg: KeyboardEvent) => void)(\n e,\n );\n } else if (e.keyCode in this.ctrlKeysMapDown && (e.ctrlKey || e.metaKey)) {\n (\n this[this.ctrlKeysMapDown[e.keyCode] as keyof this] as (\n arg: KeyboardEvent,\n ) => void\n )(e);\n } else {\n return;\n }\n e.stopImmediatePropagation();\n e.preventDefault();\n if (e.keyCode >= 33 && e.keyCode <= 40) {\n // if i press an arrow key just update selection\n this.inCompositionMode = false;\n this.clearContextTop();\n this.renderCursorOrSelection();\n } else {\n this.canvas && this.canvas.requestRenderAll();\n }\n }\n\n /**\n * Handles keyup event\n * We handle KeyUp because ie11 and edge have difficulties copy/pasting\n * if a copy/cut event fired, keyup is dismissed\n * @param {KeyboardEvent} e Event object\n */\n onKeyUp(e: KeyboardEvent) {\n if (!this.isEditing || this._copyDone || this.inCompositionMode) {\n this._copyDone = false;\n return;\n }\n if (e.keyCode in this.ctrlKeysMapUp && (e.ctrlKey || e.metaKey)) {\n (\n this[this.ctrlKeysMapUp[e.keyCode] as keyof this] as (\n arg: KeyboardEvent,\n ) => void\n )(e);\n } else {\n return;\n }\n e.stopImmediatePropagation();\n e.preventDefault();\n this.canvas && this.canvas.requestRenderAll();\n }\n\n /**\n * Handles onInput event\n * @param {Event} e Event object\n */\n onInput(this: this & { hiddenTextarea: HTMLTextAreaElement }, e: Event) {\n const fromPaste = this.fromPaste;\n const { value, selectionStart, selectionEnd } = this.hiddenTextarea;\n this.fromPaste = false;\n e && e.stopPropagation();\n if (!this.isEditing) {\n return;\n }\n const updateAndFire = () => {\n this.updateFromTextArea();\n this.fire(CHANGED);\n if (this.canvas) {\n this.canvas.fire('text:changed', { target: this as unknown as IText });\n this.canvas.requestRenderAll();\n }\n };\n if (this.hiddenTextarea.value === '') {\n this.styles = {};\n updateAndFire();\n return;\n }\n // decisions about style changes.\n const nextText = this._splitTextIntoLines(value).graphemeText,\n charCount = this._text.length,\n nextCharCount = nextText.length,\n _selectionStart = this.selectionStart,\n _selectionEnd = this.selectionEnd,\n selection = _selectionStart !== _selectionEnd;\n let copiedStyle: TextStyleDeclaration[] | undefined,\n removedText,\n charDiff = nextCharCount - charCount,\n removeFrom,\n removeTo;\n\n const textareaSelection = this.fromStringToGraphemeSelection(\n selectionStart,\n selectionEnd,\n value,\n );\n const backDelete = _selectionStart > textareaSelection.selectionStart;\n\n if (selection) {\n removedText = this._text.slice(_selectionStart, _selectionEnd);\n charDiff += _selectionEnd - _selectionStart;\n } else if (nextCharCount < charCount) {\n if (backDelete) {\n removedText = this._text.slice(_selectionEnd + charDiff, _selectionEnd);\n } else {\n removedText = this._text.slice(\n _selectionStart,\n _selectionStart - charDiff,\n );\n }\n }\n const insertedText = nextText.slice(\n textareaSelection.selectionEnd - charDiff,\n textareaSelection.selectionEnd,\n );\n if (removedText && removedText.length) {\n if (insertedText.length) {\n // let's copy some style before deleting.\n // we want to copy the style before the cursor OR the style at the cursor if selection\n // is bigger than 0.\n copiedStyle = this.getSelectionStyles(\n _selectionStart,\n _selectionStart + 1,\n false,\n );\n // now duplicate the style one for each inserted text.\n copiedStyle = insertedText.map(\n () =>\n // this return an array of references, but that is fine since we are\n // copying the style later.\n copiedStyle![0],\n );\n }\n if (selection) {\n removeFrom = _selectionStart;\n removeTo = _selectionEnd;\n } else if (backDelete) {\n // detect differences between forwardDelete and backDelete\n removeFrom = _selectionEnd - removedText.length;\n removeTo = _selectionEnd;\n } else {\n removeFrom = _selectionEnd;\n removeTo = _selectionEnd + removedText.length;\n }\n this.removeStyleFromTo(removeFrom, removeTo);\n }\n if (insertedText.length) {\n const { copyPasteData } = getEnv();\n if (\n fromPaste &&\n insertedText.join('') === copyPasteData.copiedText &&\n !config.disableStyleCopyPaste\n ) {\n copiedStyle = copyPasteData.copiedTextStyle;\n }\n this.insertNewStyleBlock(insertedText, _selectionStart, copiedStyle);\n }\n updateAndFire();\n }\n\n /**\n * Composition start\n */\n onCompositionStart() {\n this.inCompositionMode = true;\n }\n\n /**\n * Composition end\n */\n onCompositionEnd() {\n this.inCompositionMode = false;\n }\n\n onCompositionUpdate({ target }: CompositionEvent) {\n const { selectionStart, selectionEnd } = target as HTMLTextAreaElement;\n this.compositionStart = selectionStart;\n this.compositionEnd = selectionEnd;\n this.updateTextareaPosition();\n }\n\n /**\n * Copies selected text\n */\n copy() {\n if (this.selectionStart === this.selectionEnd) {\n //do not cut-copy if no selection\n return;\n }\n const { copyPasteData } = getEnv();\n copyPasteData.copiedText = this.getSelectedText();\n if (!config.disableStyleCopyPaste) {\n copyPasteData.copiedTextStyle = this.getSelectionStyles(\n this.selectionStart,\n this.selectionEnd,\n true,\n );\n } else {\n copyPasteData.copiedTextStyle = undefined;\n }\n this._copyDone = true;\n }\n\n /**\n * Pastes text\n */\n paste() {\n this.fromPaste = true;\n }\n\n /**\n * Finds the width in pixels before the cursor on the same line\n * @private\n * @param {Number} lineIndex\n * @param {Number} charIndex\n * @return {Number} widthBeforeCursor width before cursor\n */\n _getWidthBeforeCursor(lineIndex: number, charIndex: number): number {\n let widthBeforeCursor = this._getLineLeftOffset(lineIndex),\n bound;\n\n if (charIndex > 0) {\n bound = this.__charBounds[lineIndex][charIndex - 1];\n widthBeforeCursor += bound.left + bound.width;\n }\n return widthBeforeCursor;\n }\n\n /**\n * Gets start offset of a selection\n * @param {KeyboardEvent} e Event object\n * @param {Boolean} isRight\n * @return {Number}\n */\n getDownCursorOffset(e: KeyboardEvent, isRight: boolean): number {\n const selectionProp = this._getSelectionForOffset(e, isRight),\n cursorLocation = this.get2DCursorLocation(selectionProp),\n lineIndex = cursorLocation.lineIndex;\n // if on last line, down cursor goes to end of line\n if (\n lineIndex === this._textLines.length - 1 ||\n e.metaKey ||\n e.keyCode === 34\n ) {\n // move to the end of a text\n return this._text.length - selectionProp;\n }\n const charIndex = cursorLocation.charIndex,\n widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),\n indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor),\n textAfterCursor = this._textLines[lineIndex].slice(charIndex);\n return (\n textAfterCursor.length +\n indexOnOtherLine +\n 1 +\n this.missingNewlineOffset(lineIndex)\n );\n }\n\n /**\n * private\n * Helps finding if the offset should be counted from Start or End\n * @param {KeyboardEvent} e Event object\n * @param {Boolean} isRight\n * @return {Number}\n */\n _getSelectionForOffset(e: KeyboardEvent, isRight: boolean): number {\n if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) {\n return this.selectionEnd;\n } else {\n return this.selectionStart;\n }\n }\n\n /**\n * @param {KeyboardEvent} e Event object\n * @param {Boolean} isRight\n * @return {Number}\n */\n getUpCursorOffset(e: KeyboardEvent, isRight: boolean): number {\n const selectionProp = this._getSelectionForOffset(e, isRight),\n cursorLocation = this.get2DCursorLocation(selectionProp),\n lineIndex = cursorLocation.lineIndex;\n if (lineIndex === 0 || e.metaKey || e.keyCode === 33) {\n // if on first line, up cursor goes to start of line\n return -selectionProp;\n }\n const charIndex = cursorLocation.charIndex,\n widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),\n indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor),\n textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex),\n missingNewlineOffset = this.missingNewlineOffset(lineIndex - 1);\n // return a negative offset\n return (\n -this._textLines[lineIndex - 1].length +\n indexOnOtherLine -\n textBeforeCursor.length +\n (1 - missingNewlineOffset)\n );\n }\n\n /**\n * for a given width it founds the matching character.\n * @private\n */\n _getIndexOnLine(lineIndex: number, width: number) {\n const line = this._textLines[lineIndex],\n lineLeftOffset = this._getLineLeftOffset(lineIndex);\n let widthOfCharsOnLine = lineLeftOffset,\n indexOnLine = 0,\n charWidth,\n foundMatch;\n\n for (let j = 0, jlen = line.length; j < jlen; j++) {\n charWidth = this.__charBounds[lineIndex][j].width;\n widthOfCharsOnLine += charWidth;\n if (widthOfCharsOnLine > width) {\n foundMatch = true;\n const leftEdge = widthOfCharsOnLine - charWidth,\n rightEdge = widthOfCharsOnLine,\n offsetFromLeftEdge = Math.abs(leftEdge - width),\n offsetFromRightEdge = Math.abs(rightEdge - width);\n\n indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : j - 1;\n break;\n }\n }\n\n // reached end\n if (!foundMatch) {\n indexOnLine = line.length - 1;\n }\n\n return indexOnLine;\n }\n\n /**\n * Moves cursor down\n * @param {KeyboardEvent} e Event object\n */\n moveCursorDown(e: KeyboardEvent) {\n if (\n this.selectionStart >= this._text.length &&\n this.selectionEnd >= this._text.length\n ) {\n return;\n }\n this._moveCursorUpOrDown('Down', e);\n }\n\n /**\n * Moves cursor up\n * @param {KeyboardEvent} e Event object\n */\n moveCursorUp(e: KeyboardEvent) {\n if (this.selectionStart === 0 && this.selectionEnd === 0) {\n return;\n }\n this._moveCursorUpOrDown('Up', e);\n }\n\n /**\n * Moves cursor up or down, fires the events\n * @param {String} direction 'Up' or 'Down'\n * @param {KeyboardEvent} e Event object\n */\n _moveCursorUpOrDown(direction: 'Up' | 'Down', e: KeyboardEvent) {\n const offset = this[`get${direction}CursorOffset`](\n e,\n this._selectionDirection === RIGHT,\n );\n if (e.shiftKey) {\n this.moveCursorWithShift(offset);\n } else {\n this.moveCursorWithoutShift(offset);\n }\n if (offset !== 0) {\n const max = this.text.length;\n this.selectionStart = capValue(0, this.selectionStart, max);\n this.selectionEnd = capValue(0, this.selectionEnd, max);\n // TODO fix: abort and init should be an alternative depending\n // on selectionStart/End being equal or different\n this.abortCursorAnimation();\n this.initDelayedCursor();\n this._fireSelectionChanged();\n this._updateTextarea();\n }\n }\n\n /**\n * Moves cursor with shift\n * @param {Number} offset\n */\n moveCursorWithShift(offset: number) {\n const newSelection =\n this._selectionDirection === LEFT\n ? this.selectionStart + offset\n : this.selectionEnd + offset;\n this.setSelectionStartEndWithShift(\n this.selectionStart,\n this.selectionEnd,\n newSelection,\n );\n return offset !== 0;\n }\n\n /**\n * Moves cursor up without shift\n * @param {Number} offset\n */\n moveCursorWithoutShift(offset: number) {\n if (offset < 0) {\n this.selectionStart += offset;\n this.selectionEnd = this.selectionStart;\n } else {\n this.selectionEnd += offset;\n this.selectionStart = this.selectionEnd;\n }\n return offset !== 0;\n }\n\n /**\n * Moves cursor left\n * @param {KeyboardEvent} e Event object\n */\n moveCursorLeft(e: KeyboardEvent) {\n if (this.selectionStart === 0 && this.selectionEnd === 0) {\n return;\n }\n this._moveCursorLeftOrRight('Left', e);\n }\n\n /**\n * @private\n * @return {Boolean} true if a change happened\n *\n * @todo refactor not to use method name composition\n */\n _move(\n e: KeyboardEvent,\n prop: 'selectionStart' | 'selectionEnd',\n direction: 'Left' | 'Right',\n ): boolean {\n let newValue: number | undefined;\n if (e.altKey) {\n newValue = this[`findWordBoundary${direction}`](this[prop]);\n } else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36) {\n newValue = this[`findLineBoundary${direction}`](this[prop]);\n } else {\n this[prop] += direction === 'Left' ? -1 : 1;\n return true;\n }\n if (typeof newValue !== 'undefined' && this[prop] !== newValue) {\n this[prop] = newValue;\n return true;\n }\n return false;\n }\n\n /**\n * @private\n */\n _moveLeft(e: KeyboardEvent, prop: 'selectionStart' | 'selectionEnd') {\n return this._move(e, prop, 'Left');\n }\n\n /**\n * @private\n */\n _moveRight(e: KeyboardEvent, prop: 'selectionStart' | 'selectionEnd') {\n return this._move(e, prop, 'Right');\n }\n\n /**\n * Moves cursor left without keeping selection\n * @param {KeyboardEvent} e\n */\n moveCursorLeftWithoutShift(e: KeyboardEvent) {\n let change = true;\n this._selectionDirection = LEFT;\n\n // only move cursor when there is no selection,\n // otherwise we discard it, and leave cursor on same place\n if (\n this.selectionEnd === this.selectionStart &&\n this.selectionStart !== 0\n ) {\n change = this._moveLeft(e, 'selectionStart');\n }\n this.selectionEnd = this.selectionStart;\n return change;\n }\n\n /**\n * Moves cursor left while keeping selection\n * @param {KeyboardEvent} e\n */\n moveCursorLeftWithShift(e: KeyboardEvent) {\n if (\n this._selectionDirection === RIGHT &&\n this.selectionStart !== this.selectionEnd\n ) {\n return this._moveLeft(e, 'selectionEnd');\n } else if (this.selectionStart !== 0) {\n this._selectionDirection = LEFT;\n return this._moveLeft(e, 'selectionStart');\n }\n }\n\n /**\n * Moves cursor right\n * @param {KeyboardEvent} e Event object\n */\n moveCursorRight(e: KeyboardEvent) {\n if (\n this.selectionStart >= this._text.length &&\n this.selectionEnd >= this._text.length\n ) {\n return;\n }\n this._moveCursorLeftOrRight('Right', e);\n }\n\n /**\n * Moves cursor right or Left, fires event\n * @param {String} direction 'Left', 'Right'\n * @param {KeyboardEvent} e Event object\n */\n _moveCursorLeftOrRight(direction: 'Left' | 'Right', e: KeyboardEvent) {\n const actionName = `moveCursor${direction}${\n e.shiftKey ? 'WithShift' : 'WithoutShift'\n }` as const;\n this._currentCursorOpacity = 1;\n if (this[actionName](e)) {\n // TODO fix: abort and init should be an alternative depending\n // on selectionStart/End being equal or different\n this.abortCursorAnimation();\n this.initDelayedCursor();\n this._fireSelectionChanged();\n this._updateTextarea();\n }\n }\n\n /**\n * Moves cursor right while keeping selection\n * @param {KeyboardEvent} e\n */\n moveCursorRightWithShift(e: KeyboardEvent) {\n if (\n this._selectionDirection === LEFT &&\n this.selectionStart !== this.selectionEnd\n ) {\n return this._moveRight(e, 'selectionStart');\n } else if (this.selectionEnd !== this._text.length) {\n this._selectionDirection = RIGHT;\n return this._moveRight(e, 'selectionEnd');\n }\n }\n\n /**\n * Moves cursor right without keeping selection\n * @param {KeyboardEvent} e Event object\n */\n moveCursorRightWithoutShift(e: KeyboardEvent) {\n let changed = true;\n this._selectionDirection = RIGHT;\n\n if (this.selectionStart === this.selectionEnd) {\n changed = this._moveRight(e, 'selectionStart');\n this.selectionEnd = this.selectionStart;\n } else {\n this.selectionStart = this.selectionEnd;\n }\n return changed;\n }\n}\n"],"mappings":"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"}