UNPKG

fabric

Version:

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

287 lines (286 loc) 11.7 kB
import { _defineProperty } from "../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs"; import { CHANGED, NONE } from "../../constants.mjs"; import { Point } from "../../Point.mjs"; import { getDocumentFromElement } from "../../util/dom_misc.mjs"; import { cloneStyles } from "../../util/internals/cloneStyles.mjs"; import { setStyle } from "../../util/internals/dom_style.mjs"; //#region src/shapes/IText/DraggableTextDelegate.ts /** * #### Dragging IText/Textbox Lifecycle * - {@link start} is called from `mousedown` {@link IText#_mouseDownHandler} and determines if dragging should start by testing {@link isPointerOverSelection} * - if true `mousedown` {@link IText#_mouseDownHandler} is blocked to keep selection * - if the pointer moves, canvas fires numerous mousemove {@link Canvas#_onMouseMove} that we make sure **aren't** prevented ({@link IText#shouldStartDragging}) in order for the window to start a drag session * - once/if the session starts canvas calls {@link onDragStart} on the active object to determine if dragging should occur * - canvas fires relevant drag events that are handled by the handlers defined in this scope * - {@link end} is called from `mouseup` {@link IText#mouseUpHandler}, blocking IText default click behavior * - in case the drag session didn't occur, {@link end} handles a click, since logic to do so was blocked during `mousedown` */ var DraggableTextDelegate = class { constructor(target) { _defineProperty(this, "target", void 0); _defineProperty(this, "__mouseDownInPlace", false); _defineProperty(this, "__dragStartFired", false); _defineProperty(this, "__isDraggingOver", false); _defineProperty(this, "__dragStartSelection", void 0); _defineProperty(this, "__dragImageDisposer", void 0); _defineProperty(this, "_dispose", void 0); this.target = target; const disposers = [ this.target.on("dragenter", this.dragEnterHandler.bind(this)), this.target.on("dragover", this.dragOverHandler.bind(this)), this.target.on("dragleave", this.dragLeaveHandler.bind(this)), this.target.on("dragend", this.dragEndHandler.bind(this)), this.target.on("drop", this.dropHandler.bind(this)) ]; this._dispose = () => { disposers.forEach((d) => d()); this._dispose = void 0; }; } isPointerOverSelection(e) { const target = this.target; const newSelection = target.getSelectionStartFromPointer(e); return target.isEditing && newSelection >= target.selectionStart && newSelection <= target.selectionEnd && target.selectionStart < target.selectionEnd; } /** * @public override this method to disable dragging and default to mousedown logic */ start(e) { return this.__mouseDownInPlace = this.isPointerOverSelection(e); } /** * @public override this method to disable dragging without discarding selection */ isActive() { return this.__mouseDownInPlace; } /** * Ends interaction and sets cursor in case of a click * @returns true if was active */ end(e) { const active = this.isActive(); if (active && !this.__dragStartFired) { this.target.setCursorByClick(e); this.target.initDelayedCursor(true); } this.__mouseDownInPlace = false; this.__dragStartFired = false; this.__isDraggingOver = false; return active; } getDragStartSelection() { return this.__dragStartSelection; } /** * Override to customize the drag image * https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage */ setDragImage(e, { selectionStart, selectionEnd }) { var _e$dataTransfer; const target = this.target; const canvas = target.canvas; const flipFactor = new Point(target.flipX ? -1 : 1, target.flipY ? -1 : 1); const boundaries = target._getCursorBoundaries(selectionStart); const pos = new Point(boundaries.left + boundaries.leftOffset, boundaries.top + boundaries.topOffset).multiply(flipFactor).transform(target.calcTransformMatrix()); const diff = canvas.getScenePoint(e).subtract(pos); const retinaScaling = target.getCanvasRetinaScaling(); const bbox = target.getBoundingRect(); const correction = pos.subtract(new Point(bbox.left, bbox.top)); const vpt = canvas.viewportTransform; const offset = correction.add(diff).transform(vpt, true); const bgc = target.backgroundColor; const styles = cloneStyles(target.styles); target.backgroundColor = ""; const styleOverride = { stroke: "transparent", fill: "transparent", textBackgroundColor: "transparent" }; target.setSelectionStyles(styleOverride, 0, selectionStart); target.setSelectionStyles(styleOverride, selectionEnd, target.text.length); target.dirty = true; const dragImage = target.toCanvasElement({ enableRetinaScaling: canvas.enableRetinaScaling, viewportTransform: true }); target.backgroundColor = bgc; target.styles = styles; target.dirty = true; setStyle(dragImage, { position: "fixed", left: `${-dragImage.width}px`, border: NONE, width: `${dragImage.width / retinaScaling}px`, height: `${dragImage.height / retinaScaling}px` }); this.__dragImageDisposer && this.__dragImageDisposer(); this.__dragImageDisposer = () => { dragImage.remove(); }; getDocumentFromElement(e.target || this.target.hiddenTextarea).body.appendChild(dragImage); (_e$dataTransfer = e.dataTransfer) === null || _e$dataTransfer === void 0 || _e$dataTransfer.setDragImage(dragImage, offset.x, offset.y); } /** * @returns {boolean} determines whether {@link target} should/shouldn't become a drag source */ onDragStart(e) { this.__dragStartFired = true; const target = this.target; const active = this.isActive(); if (active && e.dataTransfer) { const selection = this.__dragStartSelection = { selectionStart: target.selectionStart, selectionEnd: target.selectionEnd }; const value = target._text.slice(selection.selectionStart, selection.selectionEnd).join(""); const data = { text: target.text, value, ...selection }; e.dataTransfer.setData("text/plain", value); e.dataTransfer.setData("application/fabric", JSON.stringify({ value, styles: target.getSelectionStyles(selection.selectionStart, selection.selectionEnd, true) })); e.dataTransfer.effectAllowed = "copyMove"; this.setDragImage(e, data); } target.abortCursorAnimation(); return active; } /** * use {@link targetCanDrop} to respect overriding * @returns {boolean} determines whether {@link target} should/shouldn't become a drop target */ canDrop(e) { if (this.target.editable && !this.target.getActiveControl() && !e.defaultPrevented) { if (this.isActive() && this.__dragStartSelection) { const index = this.target.getSelectionStartFromPointer(e); const dragStartSelection = this.__dragStartSelection; return index < dragStartSelection.selectionStart || index > dragStartSelection.selectionEnd; } return true; } return false; } /** * in order to respect overriding {@link IText#canDrop} we call that instead of calling {@link canDrop} directly */ targetCanDrop(e) { return this.target.canDrop(e); } dragEnterHandler({ e }) { const canDrop = this.targetCanDrop(e); if (!this.__isDraggingOver && canDrop) this.__isDraggingOver = true; } dragOverHandler(ev) { const { e } = ev; const canDrop = this.targetCanDrop(e); if (!this.__isDraggingOver && canDrop) this.__isDraggingOver = true; else if (this.__isDraggingOver && !canDrop) this.__isDraggingOver = false; if (this.__isDraggingOver) { e.preventDefault(); ev.canDrop = true; ev.dropTarget = this.target; } } dragLeaveHandler() { if (this.__isDraggingOver || this.isActive()) this.__isDraggingOver = false; } /** * Override the `text/plain | application/fabric` types of {@link DragEvent#dataTransfer} * in order to change the drop value or to customize styling respectively, by listening to the `drop:before` event * https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#performing_a_drop */ dropHandler(ev) { var _e$dataTransfer2; const { e } = ev; const didDrop = e.defaultPrevented; this.__isDraggingOver = false; e.preventDefault(); let insert = (_e$dataTransfer2 = e.dataTransfer) === null || _e$dataTransfer2 === void 0 ? void 0 : _e$dataTransfer2.getData("text/plain"); if (insert && !didDrop) { const target = this.target; const canvas = target.canvas; let insertAt = target.getSelectionStartFromPointer(e); const { styles } = e.dataTransfer.types.includes("application/fabric") ? JSON.parse(e.dataTransfer.getData("application/fabric")) : {}; const trailing = insert[Math.max(0, insert.length - 1)]; const selectionStartOffset = 0; if (this.__dragStartSelection) { const selectionStart = this.__dragStartSelection.selectionStart; const selectionEnd = this.__dragStartSelection.selectionEnd; if (insertAt > selectionStart && insertAt <= selectionEnd) insertAt = selectionStart; else if (insertAt > selectionEnd) insertAt -= selectionEnd - selectionStart; target.removeChars(selectionStart, selectionEnd); delete this.__dragStartSelection; } if (target._reNewline.test(trailing) && (target._reNewline.test(target._text[insertAt]) || insertAt === target._text.length)) insert = insert.trimEnd(); ev.didDrop = true; ev.dropTarget = target; target.insertChars(insert, styles, insertAt); canvas.setActiveObject(target); target.enterEditing(e); target.selectionStart = Math.min(insertAt + selectionStartOffset, target._text.length); target.selectionEnd = Math.min(target.selectionStart + insert.length, target._text.length); target.hiddenTextarea.value = target.text; target._updateTextarea(); target.hiddenTextarea.focus(); target.fire(CHANGED, { index: insertAt + selectionStartOffset, action: "drop" }); canvas.fire("text:changed", { target }); canvas.contextTopDirty = true; canvas.requestRenderAll(); } } /** * fired only on the drag source after drop (if occurred) * handle changes to the drag source in case of a drop on another object or a cancellation * https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#finishing_a_drag */ dragEndHandler({ e }) { if (this.isActive() && this.__dragStartFired) { if (this.__dragStartSelection) { var _e$dataTransfer3; const target = this.target; const canvas = this.target.canvas; const { selectionStart, selectionEnd } = this.__dragStartSelection; const dropEffect = ((_e$dataTransfer3 = e.dataTransfer) === null || _e$dataTransfer3 === void 0 ? void 0 : _e$dataTransfer3.dropEffect) || "none"; if (dropEffect === "none") { target.selectionStart = selectionStart; target.selectionEnd = selectionEnd; target._updateTextarea(); target.hiddenTextarea.focus(); } else { target.clearContextTop(); if (dropEffect === "move") { target.removeChars(selectionStart, selectionEnd); target.selectionStart = target.selectionEnd = selectionStart; target.hiddenTextarea && (target.hiddenTextarea.value = target.text); target._updateTextarea(); target.fire(CHANGED, { index: selectionStart, action: "dragend" }); canvas.fire("text:changed", { target }); canvas.requestRenderAll(); } target.exitEditing(); } } } this.__dragImageDisposer && this.__dragImageDisposer(); delete this.__dragImageDisposer; delete this.__dragStartSelection; this.__isDraggingOver = false; } dispose() { this._dispose && this._dispose(); } }; //#endregion export { DraggableTextDelegate }; //# sourceMappingURL=DraggableTextDelegate.mjs.map