UNPKG

@infinite-canvas-tutorial/webcomponents

Version:
293 lines (291 loc) 11.7 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { html, css, LitElement } from 'lit'; import { customElement, query, state } from 'lit/decorators.js'; import { consume } from '@lit/context'; import { Canvas, ComputedBounds, ComputedCamera, Pen, Text, UI, inferXYWidthHeight, } from '@infinite-canvas-tutorial/ecs'; import { v4 as uuidv4 } from 'uuid'; import { apiContext, appStateContext } from '../context'; let TextEditor = class TextEditor extends LitElement { constructor() { super(...arguments); this.binded = false; this.handleBlur = (event) => { const target = event.target; const content = target.value; if (content.trim() !== '' && content.trim() !== this.node.content) { this.api.runAtNextTick(() => { const entity = this.api.getEntity(this.node); if (!entity) { this.api.updateNode(Object.assign(Object.assign({}, this.node), { content, visibility: 'visible' })); } else { this.api.updateNode(this.node, { content, visibility: 'visible', }); } this.api.record(); }); } else { this.api.runAtNextTick(() => { this.api.updateNode(this.node, { visibility: 'visible', }, false); }); } const isPenSelect = this.appState.penbarSelected === Pen.SELECT; if (!isPenSelect) { this.api.setAppState({ penbarSelected: Pen.SELECT, }); } this.editable.style.display = 'none'; this.editable.value = ''; }; this.handleDblclick = (event) => { const isPenSelect = this.appState.penbarSelected === Pen.SELECT; const isPenText = this.appState.penbarSelected === Pen.TEXT; if (!isPenSelect && !isPenText) { return; } const { x: vx, y: vy } = this.api.client2Viewport({ x: event.clientX, y: event.clientY, }); const { x: wx, y: wy } = this.api.viewport2Canvas({ x: vx, y: vy, }); const entities = this.api.elementsFromBBox(wx, wy, wx, wy); const entity = entities.find((e) => !e.has(UI)); this.node = undefined; if (isPenSelect && entity && entity.has(Text)) { // Edit the existing text node. const node = this.api.getNodeByEntity(entity); const { obb } = entity.read(ComputedBounds); this.node = node; this.editable.value = node.content; // this.editable.style.color = node.fill; // this.editable.style.opacity = node.opacity && node.opacity.toString(); // this.editable.style.textAlign = node.textAlign; // TODO: support textBaseline. // this.editable.style.textBaseline = node.textBaseline; // this.editable.style.letterSpacing = // node.letterSpacing && node.letterSpacing.toString(); this.updateTextareaStyle(node); this.editable.style.width = `${obb.width}px`; this.editable.style.height = `${obb.height}px`; this.api.deselectNodes([node]); this.api.unhighlightNodes([node]); // Hide original text node for now. this.api.updateNode(node, { visibility: 'hidden', }, false); } if (isPenText) { // Create a new text node if blank area is clicked. this.node = Object.assign({ id: uuidv4(), type: 'text', content: '', anchorX: wx, anchorY: wy, zIndex: 0 }, this.appState.penbarText); inferXYWidthHeight(this.node); this.updateTextareaStyle(this.appState.penbarText); } if (!this.node) { return; } this.updatePositionWithCamera(); this.editable.style.display = 'inline-block'; this.editable.style.transformOrigin = `left top`; this.editable.focus(); }; this.handleKeyDown = (event) => { // Prevent triggering arrow keys. event.stopPropagation(); if (event.key === 'Escape') { this.editable.blur(); // } else if ( // event.key === KEYS.TAB || // (event[KEYS.CTRL_OR_CMD] && // (event.code === CODES.BRACKET_LEFT || // event.code === CODES.BRACKET_RIGHT)) // ) { // event.preventDefault(); // if (event.isComposing) { // // input keyboard // return; // } else if (event.shiftKey || event.code === CODES.BRACKET_LEFT) { // outdent(); // } else { // indent(); // } // // We must send an input event to resize the element } }; this.handleInput = (event) => { const target = event.target; const content = target.value; const attributes = Object.assign(Object.assign({}, this.node), { content }); const { minX, minY, maxX, maxY } = Text.getGeometryBounds(attributes); const width = maxX - minX; const height = maxY - minY; this.editable.style.width = `${width}px`; this.editable.style.height = `${height}px`; }; this.handleWheel = (event) => { event.preventDefault(); event.stopPropagation(); const $canvas = this.api.getCanvasElement(); if ($canvas) { const newWheelEvent = new WheelEvent('wheel', { deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: event.deltaZ, deltaMode: event.deltaMode, clientX: event.clientX, clientY: event.clientY, screenX: event.screenX, screenY: event.screenY, ctrlKey: event.ctrlKey, shiftKey: event.shiftKey, altKey: event.altKey, metaKey: event.metaKey, bubbles: true, cancelable: true, }); $canvas.dispatchEvent(newWheelEvent); } }; } disconnectedCallback() { super.disconnectedCallback(); if (this.api && this.api.getCanvas().has(Canvas)) { const $canvas = this.api.getCanvasElement(); $canvas === null || $canvas === void 0 ? void 0 : $canvas.removeEventListener('dblclick', this.handleDblclick); } } updatePositionWithCamera() { if (this.node) { const camera = this.api.getCamera(); const { zoom } = camera.read(ComputedCamera); const { x, y } = this.api.canvas2Viewport({ x: this.node.x, y: this.node.y, }); this.editable.style.left = `${x}px`; this.editable.style.top = `${y}px`; this.editable.style.transform = `scale(${zoom})`; } } updateTextareaStyle(node) { const { fontFamily, fontSize, fontWeight, fontStyle, fontVariant, fill, opacity, textAlign, textBaseline, letterSpacing, } = node; if (fontFamily) { this.editable.style.fontFamily = fontFamily; } if (fontSize) { this.editable.style.fontSize = `${fontSize}px`; } if (fontWeight) { this.editable.style.fontWeight = fontWeight.toString(); } if (fontStyle) { this.editable.style.fontStyle = fontStyle; } if (fontVariant) { this.editable.style.fontVariant = fontVariant; } if (fill) { this.editable.style.color = fill; } if (opacity) { this.editable.style.opacity = opacity.toString(); } if (textAlign) { this.editable.style.textAlign = textAlign; } if (textBaseline) { // this.editable.style.textBaseline = textBaseline; } if (letterSpacing) { this.editable.style.letterSpacing = letterSpacing.toString(); } } render() { var _a; // FIXME: wait for the element to be ready. if (((_a = this.api) === null || _a === void 0 ? void 0 : _a.element) && !this.binded) { const $canvas = this.api.getCanvasElement(); $canvas === null || $canvas === void 0 ? void 0 : $canvas.addEventListener('dblclick', this.handleDblclick); this.binded = true; } if (this.prevCameraZoom !== this.appState.cameraZoom || this.prevCameraX !== this.appState.cameraX || this.prevCameraY !== this.appState.cameraY) { this.updatePositionWithCamera(); this.prevCameraZoom = this.appState.cameraZoom; this.prevCameraX = this.appState.cameraX; this.prevCameraY = this.appState.cameraY; } return html `<textarea dir="auto" tabindex="0" wrap="off" @blur=${this.handleBlur} @input=${this.handleInput} @keydown=${this.handleKeyDown} @wheel=${this.handleWheel} ></textarea>`; } }; // @see https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/wysiwyg/textWysiwyg.tsx#L309 TextEditor.styles = css ` :host { position: absolute; } textarea { position: absolute; display: none; min-height: 1em; backface-visibility: hidden; margin: 0; padding: 0; border: 0; outline: 0; resize: none; background: transparent; overflow: hidden; overflow-wrap: break-word; box-sizing: content-box; font-size: 16px; font-family: system-ui; font-weight: normal; font-style: normal; font-variant: normal; letter-spacing: 0; width: auto; min-width: 1em; } textarea.wheel-transparent { pointer-events: none; } `; __decorate([ consume({ context: appStateContext, subscribe: true }) ], TextEditor.prototype, "appState", void 0); __decorate([ consume({ context: apiContext, subscribe: true }) ], TextEditor.prototype, "api", void 0); __decorate([ query('textarea') ], TextEditor.prototype, "editable", void 0); __decorate([ state() ], TextEditor.prototype, "node", void 0); TextEditor = __decorate([ customElement('ic-spectrum-text-editor') ], TextEditor); export { TextEditor }; //# sourceMappingURL=text-editor.js.map