feat: improved types in Node

No logic changes - only typing changes.
This commit is contained in:
psychedelicious 2024-08-25 18:38:44 +10:00
parent 583fccf249
commit 2b5a1aa4a3
2 changed files with 243 additions and 126 deletions

View File

@ -3,7 +3,18 @@ import { Factory } from './Factory';
import { SceneCanvas, HitCanvas, Canvas } from './Canvas'; import { SceneCanvas, HitCanvas, Canvas } from './Canvas';
import { Konva } from './Global'; import { Konva } from './Global';
import { Container } from './Container'; import { Container } from './Container';
import { GetSet, Vector2d, IRect } from './types'; import {
GetSet,
Vector2d,
IRect,
ToCanvasConfig,
ToDataURLConfig,
ToImageConfig,
ToBlobConfig,
Size,
CacheConfig,
GetClientRectConfig,
} from './types';
import { DD } from './DragAndDrop'; import { DD } from './DragAndDrop';
import { import {
getNumberValidator, getNumberValidator,
@ -172,11 +183,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
// all change event listeners are attached to the prototype // all change event listeners are attached to the prototype
} }
hasChildren() { hasChildren(): boolean {
return false; return false;
} }
_clearCache(attr?: string) { _clearCache(attr?: string): void {
// if we want to clear transform cache // if we want to clear transform cache
// we don't really need to remove it from the cache // we don't really need to remove it from the cache
// but instead mark as "dirty" // but instead mark as "dirty"
@ -300,17 +311,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* drawBorder: true * drawBorder: true
* }); * });
*/ */
cache(config?: { cache(config?: CacheConfig): typeof this | undefined {
x?: number;
y?: number;
width?: number;
height?: number;
drawBorder?: boolean;
offset?: number;
pixelRatio?: number;
imageSmoothingEnabled?: boolean;
hitCanvasPixelRatio?: number;
}) {
var conf = config || {}; var conf = config || {};
var rect = {} as IRect; var rect = {} as IRect;
@ -445,7 +446,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#isCached * @name Konva.Node#isCached
* @returns {Boolean} * @returns {Boolean}
*/ */
isCached() { isCached(): boolean {
return this._cache.has(CANVAS); return this._cache.has(CANVAS);
} }
@ -487,17 +488,12 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* rect.getClientRect(); * rect.getClientRect();
* // returns Object {x: -2, y: 46, width: 104, height: 208} * // returns Object {x: -2, y: 46, width: 104, height: 208}
*/ */
getClientRect(config?: { getClientRect(config?: GetClientRectConfig): IRect {
skipTransform?: boolean;
skipShadow?: boolean;
skipStroke?: boolean;
relativeTo?: Container;
}): { x: number; y: number; width: number; height: number } {
// abstract method // abstract method
// redefine in Container and Shape // redefine in Container and Shape
throw new Error('abstract "getClientRect" method call'); throw new Error('abstract "getClientRect" method call');
} }
_transformedRect(rect: IRect, top?: Node | null) { _transformedRect(rect: IRect, top?: Node | null): IRect {
var points = [ var points = [
{ x: rect.x, y: rect.y }, { x: rect.x, y: rect.y },
{ x: rect.x + rect.width, y: rect.y }, { x: rect.x + rect.width, y: rect.y },
@ -527,7 +523,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
height: maxY - minY, height: maxY - minY,
}; };
} }
_drawCachedSceneCanvas(context: Context) { _drawCachedSceneCanvas(context: Context): void {
context.save(); context.save();
context._applyOpacity(this); context._applyOpacity(this);
context._applyGlobalCompositeOperation(this); context._applyGlobalCompositeOperation(this);
@ -547,7 +543,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
); );
context.restore(); context.restore();
} }
_drawCachedHitCanvas(context: Context) { _drawCachedHitCanvas(context: Context): void {
var canvasCache = this._getCanvasCache(), var canvasCache = this._getCanvasCache(),
hitCanvas = canvasCache.hit; hitCanvas = canvasCache.hit;
context.save(); context.save();
@ -750,7 +746,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // remove listener by name * // remove listener by name
* node.off('click.foo'); * node.off('click.foo');
*/ */
off(evtStr?: string, callback?: Function) { off(evtStr?: string, callback?: Function): typeof this {
var events = (evtStr || '').split(SPACE), var events = (evtStr || '').split(SPACE),
len = events.length, len = events.length,
n, n,
@ -787,7 +783,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return this; return this;
} }
// some event aliases for third party integration like HammerJS // some event aliases for third party integration like HammerJS
dispatchEvent(evt: any) { dispatchEvent(evt: any): typeof this {
var e = { var e = {
target: this, target: this,
type: evt.type, type: evt.type,
@ -796,19 +792,23 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
this.fire(evt.type, e); this.fire(evt.type, e);
return this; return this;
} }
addEventListener(type: string, handler: (e: Event) => void) { addEventListener(type: string, handler: (e: Event) => void): typeof this {
// we have to pass native event to handler // we have to pass native event to handler
this.on(type, function (evt) { this.on(type, function (evt) {
handler.call(this, evt.evt); handler.call(this, evt.evt);
}); });
return this; return this;
} }
removeEventListener(type: string) { removeEventListener(type: string): typeof this {
this.off(type); this.off(type);
return this; return this;
} }
// like node.on // like node.on
_delegate(event: string, selector: string, handler: (e: Event) => void) { _delegate(
event: string,
selector: string,
handler: (e: Event) => void
): void {
var stopNode = this; var stopNode = this;
this.on(event, function (evt) { this.on(event, function (evt) {
var targets = evt.target.findAncestors(selector, true, stopNode); var targets = evt.target.findAncestors(selector, true, stopNode);
@ -827,7 +827,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example * @example
* node.remove(); * node.remove();
*/ */
remove() { remove(): typeof this {
if (this.isDragging()) { if (this.isDragging()) {
this.stopDrag(); this.stopDrag();
} }
@ -837,7 +837,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
this._remove(); this._remove();
return this; return this;
} }
_clearCaches() { _clearCaches(): void {
this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM);
this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY);
this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); this._clearSelfAndDescendantCache(ABSOLUTE_SCALE);
@ -845,7 +845,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
this._clearSelfAndDescendantCache(VISIBLE); this._clearSelfAndDescendantCache(VISIBLE);
this._clearSelfAndDescendantCache(LISTENING); this._clearSelfAndDescendantCache(LISTENING);
} }
_remove() { _remove(): void {
// every cached attr that is calculated via node tree // every cached attr that is calculated via node tree
// traversal must be cleared when removing a node // traversal must be cleared when removing a node
this._clearCaches(); this._clearCaches();
@ -898,9 +898,9 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* console.log(node.getId()); * console.log(node.getId());
* }) * })
*/ */
getAncestors() { getAncestors(): Container[] {
var parent = this.getParent(), var parent = this.getParent(),
ancestors: Array<Node> = []; ancestors: Array<Container> = [];
while (parent) { while (parent) {
ancestors.push(parent); ancestors.push(parent);
@ -915,8 +915,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getAttrs * @name Konva.Node#getAttrs
* @returns {Object} * @returns {Object}
*/ */
getAttrs() { getAttrs(): Partial<Config> {
return (this.attrs || {}) as Config & Record<string, any>; return this.attrs || {};
} }
/** /**
* set multiple attrs at once using an object literal * set multiple attrs at once using an object literal
@ -930,7 +930,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* fill: 'red' * fill: 'red'
* }); * });
*/ */
setAttrs(config: any) { setAttrs(config?: Partial<Config>) {
this._batchTransformChanges(() => { this._batchTransformChanges(() => {
var key, method; var key, method;
if (!config) { if (!config) {
@ -1125,7 +1125,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
// like node.position(pos) // like node.position(pos)
// for performance reasons, lets batch transform reset // for performance reasons, lets batch transform reset
// so it work faster // so it work faster
_batchTransformChanges(func) { _batchTransformChanges(func: () => void): void {
this._batchingTransformChange = true; this._batchingTransformChange = true;
func(); func();
this._batchingTransformChange = false; this._batchingTransformChange = false;
@ -1136,14 +1136,14 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
this._needClearTransformCache = false; this._needClearTransformCache = false;
} }
setPosition(pos: Vector2d) { setPosition(pos: Vector2d): typeof this {
this._batchTransformChanges(() => { this._batchTransformChanges(() => {
this.x(pos.x); this.x(pos.x);
this.y(pos.y); this.y(pos.y);
}); });
return this; return this;
} }
getPosition() { getPosition(): Vector2d {
return { return {
x: this.x(), x: this.x(),
y: this.y(), y: this.y(),
@ -1161,7 +1161,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // if you want to know position of the click, related to the rectangle you can use * // if you want to know position of the click, related to the rectangle you can use
* rect.getRelativePointerPosition(); * rect.getRelativePointerPosition();
*/ */
getRelativePointerPosition() { getRelativePointerPosition(): Vector2d | null {
const stage = this.getStage(); const stage = this.getStage();
if (!stage) { if (!stage) {
return null; return null;
@ -1192,7 +1192,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // so stage transforms are ignored * // so stage transforms are ignored
* node.getAbsolutePosition(stage) * node.getAbsolutePosition(stage)
*/ */
getAbsolutePosition(top?: Node) { getAbsolutePosition(top?: Node): Vector2d {
let haveCachedParent = false; let haveCachedParent = false;
let parent = this.parent; let parent = this.parent;
while (parent) { while (parent) {
@ -1217,7 +1217,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return absoluteTransform.getTranslation(); return absoluteTransform.getTranslation();
} }
setAbsolutePosition(pos: Vector2d) { setAbsolutePosition(pos: Vector2d): typeof this {
const { x, y, ...origTrans } = this._clearTransform(); const { x, y, ...origTrans } = this._clearTransform();
// don't clear translation // don't clear translation
@ -1241,7 +1241,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return this; return this;
} }
_setTransform(trans) { _setTransform(trans): void {
var key; var key;
for (key in trans) { for (key in trans) {
@ -1291,7 +1291,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* y: 2 * y: 2
* }); * });
*/ */
move(change: Vector2d) { move(change: Vector2d): typeof this {
var changeX = change.x, var changeX = change.x,
changeY = change.y, changeY = change.y,
x = this.x(), x = this.x(),
@ -1308,7 +1308,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
this.setPosition({ x: x, y: y }); this.setPosition({ x: x, y: y });
return this; return this;
} }
_eachAncestorReverse(func, top) { _eachAncestorReverse(func: (node: Node) => void, top?: Node): void {
var family: Array<Node> = [], var family: Array<Node> = [],
parent = this.getParent(), parent = this.getParent(),
len, len,
@ -1341,7 +1341,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @param {Number} theta * @param {Number} theta
* @returns {Konva.Node} * @returns {Konva.Node}
*/ */
rotate(theta: number) { rotate(theta: number): typeof this {
this.rotation(this.rotation() + theta); this.rotation(this.rotation() + theta);
return this; return this;
} }
@ -1413,7 +1413,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#moveToBottom * @name Konva.Node#moveToBottom
* @returns {Boolean} * @returns {Boolean}
*/ */
moveToBottom() { moveToBottom(): boolean {
if (!this.parent) { if (!this.parent) {
Util.warn('Node has no parent. moveToBottom function is ignored.'); Util.warn('Node has no parent. moveToBottom function is ignored.');
return false; return false;
@ -1427,7 +1427,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
return false; return false;
} }
setZIndex(zIndex) { setZIndex(zIndex: number): typeof this {
if (!this.parent) { if (!this.parent) {
Util.warn('Node has no parent. zIndex parameter is ignored.'); Util.warn('Node has no parent. zIndex parameter is ignored.');
return this; return this;
@ -1453,10 +1453,10 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getAbsoluteOpacity * @name Konva.Node#getAbsoluteOpacity
* @returns {Number} * @returns {Number}
*/ */
getAbsoluteOpacity() { getAbsoluteOpacity(): number {
return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity);
} }
_getAbsoluteOpacity() { _getAbsoluteOpacity(): number {
var absOpacity = this.opacity(); var absOpacity = this.opacity();
var parent = this.getParent(); var parent = this.getParent();
if (parent && !parent._isUnderCache) { if (parent && !parent._isUnderCache) {
@ -1474,7 +1474,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // move node from current layer into layer2 * // move node from current layer into layer2
* node.moveTo(layer2); * node.moveTo(layer2);
*/ */
moveTo(newContainer: any) { moveTo(newContainer: Container): typeof this {
// do nothing if new container is already parent // do nothing if new container is already parent
if (this.getParent() !== newContainer) { if (this.getParent() !== newContainer) {
this._remove(); this._remove();
@ -1533,7 +1533,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#toJSON * @name Konva.Node#toJSON
* @returns {String} * @returns {String}
*/ */
toJSON() { toJSON(): string {
return JSON.stringify(this.toObject()); return JSON.stringify(this.toObject());
} }
/** /**
@ -1542,7 +1542,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getParent * @name Konva.Node#getParent
* @returns {Konva.Node} * @returns {Konva.Node}
*/ */
getParent() { getParent(): Container | null {
return this.parent; return this.parent;
} }
/** /**
@ -1561,7 +1561,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
selector: string | Function, selector: string | Function,
includeSelf?: boolean, includeSelf?: boolean,
stopNode?: Node stopNode?: Node
) { ): Node[] {
var res: Array<Node> = []; var res: Array<Node> = [];
if (includeSelf && this._isMatch(selector)) { if (includeSelf && this._isMatch(selector)) {
@ -1598,11 +1598,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
selector: string | Function, selector: string | Function,
includeSelf?: boolean, includeSelf?: boolean,
stopNode?: Container stopNode?: Container
) { ): Node | undefined {
return this.findAncestors(selector, includeSelf, stopNode)[0]; return this.findAncestors(selector, includeSelf, stopNode)[0];
} }
// is current node match passed selector? // is current node match passed selector?
_isMatch(selector: string | Function) { _isMatch(selector: string | Function): boolean {
if (!selector) { if (!selector) {
return false; return false;
} }
@ -1663,7 +1663,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return this._getCache(STAGE, this._getStage); return this._getCache(STAGE, this._getStage);
} }
_getStage() { _getStage(): Stage | null {
var parent = this.getParent(); var parent = this.getParent();
if (parent) { if (parent) {
return parent.getStage(); return parent.getStage();
@ -1695,7 +1695,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // fire click event that bubbles * // fire click event that bubbles
* node.fire('click', null, true); * node.fire('click', null, true);
*/ */
fire(eventType: string, evt: any = {}, bubble?: boolean) { fire(eventType: string, evt: any = {}, bubble?: boolean): typeof this {
evt.target = evt.target || this; evt.target = evt.target || this;
// bubble // bubble
if (bubble) { if (bubble) {
@ -1713,19 +1713,16 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getAbsoluteTransform * @name Konva.Node#getAbsoluteTransform
* @returns {Konva.Transform} * @returns {Konva.Transform}
*/ */
getAbsoluteTransform(top?: Node | null) { getAbsoluteTransform(top?: Node | null): Transform {
// if using an argument, we can't cache the result. // if using an argument, we can't cache the result.
if (top) { if (top) {
return this._getAbsoluteTransform(top); return this._getAbsoluteTransform(top);
} else { } else {
// if no argument, we can cache the result // if no argument, we can cache the result
return this._getCache( return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform);
ABSOLUTE_TRANSFORM,
this._getAbsoluteTransform
) as Transform;
} }
} }
_getAbsoluteTransform(top?: Node) { _getAbsoluteTransform(top?: Node): Transform {
var at: Transform; var at: Transform;
// we we need position relative to an ancestor, we will iterate for all // we we need position relative to an ancestor, we will iterate for all
if (top) { if (top) {
@ -1776,7 +1773,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // get absolute scale x * // get absolute scale x
* var scaleX = node.getAbsoluteScale().x; * var scaleX = node.getAbsoluteScale().x;
*/ */
getAbsoluteScale(top?: Node) { getAbsoluteScale(top?: Node): Vector2d {
// do not cache this calculations, // do not cache this calculations,
// because it use cache transform // because it use cache transform
// this is special logic for caching with some shapes with shadow // this is special logic for caching with some shapes with shadow
@ -1806,7 +1803,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* // get absolute rotation * // get absolute rotation
* var rotation = node.getAbsoluteRotation(); * var rotation = node.getAbsoluteRotation();
*/ */
getAbsoluteRotation() { getAbsoluteRotation(): number {
// var parent: Node = this; // var parent: Node = this;
// var rotation = 0; // var rotation = 0;
@ -1823,8 +1820,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getTransform * @name Konva.Node#getTransform
* @returns {Konva.Transform} * @returns {Konva.Transform}
*/ */
getTransform() { getTransform(): Transform {
return this._getCache(TRANSFORM, this._getTransform) as Transform; return this._getCache(TRANSFORM, this._getTransform);
} }
_getTransform(): Transform { _getTransform(): Transform {
var m: Transform = this._cache.get(TRANSFORM) || new Transform(); var m: Transform = this._cache.get(TRANSFORM) || new Transform();
@ -1915,7 +1912,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
return node; return node;
} }
_toKonvaCanvas(config) { _toKonvaCanvas(config?: ToCanvasConfig): SceneCanvas {
config = config || {}; config = config || {};
var box = this.getClientRect(); var box = this.getClientRect();
@ -1976,7 +1973,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example * @example
* var canvas = node.toCanvas(); * var canvas = node.toCanvas();
*/ */
toCanvas(config?) { toCanvas(config?: ToCanvasConfig): HTMLCanvasElement {
return this._toKonvaCanvas(config)._canvas; return this._toKonvaCanvas(config)._canvas;
} }
/** /**
@ -2002,16 +1999,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing * @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing
* @returns {String} * @returns {String}
*/ */
toDataURL(config?: { toDataURL(config?: ToDataURLConfig): string {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
mimeType?: string;
quality?: number;
callback?: (str: string) => void;
}) {
config = config || {}; config = config || {};
var mimeType = config.mimeType || null, var mimeType = config.mimeType || null,
quality = config.quality || null; quality = config.quality || null;
@ -2052,16 +2040,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* } * }
* }); * });
*/ */
toImage(config?: { toImage(config?: ToImageConfig): Promise<HTMLImageElement> {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
mimeType?: string;
quality?: number;
callback?: (img: HTMLImageElement) => void;
}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const callback = config?.callback; const callback = config?.callback;
@ -2096,16 +2075,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* var blob = await node.toBlob({}); * var blob = await node.toBlob({});
* @returns {Promise<Blob>} * @returns {Promise<Blob>}
*/ */
toBlob(config?: { toBlob(config?: ToBlobConfig): Promise<Blob | null> {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
mimeType?: string;
quality?: number;
callback?: (blob: Blob | null) => void;
}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const callback = config?.callback; const callback = config?.callback;
@ -2123,12 +2093,12 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
}); });
} }
setSize(size) { setSize(size: IRect): typeof this {
this.width(size.width); this.width(size.width);
this.height(size.height); this.height(size.height);
return this; return this;
} }
getSize() { getSize(): Size {
return { return {
width: this.width(), width: this.width(),
height: this.height(), height: this.height(),
@ -2140,7 +2110,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getClassName * @name Konva.Node#getClassName
* @returns {String} * @returns {String}
*/ */
getClassName() { getClassName(): string {
return this.className || this.nodeType; return this.className || this.nodeType;
} }
/** /**
@ -2149,7 +2119,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getType * @name Konva.Node#getType
* @returns {String} * @returns {String}
*/ */
getType() { getType(): string {
return this.nodeType; return this.nodeType;
} }
getDragDistance(): number { getDragDistance(): number {
@ -2162,7 +2132,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return Konva.dragDistance; return Konva.dragDistance;
} }
} }
_off(type, name?, callback?) { _off(type, name?, callback?): void {
var evtListeners = this.eventListeners[type], var evtListeners = this.eventListeners[type],
i, i,
evtName, evtName,
@ -2190,7 +2160,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
} }
} }
_fireChangeEvent(attr, oldVal, newVal) { _fireChangeEvent(attr, oldVal, newVal): void {
this._fire(attr + CHANGE, { this._fire(attr + CHANGE, {
oldVal: oldVal, oldVal: oldVal,
newVal: newVal, newVal: newVal,
@ -2207,7 +2177,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* node.addName('selected'); * node.addName('selected');
* node.name(); // return 'red selected' * node.name(); // return 'red selected'
*/ */
addName(name) { addName(name: string): typeof this {
if (!this.hasName(name)) { if (!this.hasName(name)) {
var oldName = this.name(); var oldName = this.name();
var newName = oldName ? oldName + ' ' + name : name; var newName = oldName ? oldName + ' ' + name : name;
@ -2227,7 +2197,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* node.hasName('selected'); // return false * node.hasName('selected'); // return false
* node.hasName(''); // return false * node.hasName(''); // return false
*/ */
hasName(name) { hasName(name: string): boolean {
if (!name) { if (!name) {
return false; return false;
} }
@ -2251,7 +2221,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* node.hasName('selected'); // return false * node.hasName('selected'); // return false
* node.name(); // return 'red' * node.name(); // return 'red'
*/ */
removeName(name) { removeName(name: string): typeof this {
var names = (this.name() || '').split(/\s/g); var names = (this.name() || '').split(/\s/g);
var index = names.indexOf(name); var index = names.indexOf(name);
if (index !== -1) { if (index !== -1) {
@ -2270,7 +2240,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example * @example
* node.setAttr('x', 5); * node.setAttr('x', 5);
*/ */
setAttr(attr, val) { setAttr(attr: string, val: any): typeof this {
var func = this[SET + Util._capitalize(attr)]; var func = this[SET + Util._capitalize(attr)];
if (Util._isFunction(func)) { if (Util._isFunction(func)) {
@ -2281,13 +2251,13 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
return this; return this;
} }
_requestDraw() { _requestDraw(): void {
if (Konva.autoDrawEnabled) { if (Konva.autoDrawEnabled) {
const drawNode = this.getLayer() || this.getStage(); const drawNode = this.getLayer() || this.getStage();
drawNode?.batchDraw(); drawNode?.batchDraw();
} }
} }
_setAttr(key, val) { _setAttr(key: string, val: any): void {
var oldVal = this.attrs[key]; var oldVal = this.attrs[key];
if (oldVal === val && !Util.isObject(val)) { if (oldVal === val && !Util.isObject(val)) {
return; return;
@ -2302,7 +2272,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
this._requestDraw(); this._requestDraw();
} }
_setComponentAttr(key, component, val) { _setComponentAttr(key: string, component: string, val: any): void {
var oldVal; var oldVal;
if (val !== undefined) { if (val !== undefined) {
oldVal = this.attrs[key]; oldVal = this.attrs[key];
@ -2316,7 +2286,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
this._fireChangeEvent(key, oldVal, val); this._fireChangeEvent(key, oldVal, val);
} }
} }
_fireAndBubble(eventType, evt, compareShape?) { _fireAndBubble(eventType, evt, compareShape?): void {
if (evt && this.nodeType === SHAPE) { if (evt && this.nodeType === SHAPE) {
evt.target = this; evt.target = this;
} }
@ -2372,7 +2342,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return events; return events;
} }
_fire(eventType, evt) { _fire(eventType, evt): void {
evt = evt || {}; evt = evt || {};
evt.currentTarget = this; evt.currentTarget = this;
evt.type = eventType; evt.type = eventType;
@ -2399,14 +2369,14 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#draw * @name Konva.Node#draw
* @returns {Konva.Node} * @returns {Konva.Node}
*/ */
draw() { draw(): typeof this {
this.drawScene(); this.drawScene();
this.drawHit(); this.drawHit();
return this; return this;
} }
// drag & drop // drag & drop
_createDragElement(evt) { _createDragElement(evt): void {
var pointerId = evt ? evt.pointerId : undefined; var pointerId = evt ? evt.pointerId : undefined;
var stage = this.getStage(); var stage = this.getStage();
var ap = this.getAbsolutePosition(); var ap = this.getAbsolutePosition();
@ -2434,7 +2404,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @method * @method
* @name Konva.Node#startDrag * @name Konva.Node#startDrag
*/ */
startDrag(evt?: any, bubbleEvent = true) { startDrag(evt?: any, bubbleEvent = true): void {
if (!DD._dragElements.has(this._id)) { if (!DD._dragElements.has(this._id)) {
this._createDragElement(evt); this._createDragElement(evt);
} }
@ -2452,7 +2422,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
); );
} }
_setDragPosition(evt, elem) { _setDragPosition(evt, elem): void {
// const pointers = this.getStage().getPointersPositions(); // const pointers = this.getStage().getPointersPositions();
// const pos = pointers.find(p => p.id === this._dragEventId); // const pos = pointers.find(p => p.id === this._dragEventId);
const pos = this.getStage()!._getPointerById(elem.pointerId); const pos = this.getStage()!._getPointerById(elem.pointerId);
@ -2494,7 +2464,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @method * @method
* @name Konva.Node#stopDrag * @name Konva.Node#stopDrag
*/ */
stopDrag(evt?) { stopDrag(evt?): void {
const elem = DD._dragElements.get(this._id); const elem = DD._dragElements.get(this._id);
if (elem) { if (elem) {
elem.dragStatus = 'stopped'; elem.dragStatus = 'stopped';
@ -2503,7 +2473,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
DD._endDragAfter(evt); DD._endDragAfter(evt);
} }
setDraggable(draggable) { setDraggable(draggable): void {
this._setAttr('draggable', draggable); this._setAttr('draggable', draggable);
this._dragChange(); this._dragChange();
} }
@ -2513,12 +2483,12 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @method * @method
* @name Konva.Node#isDragging * @name Konva.Node#isDragging
*/ */
isDragging() { isDragging(): boolean {
const elem = DD._dragElements.get(this._id); const elem = DD._dragElements.get(this._id);
return elem ? elem.dragStatus === 'dragging' : false; return elem ? elem.dragStatus === 'dragging' : false;
} }
_listenDrag() { _listenDrag(): void {
this._dragCleanup(); this._dragCleanup();
this.on('mousedown.konva touchstart.konva', function (evt) { this.on('mousedown.konva touchstart.konva', function (evt) {
@ -2546,7 +2516,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
}); });
} }
_dragChange() { _dragChange(): void {
if (this.attrs.draggable) { if (this.attrs.draggable) {
this._listenDrag(); this._listenDrag();
} else { } else {
@ -2574,7 +2544,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
} }
} }
_dragCleanup() { _dragCleanup(): void {
this.off('mousedown.konva'); this.off('mousedown.konva');
this.off('touchstart.konva'); this.off('touchstart.konva');
} }

View File

@ -1,3 +1,5 @@
import type { Container } from './Container';
export interface GetSet<Type, This> { export interface GetSet<Type, This> {
(): Type; (): Type;
(v: Type): This; (v: Type): This;
@ -82,3 +84,148 @@ export interface RGB {
export interface RGBA extends RGB { export interface RGBA extends RGB {
a: number; a: number;
} }
export interface Size {
width: number;
height: number;
}
export interface ToCanvasConfig {
/**
* The x coordinate of the canvas section to be exported.
* If omitted, the x coordinate of the node's rect will be used.
*/
x?: number;
/**
* The y coordinate of the canvas section to be exported.
* If omitted, the y coordinate of the node's rect will be used.
*/
y?: number;
/**
* The width of the canvas section to be exported.
* If omitted, the width of the node's rect will be used.
*/
width?: number;
/**
* The height of the canvas section to be exported.
* If omitted, the height of the node's rect will be used.
*/
height?: number;
/**
* The pixel ratio of the of output image.
*
* Use this property to increase resolution of the output image. For example, you may wish to increase the pixel ratio
* to support high resolution (retina) displays.
*
* `pixelRatio` will be used to multiply the size of exported image. For example, if you export a 500x500 section of the canvas
* with `pixelRatio: 2, the exported image will be 1000x1000.
* @default 1
*/
pixelRatio?: number;
/**
* Whether to enable image smoothing.
* @default true
*/
imageSmoothingEnabled?: boolean;
}
export type MIMEType = 'image/jpeg' | 'image/png' | 'image/webp';
interface MIMETypeConfig {
/**
* The MIME type of the exported image. Default is `image/png`.
*
* Browsers may support different MIME types. For example, Firefox and Chromium-based browsers support `image/webp`
* and `image/jpeg` in addition to `image/png`, while Safari supports only `image/png` and `image/jpeg`.
*
* See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#browser_compatibility.
* @default 'image/png'
*/
mimeType?: MIMEType;
}
interface QualityConfig {
/**
* The quality of the exported image. Values from 0 to 1 are supported, where 0 is poorest quality and 1 is best quality.
*
* This only applies to `image/jpeg` and `image/webp` MIME types, which are lossy formats and support quality settings.
* @default 1
*/
quality?: number;
}
export interface ToDataURLConfig
extends ToCanvasConfig,
MIMETypeConfig,
QualityConfig {
/**
* A callback function that will be called with the data URL of the exported image.
* @param dataURL The data URL of the exported image.
* @returns void
*/
callback?: (dataURL: string) => void;
}
export interface ToImageConfig
extends ToCanvasConfig,
MIMETypeConfig,
QualityConfig {
/**
* A callback function that will be called with the exported image element.
* @param image The exported image element.
* @returns void
*/
callback?: (image: HTMLImageElement) => void;
}
export interface ToBlobConfig
extends ToCanvasConfig,
MIMETypeConfig,
QualityConfig {
/**
* A callback function that will be called with the exported blob.
* @param blob The exported blob, or null if the browser was unable to create the blob for any reason.
* @returns void
*/
callback?: (blob: Blob | null) => void;
}
export interface CacheConfig extends ToCanvasConfig {
/**
* When set to `true`, a red border will be drawn around the cached region. Used for debugging.
* @default false
*/
drawBorder?: boolean;
/**
* Increases the size of the cached region by the specified amount of pixels in each direction.
* @default 0
*/
offset?: number;
/**
* The pixel ratio of the cached hit canvas. Lower pixel ratios can result in better performance, but less accurate hit detection.
* @default 1
*/
hitCanvasPixelRatio?: number;
}
export interface GetClientRectConfig {
/**
* Whether to apply the current node's transforms when calculating the client rect.
* @default false
*/
skipTransform?: boolean;
/**
* Whether to apply shadow to the node when calculating the client rect.
* @default false
*/
skipShadow?: boolean;
/**
* Whether to apply stroke to the node when calculating the client rect.
* @default false
*/
skipStroke?: boolean;
/**
* If provided, the client rect will be calculated relative to the specified container. Must be a parent of the node.
*/
relativeTo?: Container;
}