UNPKG

fabric

Version:

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

1 lines 31.6 kB
{"version":3,"file":"ITextKeyBehavior.min.mjs","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 * @default\n */\n declare hiddenTextareaContainer?: HTMLElement | null;\n\n private declare _clickHandlerInitialized: boolean;\n private declare _copyDone: boolean;\n private declare 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 }).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 // @ts-expect-error legacy method calling pattern\n this[keyMap[e.keyCode]](e);\n } else if (e.keyCode in this.ctrlKeysMapDown && (e.ctrlKey || e.metaKey)) {\n // @ts-expect-error legacy method calling pattern\n this[this.ctrlKeysMapDown[e.keyCode]](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 // @ts-expect-error legacy method calling pattern\n this[this.ctrlKeysMapUp[e.keyCode]](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 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(\n this.hiddenTextarea.value,\n ).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 this.hiddenTextarea.selectionStart,\n this.hiddenTextarea.selectionEnd,\n this.hiddenTextarea.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"],"names":["ITextKeyBehavior","ITextBehavior","initHiddenTextarea","doc","this","canvas","getDocumentFromElement","getElement","getFabricDocument","textarea","createElement","Object","entries","autocapitalize","autocorrect","autocomplete","spellcheck","wrap","map","_ref","attribute","value","setAttribute","top","left","fontSize","_calcTextareaPosition","style","cssText","concat","hiddenTextareaContainer","body","appendChild","blur","keydown","keyup","input","copy","cut","paste","compositionstart","compositionupdate","compositionend","_ref2","eventName","handler","addEventListener","bind","hiddenTextarea","abortCursorAnimation","onKeyDown","e","isEditing","keyMap","direction","keysMapRtl","keysMap","keyCode","ctrlKeysMapDown","ctrlKey","metaKey","stopImmediatePropagation","preventDefault","inCompositionMode","clearContextTop","renderCursorOrSelection","requestRenderAll","onKeyUp","_copyDone","ctrlKeysMapUp","onInput","fromPaste","stopPropagation","updateAndFire","updateFromTextArea","fire","CHANGED","target","styles","nextText","_splitTextIntoLines","graphemeText","charCount","_text","length","nextCharCount","selectionStart","selectionEnd","selection","copiedStyle","removedText","removeFrom","removeTo","charDiff","textareaSelection","fromStringToGraphemeSelection","backDelete","slice","insertedText","getSelectionStyles","removeStyleFromTo","copyPasteData","getEnv","join","copiedText","config","disableStyleCopyPaste","copiedTextStyle","insertNewStyleBlock","onCompositionStart","onCompositionEnd","onCompositionUpdate","_ref3","compositionStart","compositionEnd","updateTextareaPosition","getSelectedText","undefined","_getWidthBeforeCursor","lineIndex","charIndex","bound","widthBeforeCursor","_getLineLeftOffset","__charBounds","width","getDownCursorOffset","isRight","selectionProp","_getSelectionForOffset","cursorLocation","get2DCursorLocation","_textLines","indexOnOtherLine","_getIndexOnLine","missingNewlineOffset","shiftKey","getUpCursorOffset","textBeforeCursor","line","charWidth","foundMatch","widthOfCharsOnLine","indexOnLine","j","jlen","leftEdge","rightEdge","offsetFromLeftEdge","Math","abs","moveCursorDown","_moveCursorUpOrDown","moveCursorUp","offset","_selectionDirection","RIGHT","moveCursorWithShift","moveCursorWithoutShift","max","text","capValue","initDelayedCursor","_fireSelectionChanged","_updateTextarea","newSelection","LEFT","setSelectionStartEndWithShift","moveCursorLeft","_moveCursorLeftOrRight","_move","prop","newValue","altKey","_moveLeft","_moveRight","moveCursorLeftWithoutShift","change","moveCursorLeftWithShift","moveCursorRight","actionName","_currentCursorOpacity","moveCursorRightWithShift","moveCursorRightWithoutShift","changed"],"mappings":"sXAaO,MAAeA,UAIZC,EA6CRC,kBAAAA,GACE,MAAMC,EACHC,KAAKC,QAAUC,EAAuBF,KAAKC,OAAOE,eACnDC,IACIC,EAAWN,EAAIO,cAAc,YACnCC,OAAOC,QAAQ,CACbC,eAAgB,MAChBC,YAAa,MACbC,aAAc,MACdC,WAAY,QACZ,cAAe,WACfC,KAAM,QACLC,KAAIC,IAAA,IAAEC,EAAWC,GAAMF,EAAA,OAAKV,EAASa,aAAaF,EAAWC,EAAM,IACtE,MAAME,IAAEA,EAAGC,KAAEA,EAAIC,SAAEA,GAAarB,KAAKsB,wBAGrCjB,EAASkB,MAAMC,QAAO,4BAAAC,OAA+BN,EAAGM,YAAAA,OAAWL,EAAI,uFAAAK,OAAsFJ,EAAW,MAEvKrB,KAAK0B,yBAA2B3B,EAAI4B,MAAMC,YAAYvB,GAEvDE,OAAOC,QAAQ,CACbqB,KAAM,OACNC,QAAS,YACTC,MAAO,UACPC,MAAO,UACPC,KAAM,OACNC,IAAK,OACLC,MAAO,QACPC,iBAAkB,qBAClBC,kBAAmB,sBACnBC,eAAgB,qBACexB,KAAIyB,IAAA,IAAEC,EAAWC,GAAQF,EAAA,OACxDlC,EAASqC,iBACPF,EACCxC,KAAKyC,GAA2BE,KAAK3C,MACvC,IAEHA,KAAK4C,eAAiBvC,CACxB,CAKAwB,IAAAA,GACE7B,KAAK6C,sBACP,CAOAC,SAAAA,CAAUC,GACR,IAAK/C,KAAKgD,UACR,OAEF,MAAMC,EAA4B,QAAnBjD,KAAKkD,UAAsBlD,KAAKmD,WAAanD,KAAKoD,QACjE,GAAIL,EAAEM,WAAWJ,EAEfjD,KAAKiD,EAAOF,EAAEM,UAAUN,OACnB,MAAIA,EAAEM,WAAWrD,KAAKsD,mBAAoBP,EAAEQ,UAAWR,EAAES,QAI9D,OAFAxD,KAAKA,KAAKsD,gBAAgBP,EAAEM,UAAUN,EAGxC,CACAA,EAAEU,2BACFV,EAAEW,iBACEX,EAAEM,SAAW,IAAMN,EAAEM,SAAW,IAElCrD,KAAK2D,mBAAoB,EACzB3D,KAAK4D,kBACL5D,KAAK6D,2BAEL7D,KAAKC,QAAUD,KAAKC,OAAO6D,kBAE/B,CAQAC,OAAAA,CAAQhB,IACD/C,KAAKgD,WAAahD,KAAKgE,WAAahE,KAAK2D,kBAC5C3D,KAAKgE,WAAY,EAGfjB,EAAEM,WAAWrD,KAAKiE,gBAAkBlB,EAAEQ,SAAWR,EAAES,WAErDxD,KAAKA,KAAKiE,cAAclB,EAAEM,UAAUN,GAItCA,EAAEU,2BACFV,EAAEW,iBACF1D,KAAKC,QAAUD,KAAKC,OAAO6D,mBAC7B,CAMAI,OAAAA,CAA8DnB,GAC5D,MAAMoB,EAAYnE,KAAKmE,UAGvB,GAFAnE,KAAKmE,WAAY,EACjBpB,GAAKA,EAAEqB,mBACFpE,KAAKgD,UACR,OAEF,MAAMqB,EAAgBA,KACpBrE,KAAKsE,qBACLtE,KAAKuE,KAAKC,GACNxE,KAAKC,SACPD,KAAKC,OAAOsE,KAAK,eAAgB,CAAEE,OAAQzE,OAC3CA,KAAKC,OAAO6D,mBACd,EAEF,GAAkC,KAA9B9D,KAAK4C,eAAe3B,MAGtB,OAFAjB,KAAK0E,OAAS,QACdL,IAIF,MAAMM,EAAW3E,KAAK4E,oBAClB5E,KAAK4C,eAAe3B,OACpB4D,aACFC,EAAY9E,KAAK+E,MAAMC,OACvBC,EAAgBN,EAASK,OACzBE,EAAiBlF,KAAKkF,eACtBC,EAAenF,KAAKmF,aACpBC,EAAYF,IAAmBC,EACjC,IAAIE,EACFC,EAEAC,EACAC,EAFAC,EAAWR,EAAgBH,EAI7B,MAAMY,EAAoB1F,KAAK2F,8BAC7B3F,KAAK4C,eAAesC,eACpBlF,KAAK4C,eAAeuC,aACpBnF,KAAK4C,eAAe3B,OAEhB2E,EAAaV,EAAiBQ,EAAkBR,eAElDE,GACFE,EAActF,KAAK+E,MAAMc,MAAMX,EAAgBC,GAC/CM,GAAYN,EAAeD,GAClBD,EAAgBH,IAEvBQ,EADEM,EACY5F,KAAK+E,MAAMc,MAAMV,EAAeM,EAAUN,GAE1CnF,KAAK+E,MAAMc,MACvBX,EACAA,EAAiBO,IAIvB,MAAMK,EAAenB,EAASkB,MAC5BH,EAAkBP,aAAeM,EACjCC,EAAkBP,cAiCpB,GA/BIG,GAAeA,EAAYN,SACzBc,EAAad,SAIfK,EAAcrF,KAAK+F,mBACjBb,EACAA,EAAiB,GACjB,GAGFG,EAAcS,EAAahF,KACzB,IAGEuE,EAAa,MAGfD,GACFG,EAAaL,EACbM,EAAWL,GACFS,GAETL,EAAaJ,EAAeG,EAAYN,OACxCQ,EAAWL,IAEXI,EAAaJ,EACbK,EAAWL,EAAeG,EAAYN,QAExChF,KAAKgG,kBAAkBT,EAAYC,IAEjCM,EAAad,OAAQ,CACvB,MAAMiB,cAAEA,GAAkBC,IAExB/B,GACA2B,EAAaK,KAAK,MAAQF,EAAcG,aACvCC,EAAOC,wBAERjB,EAAcY,EAAcM,iBAE9BvG,KAAKwG,oBAAoBV,EAAcZ,EAAgBG,EACzD,CACAhB,GACF,CAKAoC,kBAAAA,GACEzG,KAAK2D,mBAAoB,CAC3B,CAKA+C,gBAAAA,GACE1G,KAAK2D,mBAAoB,CAC3B,CAEAgD,mBAAAA,CAAmBC,GAA+B,IAA9BnC,OAAEA,GAA0BmC,EAC9C,MAAM1B,eAAEA,EAAcC,aAAEA,GAAiBV,EACzCzE,KAAK6G,iBAAmB3B,EACxBlF,KAAK8G,eAAiB3B,EACtBnF,KAAK+G,wBACP,CAKA9E,IAAAA,GACE,GAAIjC,KAAKkF,iBAAmBlF,KAAKmF,aAE/B,OAEF,MAAMc,cAAEA,GAAkBC,IAC1BD,EAAcG,WAAapG,KAAKgH,kBAC3BX,EAAOC,sBAOVL,EAAcM,qBAAkBU,EANhChB,EAAcM,gBAAkBvG,KAAK+F,mBACnC/F,KAAKkF,eACLlF,KAAKmF,cACL,GAKJnF,KAAKgE,WAAY,CACnB,CAKA7B,KAAAA,GACEnC,KAAKmE,WAAY,CACnB,CASA+C,qBAAAA,CAAsBC,EAAmBC,GACvC,IACEC,EADEC,EAAoBtH,KAAKuH,mBAAmBJ,GAOhD,OAJIC,EAAY,IACdC,EAAQrH,KAAKwH,aAAaL,GAAWC,EAAY,GACjDE,GAAqBD,EAAMjG,KAAOiG,EAAMI,OAEnCH,CACT,CAQAI,mBAAAA,CAAoB3E,EAAkB4E,GACpC,MAAMC,EAAgB5H,KAAK6H,uBAAuB9E,EAAG4E,GACnDG,EAAiB9H,KAAK+H,oBAAoBH,GAC1CT,EAAYW,EAAeX,UAE7B,GACEA,IAAcnH,KAAKgI,WAAWhD,OAAS,GACvCjC,EAAES,SACY,KAAdT,EAAEM,QAGF,OAAOrD,KAAK+E,MAAMC,OAAS4C,EAE7B,MAAMR,EAAYU,EAAeV,UAC/BE,EAAoBtH,KAAKkH,sBAAsBC,EAAWC,GAC1Da,EAAmBjI,KAAKkI,gBAAgBf,EAAY,EAAGG,GAEzD,OADoBtH,KAAKgI,WAAWb,GAAWtB,MAAMuB,GAEnCpC,OAChBiD,EACA,EACAjI,KAAKmI,qBAAqBhB,EAE9B,CASAU,sBAAAA,CAAuB9E,EAAkB4E,GACvC,OAAI5E,EAAEqF,UAAYpI,KAAKkF,iBAAmBlF,KAAKmF,cAAgBwC,EACtD3H,KAAKmF,aAELnF,KAAKkF,cAEhB,CAOAmD,iBAAAA,CAAkBtF,EAAkB4E,GAClC,MAAMC,EAAgB5H,KAAK6H,uBAAuB9E,EAAG4E,GACnDG,EAAiB9H,KAAK+H,oBAAoBH,GAC1CT,EAAYW,EAAeX,UAC7B,GAAkB,IAAdA,GAAmBpE,EAAES,SAAyB,KAAdT,EAAEM,QAEpC,OAAQuE,EAEV,MAAMR,EAAYU,EAAeV,UAC/BE,EAAoBtH,KAAKkH,sBAAsBC,EAAWC,GAC1Da,EAAmBjI,KAAKkI,gBAAgBf,EAAY,EAAGG,GACvDgB,EAAmBtI,KAAKgI,WAAWb,GAAWtB,MAAM,EAAGuB,GACvDe,EAAuBnI,KAAKmI,qBAAqBhB,EAAY,GAE/D,OACGnH,KAAKgI,WAAWb,EAAY,GAAGnC,OAChCiD,EACAK,EAAiBtD,QAChB,EAAImD,EAET,CAMAD,eAAAA,CAAgBf,EAAmBM,GACjC,MAAMc,EAAOvI,KAAKgI,WAAWb,GAE7B,IAEEqB,EACAC,EAHEC,EADe1I,KAAKuH,mBAAmBJ,GAEzCwB,EAAc,EAIhB,IAAK,IAAIC,EAAI,EAAGC,EAAON,EAAKvD,OAAQ4D,EAAIC,EAAMD,IAG5C,GAFAJ,EAAYxI,KAAKwH,aAAaL,GAAWyB,GAAGnB,MAC5CiB,GAAsBF,EAClBE,EAAqBjB,EAAO,CAC9BgB,GAAa,EACb,MAAMK,EAAWJ,EAAqBF,EACpCO,EAAYL,EACZM,EAAqBC,KAAKC,IAAIJ,EAAWrB,GAG3CkB,EAFwBM,KAAKC,IAAIH,EAAYtB,GAETuB,EAAqBJ,EAAIA,EAAI,EACjE,KACF,CAQF,OAJKH,IACHE,EAAcJ,EAAKvD,OAAS,GAGvB2D,CACT,CAMAQ,cAAAA,CAAepG,GAEX/C,KAAKkF,gBAAkBlF,KAAK+E,MAAMC,QAClChF,KAAKmF,cAAgBnF,KAAK+E,MAAMC,QAIlChF,KAAKoJ,oBAAoB,OAAQrG,EACnC,CAMAsG,YAAAA,CAAatG,GACiB,IAAxB/C,KAAKkF,gBAA8C,IAAtBlF,KAAKmF,cAGtCnF,KAAKoJ,oBAAoB,KAAMrG,EACjC,CAOAqG,mBAAAA,CAAoBlG,EAA0BH,GAC5C,MAAMuG,EAAStJ,KAAIyB,MAAAA,OAAOyB,EAAS,iBACjCH,EACA/C,KAAKuJ,sBAAwBC,GAO/B,GALIzG,EAAEqF,SACJpI,KAAKyJ,oBAAoBH,GAEzBtJ,KAAK0J,uBAAuBJ,GAEf,IAAXA,EAAc,CAChB,MAAMK,EAAM3J,KAAK4J,KAAK5E,OACtBhF,KAAKkF,eAAiB2E,EAAS,EAAG7J,KAAKkF,eAAgByE,GACvD3J,KAAKmF,aAAe0E,EAAS,EAAG7J,KAAKmF,aAAcwE,GAGnD3J,KAAK6C,uBACL7C,KAAK8J,oBACL9J,KAAK+J,wBACL/J,KAAKgK,iBACP,CACF,CAMAP,mBAAAA,CAAoBH,GAClB,MAAMW,EACJjK,KAAKuJ,sBAAwBW,EACzBlK,KAAKkF,eAAiBoE,EACtBtJ,KAAKmF,aAAemE,EAM1B,OALAtJ,KAAKmK,8BACHnK,KAAKkF,eACLlF,KAAKmF,aACL8E,GAEgB,IAAXX,CACT,CAMAI,sBAAAA,CAAuBJ,GAQrB,OAPIA,EAAS,GACXtJ,KAAKkF,gBAAkBoE,EACvBtJ,KAAKmF,aAAenF,KAAKkF,iBAEzBlF,KAAKmF,cAAgBmE,EACrBtJ,KAAKkF,eAAiBlF,KAAKmF,cAEX,IAAXmE,CACT,CAMAc,cAAAA,CAAerH,GACe,IAAxB/C,KAAKkF,gBAA8C,IAAtBlF,KAAKmF,cAGtCnF,KAAKqK,uBAAuB,OAAQtH,EACtC,CAQAuH,KAAAA,CACEvH,EACAwH,EACArH,GAEA,IAAIsH,EACJ,GAAIzH,EAAE0H,OACJD,EAAWxK,KAAIyB,mBAAAA,OAAoByB,IAAalD,KAAKuK,QAChD,KAAIxH,EAAES,SAAyB,KAAdT,EAAEM,SAAgC,KAAdN,EAAEM,QAI5C,OADArD,KAAKuK,IAAuB,SAAdrH,GAAwB,EAAI,GACnC,EAHPsH,EAAWxK,KAAIyB,mBAAAA,OAAoByB,IAAalD,KAAKuK,GAIvD,CACA,YAAwB,IAAbC,GAA4BxK,KAAKuK,KAAUC,IACpDxK,KAAKuK,GAAQC,GACN,EAGX,CAKAE,SAAAA,CAAU3H,EAAkBwH,GAC1B,OAAOvK,KAAKsK,MAAMvH,EAAGwH,EAAM,OAC7B,CAKAI,UAAAA,CAAW5H,EAAkBwH,GAC3B,OAAOvK,KAAKsK,MAAMvH,EAAGwH,EAAM,QAC7B,CAMAK,0BAAAA,CAA2B7H,GACzB,IAAI8H,GAAS,EAYb,OAXA7K,KAAKuJ,oBAAsBW,EAKzBlK,KAAKmF,eAAiBnF,KAAKkF,gBACH,IAAxBlF,KAAKkF,iBAEL2F,EAAS7K,KAAK0K,UAAU3H,EAAG,mBAE7B/C,KAAKmF,aAAenF,KAAKkF,eAClB2F,CACT,CAMAC,uBAAAA,CAAwB/H,GACtB,OACE/C,KAAKuJ,sBAAwBC,GAC7BxJ,KAAKkF,iBAAmBlF,KAAKmF,aAEtBnF,KAAK0K,UAAU3H,EAAG,gBACQ,IAAxB/C,KAAKkF,gBACdlF,KAAKuJ,oBAAsBW,EACpBlK,KAAK0K,UAAU3H,EAAG,wBAFpB,CAIT,CAMAgI,eAAAA,CAAgBhI,GAEZ/C,KAAKkF,gBAAkBlF,KAAK+E,MAAMC,QAClChF,KAAKmF,cAAgBnF,KAAK+E,MAAMC,QAIlChF,KAAKqK,uBAAuB,QAAStH,EACvC,CAOAsH,sBAAAA,CAAuBnH,EAA6BH,GAClD,MAAMiI,EAAU,aAAAvJ,OAAgByB,GAASzB,OACvCsB,EAAEqF,SAAW,YAAc,gBAE7BpI,KAAKiL,sBAAwB,EACzBjL,KAAKgL,GAAYjI,KAGnB/C,KAAK6C,uBACL7C,KAAK8J,oBACL9J,KAAK+J,wBACL/J,KAAKgK,kBAET,CAMAkB,wBAAAA,CAAyBnI,GACvB,OACE/C,KAAKuJ,sBAAwBW,GAC7BlK,KAAKkF,iBAAmBlF,KAAKmF,aAEtBnF,KAAK2K,WAAW5H,EAAG,kBACjB/C,KAAKmF,eAAiBnF,KAAK+E,MAAMC,QAC1ChF,KAAKuJ,oBAAsBC,EACpBxJ,KAAK2K,WAAW5H,EAAG,sBAFrB,CAIT,CAMAoI,2BAAAA,CAA4BpI,GAC1B,IAAIqI,GAAU,EASd,OARApL,KAAKuJ,oBAAsBC,EAEvBxJ,KAAKkF,iBAAmBlF,KAAKmF,cAC/BiG,EAAUpL,KAAK2K,WAAW5H,EAAG,kBAC7B/C,KAAKmF,aAAenF,KAAKkF,gBAEzBlF,KAAKkF,eAAiBlF,KAAKmF,aAEtBiG,CACT"}