diff --git a/CHANGELOG.md b/CHANGELOG.md index 73d6115b7..ab3d4e082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,63 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +- ts fixes + +### 9.3.14 (2024-07-16) + +- Fix shadow + corner radius for images +- Support `fillRule` for `Konva.Shape` on hit graph + +### 9.3.13 (2024-07-05) + +- Fallback for `Konva.Text.measureSize()` when 2d context doesn't return full data + +### 9.3.12 (2024-06-20) + +- Fix stopped transforming when it was triggered by multi-touch +- Fix svg `path.getPointAtLength()` calculations in some cases +- Fix `shape.getClientRect()` when any of parents is cached + +### 9.3.11 (2024-05-23) + +- Fix chrome clear canvas issue +- Typescript fixes + +### 9.3.9 (2024-05-20) + +- Fix underline and line-through for `Konva.Text` when `Konva._fixTextRendering = true` + +### 9.3.8 (2024-05-15) + +- Fix click events fires on stage +- Temporary `Konva._fixTextRendering = true` flag to fix inconsistent text + +### 9.3.6 (2024-03-04) + +- Fix transformer bug to enable hit graph back + +### 9.3.5 (2024-03-04) + +- `tranformer` event will be triggered AFTER all data of transformer is updated +- Improve performance of transformer + +### 9.3.4 (2024-03-03) + +- Fix clipping with zero size + +### 9.3.3 (2024-02-09) + +- Another fix for exporting buffered shapes + +### 9.3.2 (2024-01-26) + +- Fix large memory usage on node export + +### 9.3.1 (2024-01-17) + +- Fix Pixelate filter work/fix caching size +- Fix node export when large buffer canvas is used + ### 9.3.0 (2023-12-20) - New attribute `rotateLineVisible` for `Konva.Transformer` to show/hide rotate line diff --git a/README.md b/README.md index f60a2363d..48eb3c938 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ Konva works in all modern mobile and desktop browsers. A browser need to be capa At the current moment `Konva` doesn't work in IE11 directly. To make it work you just need to provide some polyfills such as `Array.prototype.find`, `String.prototype.trimLeft`, `String.prototype.trimRight`, `Array.from`. +# Debugging + +The Chrome inspector simply shows the canvas element. To see the Konva objects and their details, install the konva-dev extension at https://github.com/konvajs/konva-devtool. + # Loading and installing Konva Konva supports UMD loading. So you can use all possible variants to load the framework into your project: diff --git a/package.json b/package.json index cfe497577..4f0d123cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "konva", - "version": "9.3.0", + "version": "9.3.14", "author": "Anton Lavrenov", "files": [ "README.md", diff --git a/src/Container.ts b/src/Container.ts index 10f992926..d7bb54940 100644 --- a/src/Container.ts +++ b/src/Container.ts @@ -307,6 +307,7 @@ export abstract class Container< * canvas and redraw every shape inside the container, it should only be used for special situations * because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible * because it performs much better + * nodes with listening set to false will not be detected * @method * @name Konva.Container#getAllIntersections * @param {Object} pos @@ -342,7 +343,7 @@ export abstract class Container< }); this._requestDraw(); } - drawScene(can?: SceneCanvas, top?: Node) { + drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { var layer = this.getLayer()!, canvas = can || (layer && layer.getCanvas()), context = canvas && canvas.getContext(), @@ -361,7 +362,7 @@ export abstract class Container< this._drawCachedSceneCanvas(context); context.restore(); } else { - this._drawChildren('drawScene', canvas, top); + this._drawChildren('drawScene', canvas, top, bufferCanvas); } return this; } @@ -387,12 +388,14 @@ export abstract class Container< } return this; } - _drawChildren(drawMethod, canvas, top) { + _drawChildren(drawMethod, canvas, top, bufferCanvas?) { var context = canvas && canvas.getContext(), clipWidth = this.clipWidth(), clipHeight = this.clipHeight(), clipFunc = this.clipFunc(), - hasClip = (clipWidth && clipHeight) || clipFunc; + hasClip = + (typeof clipWidth === 'number' && typeof clipHeight === 'number') || + clipFunc; const selfCache = top === this; @@ -408,7 +411,7 @@ export abstract class Container< } else { var clipX = this.clipX(); var clipY = this.clipY(); - context.rect(clipX, clipY, clipWidth, clipHeight); + context.rect(clipX || 0, clipY || 0, clipWidth, clipHeight); } context.clip.apply(context, clipArgs); m = transform.copy().invert().getMatrix(); @@ -426,7 +429,7 @@ export abstract class Container< } this.children?.forEach(function (child) { - child[drawMethod](canvas, top); + child[drawMethod](canvas, top, bufferCanvas); }); if (hasComposition) { context.restore(); diff --git a/src/Context.ts b/src/Context.ts index 928aaa8b6..5917a74e4 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -58,6 +58,7 @@ var COMMA = ',', 'putImageData', 'quadraticCurveTo', 'rect', + 'roundRect', 'restore', 'rotate', 'save', @@ -586,6 +587,20 @@ export class Context { rect(x: number, y: number, width: number, height: number) { this._context.rect(x, y, width, height); } + /** + * roundRect function. + * @method + * @name Konva.Context#roundRect + */ + roundRect( + x: number, + y: number, + width: number, + height: number, + radii: number | DOMPointInit | (number | DOMPointInit)[] + ) { + this._context.roundRect(x, y, width, height, radii); + } /** * putImageData function. * @method diff --git a/src/Global.ts b/src/Global.ts index e68715df0..9996e3981 100644 --- a/src/Global.ts +++ b/src/Global.ts @@ -90,6 +90,7 @@ export const Konva = { _mouseDblClickPointerId: null, _touchDblClickPointerId: null, _pointerDblClickPointerId: null, + _fixTextRendering: false, /** * Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device. @@ -155,6 +156,9 @@ export const Konva = { isDragging() { return Konva['DD'].isDragging; }, + isTransforming() { + return Konva['Transformer']?.isTransforming(); + }, /** * returns whether or not a drag and drop operation is ready, but may * not necessarily have started diff --git a/src/Layer.ts b/src/Layer.ts index 5a0871b10..4e5ff9612 100644 --- a/src/Layer.ts +++ b/src/Layer.ts @@ -309,6 +309,7 @@ export class Layer extends Container { * get visible intersection shape. This is the preferred * method for determining if a point intersects a shape or not * also you may pass optional selector parameter to return ancestor of intersected shape + * nodes with listening set to false will not be detected * @method * @name Konva.Layer#getIntersection * @param {Object} pos @@ -469,7 +470,10 @@ export class Layer extends Container { } destroy(): this { - Util.releaseCanvas(this.getNativeCanvasElement(), this.getHitCanvas()._canvas); + Util.releaseCanvas( + this.getNativeCanvasElement(), + this.getHitCanvas()._canvas + ); return super.destroy(); } diff --git a/src/Node.ts b/src/Node.ts index ae4ac02e9..98a2f1eeb 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -116,19 +116,19 @@ type NodeEventMap = GlobalEventHandlersEventMap & { [index: string]: any; }; -export interface KonvaEventObject { +export interface KonvaEventObject { type: string; target: Shape | Stage; evt: EventType; pointerId: number; - currentTarget: Node; + currentTarget: This; cancelBubble: boolean; child?: Node; } export type KonvaEventListener = ( this: This, - ev: KonvaEventObject + ev: KonvaEventObject ) => void; /** @@ -343,10 +343,13 @@ export abstract class Node { return; } - // let's just add 1 pixel extra, // because using Math.floor on x, y position may shift drawing - width += offset * 2 + 1; - height += offset * 2 + 1; + // to avoid shift we need to increase size + // but we better to avoid it, for better filters flows + const extraPaddingX = Math.abs(Math.round(rect.x) - x) > 0.5 ? 1 : 0; + const extraPaddingY = Math.abs(Math.round(rect.y) - y) > 0.5 ? 1 : 0; + width += offset * 2 + extraPaddingX; + height += offset * 2 + extraPaddingY; x -= offset; y -= offset; @@ -446,7 +449,7 @@ export abstract class Node { return this._cache.has(CANVAS); } - abstract drawScene(canvas?: Canvas, top?: Node): void; + abstract drawScene(canvas?: Canvas, top?: Node, bufferCanvas?: Canvas): void; abstract drawHit(canvas?: Canvas, top?: Node): void; /** * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). @@ -811,7 +814,7 @@ export abstract class Node { var targets = evt.target.findAncestors(selector, true, stopNode); for (var i = 0; i < targets.length; i++) { evt = Util.cloneObject(evt); - evt.currentTarget = targets[i]; + evt.currentTarget = targets[i] as any; handler.call(targets[i], evt as any); } }); @@ -1026,7 +1029,10 @@ export abstract class Node { } }); - var dragSkip = !skipDragCheck && !Konva.hitOnDragEnabled && layerUnderDrag; + var dragSkip = + !skipDragCheck && + !Konva.hitOnDragEnabled && + (layerUnderDrag || Konva.isTransforming()); return this.isListening() && this.isVisible() && !dragSkip; } @@ -1929,6 +1935,15 @@ export abstract class Node { }), context = canvas.getContext(); + const bufferCanvas = new SceneCanvas({ + // width and height already multiplied by pixelRatio + // so we need to revert that + // also increase size by x nd y offset to make sure content fits canvas + width: canvas.width / canvas.pixelRatio + Math.abs(x), + height: canvas.height / canvas.pixelRatio + Math.abs(y), + pixelRatio: canvas.pixelRatio, + }); + if (config.imageSmoothingEnabled === false) { context._context.imageSmoothingEnabled = false; } @@ -1938,7 +1953,7 @@ export abstract class Node { context.translate(-1 * x, -1 * y); } - this.drawScene(canvas); + this.drawScene(canvas, undefined, bufferCanvas); context.restore(); return canvas; @@ -3140,6 +3155,8 @@ addGetterSetter(Node, 'listening', true, getBooleanValidator()); /** * get/set listening attr. If you need to determine if a node is listening or not * by taking into account its parents, use the isListening() method + * nodes with listening set to false will not be detected in hit graph + * so they will be ignored in container.getIntersection() method * @name Konva.Node#listening * @method * @param {Boolean} listening Can be true, or false. The default is true. diff --git a/src/Shape.ts b/src/Shape.ts index c327dc121..e34abbfd6 100644 --- a/src/Shape.ts +++ b/src/Shape.ts @@ -26,7 +26,7 @@ export type LineJoin = 'round' | 'bevel' | 'miter'; export type LineCap = 'butt' | 'round' | 'square'; export interface ShapeConfig extends NodeConfig { - fill?: string; + fill?: string | CanvasGradient; fillPatternImage?: HTMLImageElement; fillPatternX?: number; fillPatternY?: number; @@ -128,8 +128,13 @@ function _fillFunc(this: Node, context) { function _strokeFunc(context) { context.stroke(); } -function _fillFuncHit(context) { - context.fill(); +function _fillFuncHit(this: Node, context) { + const fillRule = this.attrs.fillRule; + if (fillRule) { + context.fill(fillRule); + } else { + context.fill(); + } } function _strokeFuncHit(context) { context.stroke(); @@ -469,10 +474,6 @@ export class Shape< // so they use that method with forced fill // it probably will be simpler, then copy/paste the code - // buffer canvas is available only inside the stage - if (!this.getStage()) { - return false; - } // force skip buffer canvas const perfectDrawEnabled = this.attrs.perfectDrawEnabled ?? true; if (!perfectDrawEnabled) { @@ -532,9 +533,22 @@ export class Shape< }; } getClientRect(config: ShapeGetClientRectConfig = {}) { + // if we have a cached parent, it will use cached transform matrix + // but we don't want to that + let hasCachedParent = false; + let parent = this.getParent(); + while (parent) { + if (parent.isCached()) { + hasCachedParent = true; + break; + } + parent = parent.getParent(); + } const skipTransform = config.skipTransform; - const relativeTo = config.relativeTo; + // force relative to stage if we have a cached parent + const relativeTo = + config.relativeTo || (hasCachedParent && this.getStage()) || undefined; const fillRect = this.getSelfRect(); @@ -573,7 +587,7 @@ export class Shape< } return rect; } - drawScene(can?: SceneCanvas, top?: Node) { + drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { // basically there are 3 drawing modes // 1 - simple drawing when nothing is cached. // 2 - when we are caching current @@ -586,7 +600,6 @@ export class Shape< drawFunc = this.getSceneFunc(), hasShadow = this.hasShadow(), stage, - bufferCanvas, bufferContext; var skipBuffer = canvas.isCache; @@ -614,8 +627,8 @@ export class Shape< // if buffer canvas is needed if (this._useBufferCanvas() && !skipBuffer) { stage = this.getStage(); - bufferCanvas = stage.bufferCanvas; - bufferContext = bufferCanvas.getContext(); + const bc = bufferCanvas || stage.bufferCanvas; + bufferContext = bc.getContext(); bufferContext.clear(); bufferContext.save(); bufferContext._applyLineJoin(this); @@ -626,20 +639,14 @@ export class Shape< drawFunc.call(this, bufferContext, this); bufferContext.restore(); - var ratio = bufferCanvas.pixelRatio; + var ratio = bc.pixelRatio; if (hasShadow) { context._applyShadow(this); } context._applyOpacity(this); context._applyGlobalCompositeOperation(this); - context.drawImage( - bufferCanvas._canvas, - 0, - 0, - bufferCanvas.width / ratio, - bufferCanvas.height / ratio - ); + context.drawImage(bc._canvas, 0, 0, bc.width / ratio, bc.height / ratio); } else { context._applyLineJoin(this); @@ -777,7 +784,7 @@ export class Shape< dash: GetSet; dashEnabled: GetSet; dashOffset: GetSet; - fill: GetSet; + fill: GetSet; fillEnabled: GetSet; fillLinearGradientColorStops: GetSet, this>; fillLinearGradientStartPoint: GetSet; @@ -826,7 +833,7 @@ export class Shape< shadowOffsetY: GetSet; shadowOpacity: GetSet; shadowBlur: GetSet; - stroke: GetSet; + stroke: GetSet; strokeEnabled: GetSet; fillAfterStrokeEnabled: GetSet; strokeScaleEnabled: GetSet; diff --git a/src/Stage.ts b/src/Stage.ts index 4740d796e..a2eb87c3e 100644 --- a/src/Stage.ts +++ b/src/Stage.ts @@ -11,7 +11,7 @@ import { _registerNode } from './Global'; import * as PointerEvents from './PointerEvents'; export interface StageConfig extends ContainerConfig { - container: HTMLDivElement | string; + container?: HTMLDivElement | string; } // CONSTANTS @@ -353,6 +353,7 @@ export class Stage extends Container { /** * get visible intersection shape. This is the preferred * method for determining if a point intersects a shape or not + * nodes with listening set to false will not be detected * @method * @name Konva.Stage#getIntersection * @param {Object} pos @@ -507,7 +508,8 @@ export class Stage extends Container { this.setPointersPositions(evt); var targetShape = this._getTargetShape(eventType); - var eventsEnabled = !DD.isDragging || Konva.hitOnDragEnabled; + var eventsEnabled = + !(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled; if (targetShape && eventsEnabled) { targetShape._fireAndBubble(events.pointerout, { evt: evt }); targetShape._fireAndBubble(events.pointerleave, { evt: evt }); @@ -550,6 +552,7 @@ export class Stage extends Container { // no shape detected? do nothing if (!shape || !shape.isListening()) { + this[eventType + 'ClickStartShape'] = undefined; return; } @@ -590,12 +593,13 @@ export class Stage extends Container { if (!events) { return; } - if (DD.isDragging && DD.node!.preventDefault() && evt.cancelable) { + if (Konva.isDragging() && DD.node!.preventDefault() && evt.cancelable) { evt.preventDefault(); } this.setPointersPositions(evt); - var eventsEnabled = !DD.isDragging || Konva.hitOnDragEnabled; + var eventsEnabled = + !(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled; if (!eventsEnabled) { return; } @@ -962,3 +966,15 @@ _registerNode(Stage); * stage.container(container); */ Factory.addGetterSetter(Stage, 'container'); + +// chrome is clearing canvas in inactive browser window, causing layer content to be erased +// so let's redraw layers as soon as window becomes active +// TODO: any other way to solve this issue? +// TODO: should we remove it if chrome fixes the issue? +if (Konva.isBrowser) { + document.addEventListener('visibilitychange', () => { + stages.forEach((stage) => { + stage.batchDraw(); + }); + }); +} diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts index b5791cc12..a17cc8787 100644 --- a/src/shapes/Image.ts +++ b/src/shapes/Image.ts @@ -65,6 +65,11 @@ export class Image extends Shape { } } _useBufferCanvas() { + const hasCornerRadius = !!this.cornerRadius(); + const hasShadow = this.hasShadow(); + if (hasCornerRadius && hasShadow) { + return true; + } return super._useBufferCanvas(true); } _sceneFunc(context: Context) { diff --git a/src/shapes/Path.ts b/src/shapes/Path.ts index 1c8e08410..67a905e25 100644 --- a/src/shapes/Path.ts +++ b/src/shapes/Path.ts @@ -235,8 +235,8 @@ export class Path extends Shape { return pathLength; } - static getPointAtLengthOfDataArray(length: number, dataArray) { - var point, + static getPointAtLengthOfDataArray(length: number, dataArray: PathSegment[]) { + var points: number[], i = 0, ii = dataArray.length; @@ -250,18 +250,18 @@ export class Path extends Shape { } if (i === ii) { - point = dataArray[i - 1].points.slice(-2); + points = dataArray[i - 1].points.slice(-2); return { - x: point[0], - y: point[1], + x: points[0], + y: points[1], }; } if (length < 0.01) { - point = dataArray[i].points.slice(0, 2); + points = dataArray[i].points.slice(0, 2); return { - x: point[0], - y: point[1], + x: points[0], + y: points[1], }; } @@ -319,62 +319,47 @@ export class Path extends Shape { return null; } - static getPointOnLine(dist, P1x, P1y, P2x, P2y, fromX?, fromY?) { - if (fromX === undefined) { - fromX = P1x; - } - if (fromY === undefined) { - fromY = P1y; - } + static getPointOnLine( + dist: number, + P1x: number, + P1y: number, + P2x: number, + P2y: number, + fromX?: number, + fromY?: number + ) { + fromX = fromX ?? P1x; + fromY = fromY ?? P1y; - var m = (P2y - P1y) / (P2x - P1x + 0.00000001); - var run = Math.sqrt((dist * dist) / (1 + m * m)); - if (P2x < P1x) { - run *= -1; + const len = this.getLineLength(P1x, P1y, P2x, P2y); + if (len < 1e-10) { + return { x: P1x, y: P1y }; } - var rise = m * run; - var pt; if (P2x === P1x) { - // vertical line - pt = { - x: fromX, - y: fromY + rise, - }; - } else if ((fromY - P1y) / (fromX - P1x + 0.00000001) === m) { - pt = { - x: fromX + run, - y: fromY + rise, - }; - } else { - var ix, iy; - - var len = this.getLineLength(P1x, P1y, P2x, P2y); - // if (len < 0.00000001) { - // return { - // x: P1x, - // y: P1y, - // }; - // } - var u = (fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y); - u = u / (len * len); - ix = P1x + u * (P2x - P1x); - iy = P1y + u * (P2y - P1y); - - var pRise = this.getLineLength(fromX, fromY, ix, iy); - var pRun = Math.sqrt(dist * dist - pRise * pRise); - run = Math.sqrt((pRun * pRun) / (1 + m * m)); - if (P2x < P1x) { - run *= -1; - } - rise = m * run; - pt = { - x: ix + run, - y: iy + rise, - }; + // Vertical line + return { x: fromX, y: fromY + (P2y > P1y ? dist : -dist) }; + } + + const m = (P2y - P1y) / (P2x - P1x); + const run = Math.sqrt((dist * dist) / (1 + m * m)) * (P2x < P1x ? -1 : 1); + const rise = m * run; + + if (Math.abs(fromY - P1y - m * (fromX - P1x)) < 1e-10) { + return { x: fromX + run, y: fromY + rise }; } - return pt; + const u = + ((fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y)) / (len * len); + const ix = P1x + u * (P2x - P1x); + const iy = P1y + u * (P2y - P1y); + const pRise = this.getLineLength(fromX, fromY, ix, iy); + const pRun = Math.sqrt(dist * dist - pRise * pRise); + const adjustedRun = + Math.sqrt((pRun * pRun) / (1 + m * m)) * (P2x < P1x ? -1 : 1); + const adjustedRise = m * adjustedRun; + + return { x: ix + adjustedRun, y: iy + adjustedRise }; } static getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index 20eefa7e4..ddd655596 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -201,11 +201,19 @@ export class Text extends Shape { shouldUnderline = textDecoration.indexOf('underline') !== -1, shouldLineThrough = textDecoration.indexOf('line-through') !== -1, n; - + direction = direction === INHERIT ? context.direction : direction; - var translateY = 0; var translateY = lineHeightPx / 2; + var baseline = MIDDLE; + if (Konva._fixTextRendering) { + var metrics = this.measureSize('M'); // Use a sample character to get the ascent + + baseline = 'alphabetic'; + translateY = + (metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2 + + lineHeightPx / 2; + } var lineTranslateX = 0; var lineTranslateY = 0; @@ -216,11 +224,10 @@ export class Text extends Shape { context.setAttr('font', this._getContextFont()); - context.setAttr('textBaseline', MIDDLE); + context.setAttr('textBaseline', baseline); context.setAttr('textAlign', LEFT); - // handle vertical alignment if (verticalAlign === MIDDLE) { alignY = (this.getHeight() - textArrLen * lineHeightPx - padding * 2) / 2; @@ -254,18 +261,17 @@ export class Text extends Shape { context.save(); context.beginPath(); - context.moveTo( - lineTranslateX, - translateY + lineTranslateY + Math.round(fontSize / 2) - ); + let yOffset = Konva._fixTextRendering + ? Math.round(fontSize / 4) + : Math.round(fontSize / 2); + const x = lineTranslateX; + const y = translateY + lineTranslateY + yOffset; + context.moveTo(x, y); spacesNumber = text.split(' ').length - 1; oneWord = spacesNumber === 0; lineWidth = align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; - context.lineTo( - lineTranslateX + Math.round(lineWidth), - translateY + lineTranslateY + Math.round(fontSize / 2) - ); + context.lineTo(x + Math.round(lineWidth), y); // I have no idea what is real ratio // just /15 looks good enough @@ -279,7 +285,8 @@ export class Text extends Shape { if (shouldLineThrough) { context.save(); context.beginPath(); - context.moveTo(lineTranslateX, translateY + lineTranslateY); + let yOffset = Konva._fixTextRendering ? -Math.round(fontSize / 4) : 0; + context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset); spacesNumber = text.split(' ').length - 1; oneWord = spacesNumber === 0; lineWidth = @@ -288,7 +295,7 @@ export class Text extends Shape { : width; context.lineTo( lineTranslateX + Math.round(lineWidth), - translateY + lineTranslateY + translateY + lineTranslateY + yOffset ); context.lineWidth = fontSize / 15; const gradient = this._getLinearGradient(); @@ -385,22 +392,43 @@ export class Text extends Shape { * That method can't handle multiline text. * @method * @name Konva.Text#measureSize - * @param {String} [text] text to measure - * @returns {Object} { width , height} of measured text + * @param {String} text text to measure + * @returns {Object} { width , height } of measured text */ - measureSize(text) { + measureSize(text: string) { var _context = getDummyContext(), fontSize = this.fontSize(), - metrics; + metrics: TextMetrics; _context.save(); _context.font = this._getContextFont(); metrics = _context.measureText(text); _context.restore(); + + // Scale the fallback values based on the provided fontSize compared to the sample size (100 in your new case) + const scaleFactor = fontSize / 100; + + // Note, fallback values are from chrome browser with 100px font size and font-family "Arial" return { + actualBoundingBoxAscent: + metrics.actualBoundingBoxAscent ?? 71.58203125 * scaleFactor, + actualBoundingBoxDescent: metrics.actualBoundingBoxDescent ?? 0, // Remains zero as there is no descent in the provided metrics + actualBoundingBoxLeft: + metrics.actualBoundingBoxLeft ?? -7.421875 * scaleFactor, + actualBoundingBoxRight: + metrics.actualBoundingBoxRight ?? 75.732421875 * scaleFactor, + alphabeticBaseline: metrics.alphabeticBaseline ?? 0, // Remains zero as it's typically relative to the baseline itself + emHeightAscent: metrics.emHeightAscent ?? 100 * scaleFactor, + emHeightDescent: metrics.emHeightDescent ?? -20 * scaleFactor, + fontBoundingBoxAscent: metrics.fontBoundingBoxAscent ?? 91 * scaleFactor, + fontBoundingBoxDescent: + metrics.fontBoundingBoxDescent ?? 21 * scaleFactor, + hangingBaseline: + metrics.hangingBaseline ?? 72.80000305175781 * scaleFactor, + ideographicBaseline: metrics.ideographicBaseline ?? -21 * scaleFactor, width: metrics.width, - height: fontSize, + height: fontSize, // Typically set to the font size }; } _getContextFont() { @@ -703,7 +731,6 @@ Factory.overWriteSetter(Text, 'width', getNumberOrAutoValidator()); Factory.overWriteSetter(Text, 'height', getNumberOrAutoValidator()); - /** * get/set direction * @name Konva.Text#direction diff --git a/src/shapes/Transformer.ts b/src/shapes/Transformer.ts index f699a132d..859608875 100644 --- a/src/shapes/Transformer.ts +++ b/src/shapes/Transformer.ts @@ -47,7 +47,7 @@ export interface TransformerConfig extends ContainerConfig { newPos: Vector2d, evt: any ) => Vector2d; - anchorStyleFunc?: (anchor: Shape) => void; + anchorStyleFunc?: (anchor: Rect) => void; } var EVENTS_NAME = 'tr-konva'; @@ -203,6 +203,7 @@ function getSnap(snaps: Array, newRotationRad: number, tol: number) { return snapped; } +let activeTransformersCount = 0; /** * Transformer constructor. Transformer is a special type of group that allow you transform Konva * primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes @@ -244,7 +245,6 @@ function getSnap(snaps: Array, newRotationRad: number, tol: number) { * }); * layer.add(transformer); */ - export class Transformer extends Group { _nodes: Array; _movingAnchorName: string | null = null; @@ -254,6 +254,10 @@ export class Transformer extends Group { cos: number; _cursorChange: boolean; + static isTransforming = () => { + return activeTransformersCount > 0; + }; + constructor(config?: TransformerConfig) { // call super constructor super(config); @@ -666,6 +670,11 @@ export class Transformer extends Group { }); } _handleMouseDown(e) { + // do nothing if we already transforming + // that is possible to trigger with multitouch + if (this._transforming) { + return; + } this._movingAnchorName = e.target.name().split(' ')[0]; var attrs = this._getNodeRect(); @@ -690,6 +699,7 @@ export class Transformer extends Group { x: pos.x - ap.x, y: pos.y - ap.y, }; + activeTransformersCount++; this._fire('transformstart', { evt: e.evt, target: this.getNode() }); this._nodes.forEach((target) => { target._fire('transformstart', { evt: e.evt, target }); @@ -955,11 +965,16 @@ export class Transformer extends Group { window.removeEventListener('touchend', this._handleMouseUp, true); } var node = this.getNode(); + activeTransformersCount--; this._fire('transformend', { evt: e, target: node }); + // redraw layer to restore hit graph + this.getLayer()?.batchDraw(); if (node) { this._nodes.forEach((target) => { target._fire('transformend', { evt: e, target }); + // redraw layer to restore hit graph + target.getLayer()?.batchDraw(); }); } this._movingAnchorName = null; @@ -1109,11 +1124,14 @@ export class Transformer extends Group { const attrs = newLocalTransform.decompose(); node.setAttrs(attrs); - this._fire('transform', { evt: evt, target: node }); - node._fire('transform', { evt: evt, target: node }); node.getLayer()?.batchDraw(); }); this.rotation(Util._getRotation(newAttrs.rotation)); + // trigger transform event AFTER we update rotation + this._nodes.forEach((node) => { + this._fire('transform', { evt: evt, target: node }); + node._fire('transform', { evt: evt, target: node }); + }); this._resetTransformCache(); this.update(); this.getLayer()!.batchDraw(); diff --git a/test/manual/Pixelate-test.ts b/test/manual/Pixelate-test.ts index cb22ea303..a8a9dc416 100644 --- a/test/manual/Pixelate-test.ts +++ b/test/manual/Pixelate-test.ts @@ -1,6 +1,5 @@ -import { assert } from 'chai'; - import { addStage, Konva, loadImage } from '../unit/test-utils'; +import { cloneAndCompareLayer } from '../unit/test-utils'; describe('Pixelate', function () { // ====================================================== @@ -42,4 +41,25 @@ describe('Pixelate', function () { done(); }); }); + + it('make sure we have no extra transparent pixels', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + Konva.Image.fromURL( + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGUAAABmCAYAAADS6F9hAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t1cEJADAMw8B2/6Fd6BT3UCYQEiZ3205HGbhFoXp8mKJ4TYoCNilKUUQDIFM/pSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgtpSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgPgGOW3jCsp3sAAAAASUVORK5CYII=', + function (image) { + layer.add(image); + + image.cache(); + image.filters([Konva.Filters.Pixelate]); + image.pixelSize(4); + layer.draw(); + cloneAndCompareLayer(layer); + + done(); + } + ); + }); }); diff --git a/test/sandbox.html b/test/sandbox.html index 674c2c187..a1d11e272 100644 --- a/test/sandbox.html +++ b/test/sandbox.html @@ -30,45 +30,42 @@ diff --git a/test/unit/Context-test.ts b/test/unit/Context-test.ts index 7d5a256c6..7cf6fc5d8 100644 --- a/test/unit/Context-test.ts +++ b/test/unit/Context-test.ts @@ -22,6 +22,7 @@ describe('Context', function () { 'arc', 'arcTo', 'rect', + 'roundRect', 'ellipse', 'fill', 'stroke', diff --git a/test/unit/DragAndDropEvents-test.ts b/test/unit/DragAndDropEvents-test.ts index 8219e7d1c..2eb0474d6 100644 --- a/test/unit/DragAndDropEvents-test.ts +++ b/test/unit/DragAndDropEvents-test.ts @@ -6,6 +6,9 @@ import { simulateMouseDown, simulateMouseMove, simulateMouseUp, + simulatePointerDown, + simulatePointerMove, + simulatePointerUp, } from './test-utils'; describe('DragAndDropEvents', function () { @@ -219,10 +222,6 @@ describe('DragAndDropEvents', function () { clicked = true; }); - circle.on('dblclick', function () { - //console.log('dblclick'); - }); - simulateMouseDown(stage, { x: 40, y: 40, @@ -245,6 +244,58 @@ describe('DragAndDropEvents', function () { }, 20); }); + // TODO: how to solve it? + // hint: every shape has pointerId that indicates which pointer is dragging it + // but "pointer" event and mouse event has different pointerId + // so we need to find a way to match them + // should we save several pointers per shape? + // doesn't sound good + // switch to pointer only event handling? + it.skip('click should not occur after drag and drop', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: 40, + y: 40, + radius: 20, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + draggable: true, + }); + + layer.add(circle); + stage.add(layer); + + var clicked = false; + + stage.on('pointerclick', function () { + clicked = true; + }); + + simulatePointerDown(stage, { + x: 40, + y: 40, + }); + + setTimeout(function () { + simulatePointerMove(stage, { + x: 100, + y: 100, + }); + + simulatePointerUp(stage, { + x: 100, + y: 100, + }); + + assert(!clicked, 'click event should not have been fired'); + + done(); + }, 20); + }); + // ====================================================== it('drag and drop distance', function (done) { var stage = addStage(); diff --git a/test/unit/Group-test.ts b/test/unit/Group-test.ts index f0cc602a0..c2ec769ff 100644 --- a/test/unit/Group-test.ts +++ b/test/unit/Group-test.ts @@ -55,15 +55,43 @@ describe('Group', function () { var path = new Konva.Group({ width: 100, height: 100, - clipFunc: () => [new Path2D('M0 0v50h50Z')] + clipFunc: () => [new Path2D('M0 0v50h50Z')], }); layer.add(path); stage.add(layer); - const trace = layer.getContext().getTrace() + const trace = layer.getContext().getTrace(); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();'); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();' + ); + }); + + it('clip group with by zero size', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var group = new Konva.Group({ + width: 100, + height: 100, + clipWidth: 0, + clipHeight: 0, + }); + + layer.add(group); + stage.add(layer); + + const trace = layer.getContext().getTrace(); + + console.log(trace); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,0,0);clip();transform(1,0,0,1,0,0);restore();' + ); }); it('clip group with a Path2D and clipRule', function () { @@ -80,8 +108,11 @@ describe('Group', function () { layer.add(path); stage.add(layer); - const trace = layer.getContext().getTrace() + const trace = layer.getContext().getTrace(); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();'); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();' + ); }); }); diff --git a/test/unit/Image-test.ts b/test/unit/Image-test.ts index 1f666ed36..be9c95168 100644 --- a/test/unit/Image-test.ts +++ b/test/unit/Image-test.ts @@ -413,4 +413,39 @@ describe('Image', function () { done(); }); }); + + it('corner radius with shadow', function (done) { + // that will trigger buffer canvas + loadImage('darth-vader.jpg', (imageObj) => { + var stage = addStage(); + + var layer = new Konva.Layer(); + var darth = new Konva.Image({ + x: 20, + y: 20, + image: imageObj, + cornerRadius: 10, + draggable: true, + stroke: 'red', + strokeWidth: 100, + strokeEnabled: false, + shadowColor: 'black', + shadowBlur: 10, + shadowOffsetX: 10, + shadowOffsetY: 10, + scaleX: 0.5, + scaleY: 0.5, + }); + + layer.add(darth); + stage.add(layer); + + assert.equal( + layer.getContext().getTrace(true), + 'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();' + ); + + done(); + }); + }); }); diff --git a/test/unit/MouseEvents-test.ts b/test/unit/MouseEvents-test.ts index ecc978a87..ed08d1441 100644 --- a/test/unit/MouseEvents-test.ts +++ b/test/unit/MouseEvents-test.ts @@ -2377,4 +2377,67 @@ describe('MouseEvents', function () { }); assert.deepEqual(stage.getPointerPosition(), { x: 60, y: 60 }); }); + + it('mousedown on empty then mouseup on shape', function () { + if (isNode) { + return; + } + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + stage.on('mousedown mousemove mouseup click', function (e) { + console.log('state', e.type); + }); + + var rect = new Konva.Rect({ + width: 50, + height: 50, + fill: 'red', + draggable: true, + }); + layer.add(rect); + + layer.draw(); + + var clicks = 0; + rect.on('click', function () { + console.log('click'); + clicks += 1; + if (clicks === 2) { + debugger; + } + }); + + simulateMouseDown(stage, { + x: 40, + y: 40, + }); + + simulateMouseUp(stage, { + x: 40, + y: 40, + }); + + // trigger click + assert.equal(clicks, 1, 'clicks not triggered'); + + // mousedown outside + simulateMouseDown(stage, { + x: 60, + y: 6, + }); + // move into rect + simulateMouseMove(stage, { + x: 40, + y: 40, + }); + // mouseup inside rect + simulateMouseUp(stage, { + x: 40, + y: 40, + }); + // it shouldn't trigger the click event!!! + assert.equal(clicks, 1, 'clicks not triggered'); + }); }); diff --git a/test/unit/Node-cache-test.ts b/test/unit/Node-cache-test.ts index 1fbf759c6..687a1c5ad 100644 --- a/test/unit/Node-cache-test.ts +++ b/test/unit/Node-cache-test.ts @@ -1469,7 +1469,7 @@ describe('Caching', function () { layer.draw(); assert.equal( circle._cache.get('canvas').filter.width, - 21 * circle._cache.get('canvas').filter.pixelRatio + 20 * circle._cache.get('canvas').filter.pixelRatio ); circle.filters([]); // TODO: should we clear cache canvas? diff --git a/test/unit/Path-test.ts b/test/unit/Path-test.ts index e8cdff824..4b3501f67 100644 --- a/test/unit/Path-test.ts +++ b/test/unit/Path-test.ts @@ -1134,11 +1134,11 @@ describe('Path', function () { assert.deepEqual(points, [ { x: 300, y: 10 }, - { x: 290.2871413779118, y: 27.48314552325543 }, - { x: 280.57428275582356, y: 44.96629104651086 }, - { x: 270.86142413373534, y: 62.4494365697663 }, - { x: 261.1485655116471, y: 79.93258209302172 }, - { x: 251.43570688955887, y: 97.41572761627717 }, + { x: 290.28714137642737, y: 27.483145522430753 }, + { x: 280.57428275285474, y: 44.96629104486151 }, + { x: 270.86142412928206, y: 62.44943656729226 }, + { x: 261.1485655057094, y: 79.93258208972301 }, + { x: 251.4357068821368, y: 97.41572761215377 }, { x: 230.89220826660141, y: 87.23996356219386 }, { x: 207.0639321224534, y: 74.08466390481559 }, { x: 182.87529785963875, y: 63.52674972743341 }, @@ -1168,6 +1168,47 @@ describe('Path', function () { stage.add(layer); }); + it('get point at path with float attrs', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + const data = + 'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + data, + }); + layer.add(path); + if (isBrowser) { + const SVGPath = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'path' + ) as SVGPathElement; + SVGPath.setAttribute('d', data); + for (var i = 0; i < path.getLength(); i += 1) { + var p = path.getPointAtLength(i); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + const position = SVGPath.getPointAtLength(i); + assert( + Math.abs(p.x - position.x) <= 1, + 'error for x should be smaller than 10% for i = ' + i + ); + assert( + Math.abs(p.y - position.y) <= 1, + 'error for y should be smaller than 10% for i = ' + i + ); + } + } + }); + it('get point at path - bezier', function () { var stage = addStage(); var layer = new Konva.Layer(); @@ -1618,8 +1659,11 @@ describe('Path', function () { layer.add(path); stage.add(layer); - const trace = layer.getContext().getTrace() + const trace = layer.getContext().getTrace(); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();'); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();' + ); }); }); diff --git a/test/unit/Shape-test.ts b/test/unit/Shape-test.ts index d3035562c..26758dda7 100644 --- a/test/unit/Shape-test.ts +++ b/test/unit/Shape-test.ts @@ -12,6 +12,7 @@ import { compareLayers, loadImage, Konva, + compareCanvases, } from './test-utils'; describe('Shape', function () { @@ -1479,6 +1480,76 @@ describe('Shape', function () { } }); + it('export when buffer canvas is used should handle scaling correctly', async function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group(); + layer.add(group); + + var text = new Konva.Text({ + text: 'hello', + fontSize: 300, + fill: 'green', + shadowColor: 'black', + }); + group.add(text); + + const canvas1 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + text.stroke('transparent'); + const canvas2 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + + compareCanvases(canvas2, canvas1, 255, 10); + }); + + it('export when buffer canvas is used should handle scaling correctly another time', async function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var group = new Konva.Group({ + x: 400, + }); + layer.add(group); + + var text = new Konva.Text({ + text: 'hello', + fontSize: 300, + fill: 'green', + shadowColor: 'black', + }); + group.add(text); + + const canvas1 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + text.stroke('transparent'); + const canvas2 = group.toCanvas({ + x: group.x(), + y: group.y(), + width: text.width(), + height: text.height(), + }); + + compareCanvases(canvas2, canvas1, 240, 110); + }); + // ====================================================== it('optional disable shadow for stroke', function () { var stage = addStage(); @@ -1582,6 +1653,35 @@ describe('Shape', function () { assert.equal(rect.height, 100, 'should not effect width'); }); + it('getClientRect should not use cached values', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + var shape = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 100, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + strokeEnabled: false, + shadowOffsetX: 10, + shadowEnabled: false, + }); + + layer.add(shape); + stage.add(layer); + + layer.cache(); + + layer.scaleX(2); + + const rect = shape.getClientRect(); + + assert.equal(rect.x, 200); + }); + it('getClientRect for shape in transformed parent', function () { var stage = addStage(); var layer = new Konva.Layer(); @@ -2203,4 +2303,36 @@ describe('Shape', function () { assert.equal(callCount, 0); Konva.Util.warn = oldWarn; }); + + it('fill rule on hit graph', function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + var mask = new Konva.Shape({ + sceneFunc: function (ctx, shape) { + ctx.beginPath(); + ctx.rect(0, 0, 500, 500); + ctx.rect(100, 100, 100, 100); + ctx.closePath(); + ctx.fillShape(shape); + }, + draggable: true, + fill: 'red', + fillRule: 'evenodd', + }); + + layer.add(mask); + layer.draw(); + const trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,500,500);rect(100,100,100,100);closePath();fillStyle=red;fill(evenodd);restore();' + ); + + const hitShape = layer.getIntersection({ x: 150, y: 150 }); + assert.equal(hitShape, null); + }); }); diff --git a/test/unit/Text-test.ts b/test/unit/Text-test.ts index f8b127aa9..f95cb8046 100644 --- a/test/unit/Text-test.ts +++ b/test/unit/Text-test.ts @@ -1654,7 +1654,7 @@ describe('Text', function () { assert.equal(layer.getContext().getTrace(), trace); }); - + it('sets ltr text direction', function () { var stage = addStage(); var layer = new Konva.Layer(); @@ -1673,9 +1673,8 @@ describe('Text', function () { assert.equal(layer.getContext().getTrace(), trace); }); - - - it('sets rtl text direction', function () { + + it('sets rtl text direction', function () { var stage = addStage(); var layer = new Konva.Layer(); @@ -1693,7 +1692,7 @@ describe('Text', function () { assert.equal(layer.getContext().getTrace(), trace); }); - + it('sets rtl text direction with letterSpacing', function () { var stage = addStage(); var layer = new Konva.Layer(); @@ -1713,4 +1712,22 @@ describe('Text', function () { assert.equal(layer.getContext().getTrace(), trace); }); + + it('try fixed render', () => { + Konva._fixTextRendering = true; + var stage = addStage(); + var layer = new Konva.Layer(); + + stage.add(layer); + var text = new Konva.Text({ text: 'hello', fontSize: 100 }); + + layer.add(text); + layer.draw(); + Konva._fixTextRendering = false; + + const trace = + 'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 100px Arial;textBaseline=alphabetic;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,85);restore();restore();'; + + assert.equal(layer.getContext().getTrace(), trace); + }); }); diff --git a/test/unit/TouchEvents-test.ts b/test/unit/TouchEvents-test.ts index f79449994..5d8fa7701 100644 --- a/test/unit/TouchEvents-test.ts +++ b/test/unit/TouchEvents-test.ts @@ -460,11 +460,11 @@ describe('TouchEvents', function () { ); assert.equal(touchEnd, 1); assert.equal(stageTouchEnd, 1); - assert.equal(stageTap, 2, 'one tap should be fired'); + assert.equal(stageTap, 1, 'one tap should be fired'); assert.equal( stageEventStack.join(' '), - 'touchstart touchstart touchstart touchend tap tap', + 'touchstart touchstart touchstart touchend tap', 'should fire tap after touchend' ); @@ -481,7 +481,7 @@ describe('TouchEvents', function () { assert.equal(touchEnd, 2); assert.equal(touchEnd2, 1); assert.equal(stageTouchEnd, 3); - assert.equal(stageTap, 2, 'still one tap should be fired'); + assert.equal(stageTap, 1, 'still one tap should be fired'); // Don't need to check event stack here, the pointers moved so no tap is fired }); diff --git a/test/unit/Transformer-test.ts b/test/unit/Transformer-test.ts index 782969936..af6782df1 100644 --- a/test/unit/Transformer-test.ts +++ b/test/unit/Transformer-test.ts @@ -4929,4 +4929,163 @@ describe('Transformer', function () { assertAlmostEqual(rect.scaleX(), 1); assertAlmostEqual(rect.scaleY(), 1); }); + + it('should be able to prevent rotation in transform event', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 55, + y: 55, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + tr.on('transform', function (e) { + tr.rotation(0); + }); + + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + assert.equal(tr.rotation(), 0); + simulateMouseUp(tr, { x: 110, y: 2 }); + }); + + it('skip render on hit graph while transforming', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 55, + y: 55, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + let shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, null); + simulateMouseUp(tr, { x: 110, y: 2 }); + layer.draw(); + shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, rect); + // reset position + rect.setAttrs({ + x: 50, + y: 50, + draggable: true, + width: 100, + height: 100, + }); + + tr.nodes([rect]); + layer.draw(); + // now check if graph is visible back when we moved a bit + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + setTimeout(() => { + shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, null); + simulateMouseUp(tr, { x: 110, y: 2 }); + setTimeout(() => { + shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, rect); + done(); + }, 100); + }, 100); + }); + + it('enable hit graph back on transformer destroy', function (done) { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var rect = new Konva.Rect({ + x: 55, + y: 55, + draggable: true, + width: 100, + height: 100, + fill: 'yellow', + }); + layer.add(rect); + + var tr = new Konva.Transformer({ + nodes: [rect], + }); + layer.add(tr); + layer.draw(); + + // now check if graph is visible back when we moved a bit + simulateMouseDown(tr, { + x: 100, + y: 2, + }); + simulateMouseMove(tr, { + x: 110, + y: 2, + }); + setTimeout(() => { + tr.destroy(); + setTimeout(() => { + var shape = layer.getIntersection({ + x: 100, + y: 100, + }); + assert.equal(shape, rect); + done(); + }, 100); + }, 100); + }); }); diff --git a/tsconfig.json b/tsconfig.json index a9c5df04c..da4622615 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,9 +14,11 @@ "noImplicitAny": false, "noImplicitThis": false, "useUnknownInCatchVariables": false, + "skipLibCheck": true, // probably we would never enable this one // because it's too strict, konva generates many functions on the runtime "strictPropertyInitialization": false, + }, - "include": ["./src/**/*.ts"] + "include": ["./src/**/*.ts"], }