Merge branch 'master' of github.com:konvajs/konva

This commit is contained in:
Anton Lavrevov
2025-10-21 14:52:42 -05:00
13 changed files with 72 additions and 67 deletions

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ types
out.png
cmj
.test-temp
.history
# Numerous always-ignore extensions
*.diff

View File

@@ -1,5 +1,5 @@
<p align="center">
<img src="https://konvajs.org/android-chrome-192x192.png" alt="Konva logo" height="180" />
<img src="https://konvajs.org/img/icon.png" alt="Konva logo" height="60" />
</p>
<h1 align="center">Konva</h1>

View File

@@ -44,13 +44,13 @@
"test": "npm run test:browser && npm run test:node && npm run test:import",
"test:build": "PARCEL_WORKER_BACKEND=process parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps",
"test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security -a no-sandbox -a disable-setuid-sandbox",
"test:watch": "rm -rf ./.parcel-cache && PARCEL_WORKERS=0 parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html",
"test:watch": "rimraf ./.parcel-cache && PARCEL_WORKERS=0 parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html",
"test:node:canvas": "mocha -r tsx -r ./test/node-canvas-global-setup.mjs --extension ts --recursive './test/unit/' --exit",
"test:node:skia": "mocha -r tsx -r ./test/node-skia-global-setup.mjs --extension ts --recursive './test/unit/' --exit",
"test:node": "npm run test:node:canvas && npm run test:node:skia",
"tsc": "tsc --removeComments",
"rollup": "rollup -c",
"clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build",
"clean": "rimraf ./lib && rimraf ./types && rimraf ./cmj && rimraf ./test-build",
"watch": "rollup -c -w",
"size": "size-limit"
},
@@ -107,6 +107,7 @@
"parcel": "2.15.4",
"prettier": "^3.6.2",
"process": "^0.11.10",
"rimraf": "^6.0.1",
"rollup": "^4.48.0",
"rollup-plugin-typescript2": "^0.36.0",
"size-limit": "^11.2.0",

View File

@@ -32,7 +32,8 @@ export interface ContainerConfig extends NodeConfig {
*/
export abstract class Container<
ChildType extends Node = Node,
> extends Node<ContainerConfig> {
Config extends ContainerConfig = ContainerConfig
> extends Node<Config> {
children: Array<ChildType> = [];
/**

View File

@@ -196,7 +196,7 @@ export const Konva = {
_injectGlobal(Konva) {
if (typeof glob.Konva !== 'undefined') {
console.error(
'Severa Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.'
'Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.'
);
}
glob.Konva = Konva;

View File

@@ -10,7 +10,7 @@ import type { Layer } from './Layer.ts';
import type { Shape } from './Shape.ts';
import type { Stage } from './Stage.ts';
import type { GetSet, IRect, Vector2d } from './types.ts';
import { Transform, Util } from './Util.ts';
import { Transform, Util, type AnyString } from './Util.ts';
import {
getBooleanValidator,
getNumberValidator,
@@ -134,9 +134,8 @@ type globalCompositeOperationType =
| 'color'
| 'luminosity';
export interface NodeConfig {
// allow any custom attribute
[index: string]: any;
// allow any custom attribute
export type NodeConfig<Props extends Record<string, any> = {}> = Props & {
x?: number;
y?: number;
width?: number;
@@ -223,6 +222,20 @@ export type KonvaEventListener<This, EventType> = (
ev: KonvaEventObject<EventType, This>
) => void;
export type CanvasConfig = {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
imageSmoothingEnabled?: boolean;
};
export type ImageConfig = CanvasConfig & {
mimeType?: string;
quality?: number;
};
/**
* Node constructor. Nodes are entities that can be transformed, layered,
* and have bound events. The stage, layers, groups, and shapes all extend Node.
@@ -393,17 +406,13 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* drawBorder: true
* });
*/
cache(config?: {
x?: number;
y?: number;
width?: number;
height?: number;
drawBorder?: boolean;
offset?: number;
pixelRatio?: number;
imageSmoothingEnabled?: boolean;
hitCanvasPixelRatio?: number;
}) {
cache(
config?: CanvasConfig & {
drawBorder?: boolean;
offset?: number;
hitCanvasPixelRatio?: number;
}
) {
const conf = config || {};
let rect = {} as IRect;
@@ -1015,13 +1024,13 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example
* var x = node.getAttr('x');
*/
getAttr<T>(attr: string) {
const method = 'get' + Util._capitalize(attr);
getAttr<AttrConfig extends Config, K extends AnyString<keyof Config>>(attr: K): K extends keyof AttrConfig ? AttrConfig[K] : any {
const method = 'get' + Util._capitalize(attr as string);
if (Util._isFunction((this as any)[method])) {
return (this as any)[method]();
}
// otherwise get directly
return this.attrs[attr] as T | undefined;
return this.attrs[attr];
}
/**
* get ancestors
@@ -1050,8 +1059,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @name Konva.Node#getAttrs
* @returns {Object}
*/
getAttrs() {
return (this.attrs || {}) as Config & Record<string, any>;
getAttrs(): Config {
return (this.attrs || {});
}
/**
* set multiple attrs at once using an object literal
@@ -1065,7 +1074,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* fill: 'red'
* });
*/
setAttrs(config: any) {
setAttrs(config?: Config) {
this._batchTransformChanges(() => {
let key, method;
if (!config) {
@@ -1624,7 +1633,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @returns {Object}
*/
toObject() {
let attrs = this.getAttrs() as any,
let attrs = this.getAttrs(),
key,
val,
getter,
@@ -2111,7 +2120,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example
* var canvas = node.toCanvas();
*/
toCanvas(config?) {
toCanvas(config?: CanvasConfig) {
return this._toKonvaCanvas(config)._canvas;
}
/**
@@ -2137,16 +2146,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @param {Boolean} [config.imageSmoothingEnabled] set this to false if you want to disable imageSmoothing
* @returns {String}
*/
toDataURL(config?: {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
mimeType?: string;
quality?: number;
callback?: (str: string) => void;
}) {
toDataURL(
config?: ImageConfig & {
callback?: (url: string) => void;
}
) {
config = config || {};
const mimeType = config.mimeType || null,
quality = config.quality || null;
@@ -2187,16 +2191,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* }
* });
*/
toImage(config?: {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
mimeType?: string;
quality?: number;
callback?: (img: HTMLImageElement) => void;
}) {
toImage(
config?: ImageConfig & {
callback?: (img: HTMLImageElement) => void;
}
) {
return new Promise<HTMLImageElement>((resolve, reject) => {
try {
const callback = config?.callback;
@@ -2231,16 +2230,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* var blob = await node.toBlob({});
* @returns {Promise<Blob>}
*/
toBlob(config?: {
x?: number;
y?: number;
width?: number;
height?: number;
pixelRatio?: number;
mimeType?: string;
quality?: number;
callback?: (blob: Blob | null) => void;
}) {
toBlob(
config?: ImageConfig & {
callback?: (blob: Blob | null) => void;
}
) {
return new Promise((resolve, reject) => {
try {
const callback = config?.callback;
@@ -2405,8 +2399,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example
* node.setAttr('x', 5);
*/
setAttr(attr: string, val) {
const func = this[SET + Util._capitalize(attr)];
setAttr<AttrConfig extends Config, K extends AnyString<keyof Config>>(attr: K, val: K extends keyof AttrConfig ? AttrConfig[K] : any) {
const func = this[SET + Util._capitalize(attr as string)];
if (Util._isFunction(func)) {
func.call(this, val);
@@ -2422,7 +2416,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
drawNode?.batchDraw();
}
}
_setAttr(key: string, val) {
_setAttr<AttrConfig extends Config, K extends AnyString<keyof Config>>(key: K, val: K extends keyof AttrConfig ? AttrConfig[K] : any) {
const oldVal = this.attrs[key];
if (oldVal === val && !Util.isObject(val)) {
return;
@@ -2878,7 +2872,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
}
}
interface AnimTo extends NodeConfig {
interface AnimTo extends NodeConfig<Record<string, any>> {
onFinish?: Function;
onUpdate?: Function;
duration?: number;

View File

@@ -26,7 +26,7 @@ export type ShapeConfigHandler<TTarget> = {
export type LineJoin = 'round' | 'bevel' | 'miter';
export type LineCap = 'butt' | 'round' | 'square';
export interface ShapeConfig extends NodeConfig {
export type ShapeConfig<Props extends Record<string, any> = {}> = NodeConfig<Props> & {
fill?: string | CanvasGradient;
fillPatternImage?: HTMLImageElement;
fillPatternX?: number;

View File

@@ -154,7 +154,7 @@ export const stages: Stage[] = [];
* });
*/
export class Stage extends Container<Layer> {
export class Stage extends Container<Layer, StageConfig> {
content: HTMLDivElement;
pointerPos: Vector2d | null;
_pointerPositions: (Vector2d & { id?: number })[] = [];

View File

@@ -151,6 +151,9 @@ class TweenEngine {
}
export interface TweenConfig extends NodeConfig {
easing?: typeof Easings[keyof typeof Easings];
yoyo?: boolean;
onReset?: Function;
onFinish?: Function;
onUpdate?: Function;
duration?: number;
@@ -419,7 +422,7 @@ export class Tween {
// after tweening points of line we need to set original end
const attrs = Tween.attrs[node._id][this._id];
if (attrs.points && attrs.points.trueEnd) {
node.setAttr('points', attrs.points.trueEnd);
node.setAttr('points' as any, attrs.points.trueEnd);
}
if (this.onFinish) {

View File

@@ -1121,3 +1121,5 @@ export const Util = {
}
},
};
export type AnyString<T> = T | (string & {})

View File

@@ -7,6 +7,7 @@ const canvas = Canvas['default'] || Canvas;
// @ts-ignore
global.DOMMatrix = canvas.DOMMatrix;
// @ts-ignore
(global as any).Path2D ??= class Path2D {
constructor(path: any) {
(this as any).path = path;

View File

@@ -8,7 +8,7 @@ import type { GetSet } from '../types.ts';
import type { Context } from '../Context.ts';
import { getNumberOrArrayOfNumbersValidator } from '../Validators.ts';
export interface RectConfig extends ShapeConfig {
export type RectConfig<Props extends Record<string, any> = {}> = ShapeConfig<Props> & {
cornerRadius?: number | number[];
}
@@ -30,7 +30,7 @@ export interface RectConfig extends ShapeConfig {
* strokeWidth: 5
* });
*/
export class Rect extends Shape<RectConfig> {
export class Rect<Props extends Record<string, any> = {}> extends Shape<RectConfig<Props>> {
_sceneFunc(context: Context) {
const cornerRadius = this.cornerRadius(),
width = this.width(),

View File

@@ -2,8 +2,10 @@ import { Konva } from './_CoreInternals.ts';
// @ts-ignore
import { Canvas, DOMMatrix, Image, Path2D } from 'skia-canvas';
// @ts-ignore
global.DOMMatrix = DOMMatrix as any;
// @ts-ignore
global.Path2D = Path2D as any;
Path2D.prototype.toString = () => '[object Path2D]';