Better types inferences for Rect attributes

This commit is contained in:
Jérémy Melchor
2025-10-09 09:53:02 +02:00
parent ee8a5e8904
commit 962f74dc3b
8 changed files with 27 additions and 21 deletions

1
.gitignore vendored
View File

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

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

@@ -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;
@@ -1015,13 +1014,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 +1049,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 +1064,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 +1623,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,
@@ -2405,8 +2404,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 +2421,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 +2877,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

@@ -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(),