diff --git a/CHANGELOG.md b/CHANGELOG.md index 29cc0699104..8f9a681bac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- chore(TS): Observable types [#8431](https://github.com/fabricjs/fabric.js/pull/8431) - chore(TS): migrate Group/ActiveSelection [#8455](https://github.com/fabricjs/fabric.js/pull/8455) - fix(TS): migration error of itext key mixin (#8421) [#8457](https://github.com/fabricjs/fabric.js/pull/8457) - chore(TS): migrate text classes/mixins [#8421](https://github.com/fabricjs/fabric.js/pull/8421) diff --git a/src/EventTypeDefs.ts b/src/EventTypeDefs.ts new file mode 100644 index 00000000000..9bbbb975934 --- /dev/null +++ b/src/EventTypeDefs.ts @@ -0,0 +1,234 @@ +import type { Control } from './controls/control.class'; +import type { Point } from './point.class'; +import type { FabricObject } from './shapes/fabricObject.class'; +import type { Group } from './shapes/group.class'; +import type { TOriginX, TOriginY, TRadian } from './typedefs'; +import type { saveObjectTransform } from './util/misc/objectTransforms'; +import type { Canvas } from './__types__'; +import type { IText } from './shapes/itext.class'; + +export type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey'; + +export type TPointerEvent = MouseEvent | TouchEvent; + +export type TransformAction = ( + eventData: TPointerEvent, + transform: T, + x: number, + y: number +) => R; + +export type TransformActionHandler = + TransformAction; + +export type ControlCallback = ( + eventData: TPointerEvent, + control: Control, + fabricObject: FabricObject +) => R; + +export type ControlCursorCallback = ControlCallback; + +/** + * relative to target's containing coordinate plane + * both agree on every point + */ +export type Transform = { + target: FabricObject; + action: string; + actionHandler: TransformActionHandler; + corner: string; + scaleX: number; + scaleY: number; + skewX: number; + skewY: number; + offsetX: number; + offsetY: number; + originX: TOriginX; + originY: TOriginY; + ex: number; + ey: number; + lastX: number; + lastY: number; + theta: TRadian; + width: number; + height: number; + shiftKey: boolean; + altKey: boolean; + original: ReturnType; +}; + +export type TEvent = { + e: E; +}; + +export type BasicTransformEvent = TEvent & { + transform: Transform; + pointer: Point; +}; + +export type TModificationEvents = + | 'moving' + | 'scaling' + | 'rotating' + | 'skewing' + | 'resizing'; + +type ObjectModifiedEvents = Record & { + modified: BasicTransformEvent | never; +}; + +type CanvasModifiedEvents = Record< + `object:${keyof ObjectModifiedEvents}`, + BasicTransformEvent & { target: FabricObject } +>; + +export type TransformEvent = + BasicTransformEvent & { + target: FabricObject; + subTargets: FabricObject[]; + button: number; + isClick: boolean; + pointer: Point; + absolutePointer: Point; + }; + +type SimpleEventHandler = TEvent & { + target: FabricObject; + subTargets: FabricObject[]; +}; + +type InEvent = { + previousTarget?: FabricObject; +}; + +type OutEvent = { + nextTarget?: FabricObject; +}; + +type DragEventData = TEvent & { + target: FabricObject; + subTargets?: FabricObject[]; + dragSource?: FabricObject; + canDrop?: boolean; + dropTarget?: FabricObject; +}; + +type DropEventData = DragEventData & { pointer: Point }; + +type DnDEvents = { + dragstart: TEvent & { target: FabricObject }; + drag: DragEventData; + dragover: DragEventData; + dragenter: DragEventData & InEvent; + dragleave: DragEventData & OutEvent; + dragend: DragEventData; + 'drop:before': DropEventData; + drop: DropEventData; + 'drop:after': DropEventData; +}; + +type CanvasDnDEvents = DnDEvents & { + 'drag:enter': DragEventData & InEvent; + 'drag:leave': DragEventData & OutEvent; +}; + +type CanvasSelectionEvents = { + 'selection:created': TEvent & { + selected: FabricObject[]; + }; + 'selection:updated': TEvent & { + selected: FabricObject[]; + deselected: FabricObject[]; + }; + 'before:selection:cleared': Partial & { + deselected: FabricObject[]; + }; + 'selection:cleared': Partial & { + deselected: FabricObject[]; + }; +}; + +type BeforeSuffix = `${T}:before`; +type WithBeforeSuffix = T | BeforeSuffix; + +type TPointerEvents> = Record< + `${Prefix}${ + | WithBeforeSuffix<'down'> + | WithBeforeSuffix<'move'> + | WithBeforeSuffix<'up'> + | 'dblclick'}`, + TransformEvent & E +> & + Record<`${Prefix}wheel`, TransformEvent & E> & + Record<`${Prefix}over`, TransformEvent & InEvent & E> & + Record<`${Prefix}out`, TransformEvent & OutEvent & E>; + +export type ObjectPointerEvents = TPointerEvents<'mouse'>; +export type CanvasPointerEvents = TPointerEvents<'mouse:'>; + +export type ObjectEvents = ObjectPointerEvents & + DnDEvents & + ObjectModifiedEvents & { + // selection + selected: { + e: TEvent; + target: FabricObject; + }; + deselected: { + e?: TEvent; + target: FabricObject; + }; + + // tree + added: { target: Group | Canvas }; + removed: { target: Group | Canvas }; + + // erasing + 'erasing:end': { path: FabricObject }; + }; + +export type StaticCanvasEvents = { + // tree + 'object:added': { target: FabricObject }; + 'object:removed': { target: FabricObject }; + 'canvas:cleared': never; + + // rendering + 'before:render': { ctx: CanvasRenderingContext2D }; + 'after:render': { ctx: CanvasRenderingContext2D }; +}; + +export type CanvasEvents = StaticCanvasEvents & + CanvasPointerEvents & + CanvasDnDEvents & + CanvasModifiedEvents & + CanvasSelectionEvents & { + // brushes + 'before:path:created': { path: FabricObject }; + 'path:created': { path: FabricObject }; + + // erasing + 'erasing:start': never; + 'erasing:end': + | never + | { + path: FabricObject; + targets: FabricObject[]; + subTargets: FabricObject[]; + drawables: { + backgroundImage?: FabricObject; + overlayImage?: FabricObject; + }; + }; + + // IText + 'text:selection:changed': { target: IText }; + 'text:changed': { target: IText }; + 'text:editing:entered': { target: IText }; + 'text:editing:exited': { target: IText }; + + // misc + 'contextmenu:before': SimpleEventHandler; + contextmenu: SimpleEventHandler; + }; diff --git a/src/__types__.ts b/src/__types__.ts index 64671567cb8..28323dc80ab 100644 --- a/src/__types__.ts +++ b/src/__types__.ts @@ -1,6 +1,7 @@ +import { CanvasEvents, ModifierKey } from './EventTypeDefs'; import type { Observable } from './mixins/observable.mixin'; import type { Point } from './point.class'; -import { ModifierKey, TMat2D } from './typedefs'; +import { TMat2D } from './typedefs'; /** * @todo remove transient @@ -18,4 +19,4 @@ export type StaticCanvas = Record & { br: Point; }; getRetinaScaling(): number; -} & Observable; +} & Observable; diff --git a/src/brushes/pencil_brush.class.ts b/src/brushes/pencil_brush.class.ts index 00cbf4e2b81..8cac3585d3c 100644 --- a/src/brushes/pencil_brush.class.ts +++ b/src/brushes/pencil_brush.class.ts @@ -1,8 +1,9 @@ import { fabric } from '../../HEADER'; +import { ModifierKey, TEvent } from '../EventTypeDefs'; import { Point } from '../point.class'; import { Shadow } from '../shadow.class'; import { Path } from '../shapes/path.class'; -import { TEvent, ModifierKey, PathData } from '../typedefs'; +import { PathData } from '../typedefs'; import { getSmoothPathFromPoints, joinPath } from '../util/path'; import { Canvas } from '../__types__'; import { BaseBrush } from './base_brush.class'; diff --git a/src/canvas.class.ts b/src/canvas.class.ts index a5d8a2f8312..3e1a8f967a7 100644 --- a/src/canvas.class.ts +++ b/src/canvas.class.ts @@ -2,7 +2,7 @@ import { dragHandler, getActionFromCorner } from './controls/actions'; import { Point } from './point.class'; import { FabricObject } from './shapes/fabricObject.class'; -import { Transform } from './typedefs'; +import { Transform } from './EventTypeDefs'; import { saveObjectTransform } from './util/misc/objectTransforms'; (function (global) { @@ -441,10 +441,12 @@ import { saveObjectTransform } from './util/misc/objectTransforms'; this._objectsToRender = undefined; // removing active object should fire "selection:cleared" events if (obj === this._activeObject) { - this.fire('before:selection:cleared', { target: obj }); + this.fire('before:selection:cleared', { deselected: [obj] }); this._discardActiveObject(); - this.fire('selection:cleared', { target: obj }); - obj.fire('deselected'); + this.fire('selection:cleared', { deselected: [obj] }); + obj.fire('deselected', { + target: obj, + }); } if (obj === this._hoveredTarget) { this._hoveredTarget = null; @@ -548,7 +550,7 @@ import { saveObjectTransform } from './util/misc/objectTransforms'; var ctx = this.contextTop; this.clearContext(ctx); this.renderTopLayer(ctx); - this.fire('after:render'); + this.fire('after:render', { ctx }); return this; }, @@ -1363,7 +1365,10 @@ import { saveObjectTransform } from './util/misc/objectTransforms'; var currentActives = this.getActiveObjects(), activeObject = this.getActiveObject(); if (currentActives.length) { - this.fire('before:selection:cleared', { target: activeObject, e: e }); + this.fire('before:selection:cleared', { + e, + deselected: [activeObject], + }); } this._discardActiveObject(e); this._fireSelectionEvents(currentActives, e); diff --git a/src/controls/changeWidth.ts b/src/controls/changeWidth.ts index 3f3ad72017e..1f6549b3707 100644 --- a/src/controls/changeWidth.ts +++ b/src/controls/changeWidth.ts @@ -1,4 +1,4 @@ -import { TransformActionHandler } from '../typedefs'; +import { TransformActionHandler } from '../EventTypeDefs'; import { getLocalPoint, isTransformCentered } from './util'; import { wrapWithFireEvent } from './wrapWithFireEvent'; import { wrapWithFixedAnchor } from './wrapWithFixedAnchor'; diff --git a/src/controls/control.class.ts b/src/controls/control.class.ts index 1a76a1789af..8f3067f917d 100644 --- a/src/controls/control.class.ts +++ b/src/controls/control.class.ts @@ -1,15 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { fabric } from '../../HEADER'; import { halfPI } from '../constants'; -import { Point } from '../point.class'; -import type { FabricObject } from '../shapes/object.class'; import { - TDegree, - TMat2D, TPointerEvent, TransformAction, TransformActionHandler, -} from '../typedefs'; +} from '../EventTypeDefs'; +import { Point } from '../point.class'; +import type { FabricObject } from '../shapes/object.class'; +import { TDegree, TMat2D } from '../typedefs'; import { cos } from '../util/misc/cos'; import { degreesToRadians } from '../util/misc/radiansDegreesConversion'; import { sin } from '../util/misc/sin'; diff --git a/src/controls/drag.ts b/src/controls/drag.ts index 91dcebe1244..435cd018a3f 100644 --- a/src/controls/drag.ts +++ b/src/controls/drag.ts @@ -1,4 +1,4 @@ -import { TransformActionHandler } from '../typedefs'; +import { TransformActionHandler } from '../EventTypeDefs'; import { fireEvent } from '../util/fireEvent'; import { commonEventInfo, isLocked } from './util'; diff --git a/src/controls/rotate.ts b/src/controls/rotate.ts index a8c5a39fc3b..8ff9e35061a 100644 --- a/src/controls/rotate.ts +++ b/src/controls/rotate.ts @@ -1,6 +1,7 @@ -// @ts-nocheck - -import { ControlCursorCallback, TransformActionHandler } from '../typedefs'; +import { + ControlCursorCallback, + TransformActionHandler, +} from '../EventTypeDefs'; import { radiansToDegrees } from '../util/misc/radiansDegreesConversion'; import { isLocked, NOT_ALLOWED_CURSOR } from './util'; import { wrapWithFireEvent } from './wrapWithFireEvent'; diff --git a/src/controls/scale.ts b/src/controls/scale.ts index 557104c95fb..2c42f97613e 100644 --- a/src/controls/scale.ts +++ b/src/controls/scale.ts @@ -1,11 +1,11 @@ -import type { FabricObject } from '../shapes/fabricObject.class'; import { ControlCursorCallback, - TAxis, TPointerEvent, Transform, TransformActionHandler, -} from '../typedefs'; +} from '../EventTypeDefs'; +import type { FabricObject } from '../shapes/fabricObject.class'; +import { TAxis } from '../typedefs'; import { Canvas } from '../__types__'; import { findCornerQuadrant, diff --git a/src/controls/scaleSkew.ts b/src/controls/scaleSkew.ts index f9ef737b836..4b1e5500e52 100644 --- a/src/controls/scaleSkew.ts +++ b/src/controls/scaleSkew.ts @@ -1,11 +1,11 @@ -import type { FabricObject } from '../shapes/object.class'; import { ControlCallback, ControlCursorCallback, - TAxisKey, TPointerEvent, TransformActionHandler, -} from '../typedefs'; +} from '../EventTypeDefs'; +import type { FabricObject } from '../shapes/object.class'; +import { TAxisKey } from '../typedefs'; import { Canvas } from '../__types__'; import { scaleCursorStyleHandler, scalingX, scalingY } from './scale'; import { skewCursorStyleHandler, skewHandlerX, skewHandlerY } from './skew'; diff --git a/src/controls/skew.ts b/src/controls/skew.ts index 832df5abd94..28d782c636e 100644 --- a/src/controls/skew.ts +++ b/src/controls/skew.ts @@ -1,13 +1,12 @@ -import { resolveOrigin } from '../mixins/object_origin.mixin'; -import { Point } from '../point.class'; import { ControlCursorCallback, - TAxis, - TAxisKey, TPointerEvent, Transform, TransformActionHandler, -} from '../typedefs'; +} from '../EventTypeDefs'; +import { resolveOrigin } from '../mixins/object_origin.mixin'; +import { Point } from '../point.class'; +import { TAxis, TAxisKey } from '../typedefs'; import { degreesToRadians, radiansToDegrees, diff --git a/src/controls/util.ts b/src/controls/util.ts index 198e9caca0b..19152d18c22 100644 --- a/src/controls/util.ts +++ b/src/controls/util.ts @@ -1,14 +1,13 @@ -import { resolveOrigin } from '../mixins/object_origin.mixin'; -import { Point } from '../point.class'; -import type { FabricObject } from '../shapes/fabricObject.class'; import { - TOriginX, - TOriginY, TPointerEvent, Transform, TransformAction, - TransformEvent, -} from '../typedefs'; + BasicTransformEvent, +} from '../EventTypeDefs'; +import { resolveOrigin } from '../mixins/object_origin.mixin'; +import { Point } from '../point.class'; +import type { FabricObject } from '../shapes/fabricObject.class'; +import { TOriginX, TOriginY } from '../typedefs'; import { degreesToRadians, radiansToDegrees, @@ -62,12 +61,10 @@ export const isLocked = ( | 'lockScalingFlip' ) => target[lockingKey]; -export const commonEventInfo: TransformAction = ( - eventData, - transform, - x, - y -) => { +export const commonEventInfo: TransformAction< + Transform, + BasicTransformEvent +> = (eventData, transform, x, y) => { return { e: eventData, transform, diff --git a/src/controls/wrapWithFireEvent.ts b/src/controls/wrapWithFireEvent.ts index c3fba2c77cd..1d1c33b698e 100644 --- a/src/controls/wrapWithFireEvent.ts +++ b/src/controls/wrapWithFireEvent.ts @@ -1,4 +1,8 @@ -import { Transform, TransformActionHandler } from '../typedefs'; +import { + TModificationEvents, + Transform, + TransformActionHandler, +} from '../EventTypeDefs'; import { fireEvent } from '../util/fireEvent'; import { commonEventInfo } from './util'; @@ -8,7 +12,7 @@ import { commonEventInfo } from './util'; * @return {Function} a function with an action handler signature */ export const wrapWithFireEvent = ( - eventName: string, + eventName: TModificationEvents, actionHandler: TransformActionHandler ) => { return ((eventData, transform, x, y) => { diff --git a/src/controls/wrapWithFixedAnchor.ts b/src/controls/wrapWithFixedAnchor.ts index a351c408ef9..cf9eb87e68c 100644 --- a/src/controls/wrapWithFixedAnchor.ts +++ b/src/controls/wrapWithFixedAnchor.ts @@ -1,4 +1,4 @@ -import { Transform, TransformActionHandler } from '../typedefs'; +import { Transform, TransformActionHandler } from '../EventTypeDefs'; /** * Wrap an action handler with saving/restoring object position on the transform. diff --git a/src/mixins/itext_behavior.mixin.ts b/src/mixins/itext_behavior.mixin.ts index 51c623d86cb..cf765f69792 100644 --- a/src/mixins/itext_behavior.mixin.ts +++ b/src/mixins/itext_behavior.mixin.ts @@ -1,9 +1,10 @@ // @ts-nocheck import { fabric } from '../../HEADER'; +import { ObjectEvents, TEvent } from '../EventTypeDefs'; import { Point } from '../point.class'; import { Text } from '../shapes/text.class'; -import { TEvent, TPointerEvent } from '../typedefs'; +import { TPointerEvent } from '../typedefs'; import { setStyle } from '../util/dom_style'; import { removeFromArray } from '../util/internals'; import { createCanvasElement } from '../util/misc/dom'; @@ -14,7 +15,9 @@ import { TextStyleDeclaration } from './text_style.mixin'; // extend this regex to support non english languages const reNonWord = /[ \n\.,;!\?\-]/; -export abstract class ITextBehaviorMixin extends Text { +export abstract class ITextBehaviorMixin< + EventSpec extends ObjectEvents +> extends Text { abstract isEditing: boolean; abstract cursorDelay: number; abstract selectionStart: number; diff --git a/src/mixins/itext_click_behavior.mixin.ts b/src/mixins/itext_click_behavior.mixin.ts index 65e60a8defc..6cc5188304f 100644 --- a/src/mixins/itext_click_behavior.mixin.ts +++ b/src/mixins/itext_click_behavior.mixin.ts @@ -1,11 +1,14 @@ //@ts-nocheck +import { ObjectEvents } from '../EventTypeDefs'; import { IPoint, Point } from '../point.class'; import { TPointerEvent, TransformEvent } from '../typedefs'; import { stopEvent } from '../util/dom_event'; import { invertTransform, transformPoint } from '../util/misc/matrix'; import { ITextKeyBehaviorMixin } from './itext_key_behavior.mixin'; -export abstract class ITextClickBehaviorMixin extends ITextKeyBehaviorMixin { +export abstract class ITextClickBehaviorMixin< + EventSpec extends ObjectEvents +> extends ITextKeyBehaviorMixin { private __lastClickTime: number; private __lastLastClickTime: number; private __lastPointer: IPoint | Record; diff --git a/src/mixins/itext_key_behavior.mixin.ts b/src/mixins/itext_key_behavior.mixin.ts index ba45312b364..2aeed7489f6 100644 --- a/src/mixins/itext_key_behavior.mixin.ts +++ b/src/mixins/itext_key_behavior.mixin.ts @@ -2,12 +2,15 @@ import { fabric } from '../../HEADER'; import { config } from '../config'; +import { ObjectEvents } from '../EventTypeDefs'; import { TPointerEvent } from '../typedefs'; import { capValue } from '../util/misc/capValue'; import { ITextBehaviorMixin } from './itext_behavior.mixin'; import type { TKeyMapIText } from './itext_key_const'; -export abstract class ITextKeyBehaviorMixin extends ITextBehaviorMixin { +export abstract class ITextKeyBehaviorMixin< + EventSpec extends ObjectEvents +> extends ITextBehaviorMixin { /** * For functionalities on keyDown * Map a special key to a function of the instance/prototype diff --git a/src/mixins/object_geometry.mixin.ts b/src/mixins/object_geometry.mixin.ts index 77e49400bb3..9d69bd7ba1d 100644 --- a/src/mixins/object_geometry.mixin.ts +++ b/src/mixins/object_geometry.mixin.ts @@ -23,6 +23,7 @@ import { degreesToRadians } from '../util/misc/radiansDegreesConversion'; import { sin } from '../util/misc/sin'; import { Canvas, StaticCanvas } from '../__types__'; import { ObjectOrigin } from './object_origin.mixin'; +import { ObjectEvents } from '../EventTypeDefs'; type TLineDescriptor = { o: Point; @@ -43,7 +44,9 @@ type TMatrixCache = { type TACoords = TCornerPoint; -export class ObjectGeometry extends ObjectOrigin { +export class ObjectGeometry< + EventSpec extends ObjectEvents = ObjectEvents +> extends ObjectOrigin { /** * When true, an object is rendered as flipped horizontally * @type Boolean diff --git a/src/mixins/object_interactivity.mixin.ts b/src/mixins/object_interactivity.mixin.ts index fad7486b3b4..3885641e8fe 100644 --- a/src/mixins/object_interactivity.mixin.ts +++ b/src/mixins/object_interactivity.mixin.ts @@ -11,6 +11,7 @@ import { import { ObjectGeometry } from './object_geometry.mixin'; import type { Control } from '../controls/control.class'; import { sizeAfterTransform } from '../util/misc/objectTransforms'; +import { ObjectEvents } from '../EventTypeDefs'; type TOCoord = IPoint & { corner: TCornerPoint; @@ -19,7 +20,9 @@ type TOCoord = IPoint & { type TControlSet = Record; -export class InteractiveFabricObject extends FabricObject { +export class InteractiveFabricObject< + EventSpec extends ObjectEvents = ObjectEvents +> extends FabricObject { /** * Describe object's corner position in canvas element coordinates. * properties are depending on control keys and padding the main controls. diff --git a/src/mixins/object_origin.mixin.ts b/src/mixins/object_origin.mixin.ts index 37ac9e11afe..6f3e3637929 100644 --- a/src/mixins/object_origin.mixin.ts +++ b/src/mixins/object_origin.mixin.ts @@ -1,10 +1,10 @@ import { Point } from '../point.class'; +import type { Group } from '../shapes/group.class'; +import { TDegree, TOriginX, TOriginY } from '../typedefs'; import { transformPoint } from '../util/misc/matrix'; +import { sizeAfterTransform } from '../util/misc/objectTransforms'; import { degreesToRadians } from '../util/misc/radiansDegreesConversion'; import { CommonMethods } from './shared_methods.mixin'; -import { TDegree, TOriginX, TOriginY } from '../typedefs'; -import { Group } from '../shapes/group.class'; -import { sizeAfterTransform } from '../util/misc/objectTransforms'; const originOffset = { left: -0.5, @@ -27,7 +27,7 @@ export const resolveOrigin = ( ? originOffset[originValue] : originValue - 0.5; -export class ObjectOrigin extends CommonMethods { +export class ObjectOrigin extends CommonMethods { /** * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} * @type Number diff --git a/src/mixins/observable.mixin.ts b/src/mixins/observable.mixin.ts index b0236b13915..659b74c56ff 100644 --- a/src/mixins/observable.mixin.ts +++ b/src/mixins/observable.mixin.ts @@ -1,15 +1,19 @@ -//@ts-nocheck - import { fabric } from '../../HEADER'; -type EventRegistryObject = Record; +export type TEventCallback = (options: T) => any; + +type EventRegistryObject< + K extends string | number | symbol = string, + E = any +> = Record>; /** * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events} * @see {@link http://fabricjs.com/events|Events demo} */ -export class Observable { - private __eventListeners: Record = {}; +export class Observable { + private __eventListeners: Record = + {} as Record; /** * Observes specified event @@ -19,16 +23,28 @@ export class Observable { * @param {Function} handler Function that receives a notification when an event of the specified type occurs * @return {Function} disposer */ - on(eventName: string, handler: Function): Function; - on(handlers: EventRegistryObject): Function; - on(arg0: string | EventRegistryObject, handler?: Function): Function { + on( + eventName: K, + handler: TEventCallback + ): VoidFunction; + on( + eventName: K, + handler: TEventCallback + ): VoidFunction; + on( + handlers: EventRegistryObject + ): VoidFunction; + on( + arg0: K | EventRegistryObject, + handler?: TEventCallback + ): VoidFunction { if (!this.__eventListeners) { - this.__eventListeners = {}; + this.__eventListeners = {} as Record; } if (typeof arg0 === 'object') { // one object with key/value pairs was passed for (const eventName in arg0) { - this.on(eventName, arg0[eventName]); + this.on(eventName as K, arg0[eventName]); } return () => this.off(arg0); } else if (handler) { @@ -52,18 +68,30 @@ export class Observable { * @param {Function} handler Function that receives a notification when an event of the specified type occurs * @return {Function} disposer */ - once(eventName: string, handler: Function): Function; - once(handlers: EventRegistryObject): Function; - once(arg0: string | EventRegistryObject, handler?: Function): Function { + once( + eventName: K, + handler: TEventCallback + ): VoidFunction; + once( + eventName: K, + handler: TEventCallback + ): VoidFunction; + once( + handlers: EventRegistryObject + ): VoidFunction; + once( + arg0: K | EventRegistryObject, + handler?: TEventCallback + ): VoidFunction { if (typeof arg0 === 'object') { // one object with key/value pairs was passed - const disposers: Function[] = []; + const disposers: VoidFunction[] = []; for (const eventName in arg0) { - disposers.push(this.once(eventName, arg0[eventName])); + disposers.push(this.once(eventName as K, arg0[eventName])); } return () => disposers.forEach((d) => d()); } else if (handler) { - const disposer = this.on(arg0, (...args: any[]) => { + const disposer = this.on(arg0, (...args) => { handler(...args); disposer(); }); @@ -79,7 +107,10 @@ export class Observable { * @param {string} eventName * @param {Function} [handler] */ - private _removeEventListener(eventName: string, handler?: Function) { + private _removeEventListener( + eventName: K, + handler?: TEventCallback + ) { if (!this.__eventListeners[eventName]) { return; } @@ -100,9 +131,12 @@ export class Observable { * @param {EventRegistryObject} handlers key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) * @param {Function} handler Function to be deleted from EventListeners */ - off(eventName: string, handler: Function): void; + off(eventName: K, handler: TEventCallback): void; off(handlers: EventRegistryObject): void; - off(arg0?: string | EventRegistryObject, handler?: Function) { + off( + arg0?: K | EventRegistryObject, + handler?: TEventCallback + ) { if (!this.__eventListeners) { return; } @@ -116,7 +150,7 @@ export class Observable { // one object with key/value pairs was passed else if (typeof arg0 === 'object') { for (const eventName in arg0) { - this._removeEventListener(eventName, arg0[eventName]); + this._removeEventListener(eventName as K, arg0[eventName]); } } else { this._removeEventListener(arg0, handler); @@ -128,7 +162,7 @@ export class Observable { * @param {String} eventName Event name to fire * @param {Object} [options] Options object */ - fire(eventName: string, options?: object) { + fire(eventName: K, options?: EventSpec[K]) { if (!this.__eventListeners) { return; } diff --git a/src/mixins/shared_methods.mixin.ts b/src/mixins/shared_methods.mixin.ts index 94cf36bbf7c..8e8841f860e 100644 --- a/src/mixins/shared_methods.mixin.ts +++ b/src/mixins/shared_methods.mixin.ts @@ -1,7 +1,7 @@ //@ts-nocheck import { Observable } from './observable.mixin'; -export class CommonMethods extends Observable { +export class CommonMethods extends Observable { /** * Sets object's properties from options * @param {Object} [options] Options object diff --git a/src/mixins/text_style.mixin.ts b/src/mixins/text_style.mixin.ts index c0975364ed3..487b7941058 100644 --- a/src/mixins/text_style.mixin.ts +++ b/src/mixins/text_style.mixin.ts @@ -1,3 +1,4 @@ +import { ObjectEvents } from '../EventTypeDefs'; import { FabricObject } from '../shapes/fabricObject.class'; export type TextStyleDeclaration = Record; @@ -6,7 +7,9 @@ export type TextStyle = { [line: number | string]: { [char: number | string]: TextStyleDeclaration }; }; -export abstract class TextStyleMixin extends FabricObject { +export abstract class TextStyleMixin< + EventSpec extends ObjectEvents +> extends FabricObject { abstract styles: TextStyle; protected abstract _textLines: string[][]; protected abstract _forceClearCache: boolean; diff --git a/src/shapes/group.class.ts b/src/shapes/group.class.ts index 877ec11e545..7da54cbdf10 100644 --- a/src/shapes/group.class.ts +++ b/src/shapes/group.class.ts @@ -1,4 +1,5 @@ -// @ts-nocheck +//@ts-nocheck +import { ObjectEvents } from '../EventTypeDefs'; import { fabric } from '../../HEADER'; import { createCollectionMixin } from '../mixins/collection.mixin'; import { resolveOrigin } from '../mixins/object_origin.mixin'; @@ -19,12 +20,6 @@ import { degreesToRadians } from '../util/misc/radiansDegreesConversion'; import { sin } from '../util/misc/sin'; import { FabricObject, fabricObjectDefaultValues } from './fabricObject.class'; -export type LayoutStrategy = - | 'fit-content' - | 'fit-content-lazy' - | 'fixed' - | 'clip-path'; - export type LayoutContextType = | 'initialization' | 'object_modified' @@ -33,9 +28,6 @@ export type LayoutContextType = | 'layout_change' | 'imperative'; -/** - * context object with data regarding what triggered the call - */ export type LayoutContext = { type: LayoutContextType; /** @@ -45,6 +37,29 @@ export type LayoutContext = { [key: string]: any; }; +export type LayoutResult = { + centerX: number; + centerY: number; + width: number; + height: number; +}; + +export type GroupEvents = ObjectEvents & { + layout: { + context: LayoutContext; + result: LayoutResult; + diff: Point; + }; + 'object:added': { target: FabricObject }; + 'object:removed': { target: FabricObject }; +}; + +export type LayoutStrategy = + | 'fit-content' + | 'fit-content-lazy' + | 'fixed' + | 'clip-path'; + /** * positioning and layout data **relative** to instance's parent */ @@ -74,7 +89,7 @@ export type LayoutResult = { * @fires object:removed * @fires layout once layout completes */ -export class Group extends createCollectionMixin(FabricObject) { +export class Group extends createCollectionMixin(FabricObject) { /** * Specifies the **layout strategy** for instance * Used by `getLayoutStrategyResult` to calculate layout diff --git a/src/shapes/itext.class.ts b/src/shapes/itext.class.ts index 10be3e80ea1..98f1dfb9d38 100644 --- a/src/shapes/itext.class.ts +++ b/src/shapes/itext.class.ts @@ -1,5 +1,6 @@ // @ts-nocheck import { fabric } from '../../HEADER'; +import { ObjectEvents, TransformEvent } from '../EventTypeDefs'; import { ITextClickBehaviorMixin } from '../mixins/itext_click_behavior.mixin'; import { TClassProperties, TFiller } from '../typedefs'; import { stylesFromArray } from '../util/misc/textStyles'; @@ -10,6 +11,15 @@ import { ctrlKeysMapDown, ctrlKeysMapUp, } from '../mixins/itext_key_const'; + +export type ITextEvents = ObjectEvents & { + 'selection:changed': never; + changed: never; + tripleclick: TransformEvent; + 'editing:entered': never; + 'editing:exited': never; +}; + /** * IText class (introduced in v1.4) Events are also fired with "text:" * prefix when observing canvas. @@ -60,7 +70,7 @@ import { * Select line: triple click * */ -export class IText extends ITextClickBehaviorMixin { +export class IText extends ITextClickBehaviorMixin { /** * Index where text selection starts (or where cursor is when there is no selection) * @type Number diff --git a/src/shapes/object.class.ts b/src/shapes/object.class.ts index de16199d9be..20163141534 100644 --- a/src/shapes/object.class.ts +++ b/src/shapes/object.class.ts @@ -13,6 +13,8 @@ import { capitalize } from '../util/lang_string'; import { capValue } from '../util/misc/capValue'; import { createCanvasElement } from '../util/misc/dom'; import { qrDecompose, transformPoint } from '../util/misc/matrix'; +import { Canvas, StaticCanvas } from '../__types__'; +import { ObjectEvents } from '../EventTypeDefs'; import { enlivenObjectEnlivables } from '../util/misc/objectEnlive'; import { pick } from '../util/misc/pick'; import { toFixed } from '../util/misc/toFixed'; @@ -34,17 +36,12 @@ const ALIASING_LIMIT = 2; * * @fires selected * @fires deselected - * @fires modified - * @fires modified - * @fires moved - * @fires scaled - * @fires rotated - * @fires skewed * * @fires rotating * @fires scaling * @fires moving * @fires skewing + * @fires modified * * @fires mousedown * @fires mouseup @@ -58,7 +55,9 @@ const ALIASING_LIMIT = 2; * @fires dragleave * @fires drop */ -export class FabricObject extends ObjectGeometry { +export class FabricObject< + EventSpec extends ObjectEvents = ObjectEvents +> extends ObjectGeometry { type: string; /** diff --git a/src/shapes/text.class.ts b/src/shapes/text.class.ts index 76f444f3894..215acc9ae1d 100644 --- a/src/shapes/text.class.ts +++ b/src/shapes/text.class.ts @@ -2,6 +2,7 @@ import { fabric } from '../../HEADER'; import { cache } from '../cache'; import { DEFAULT_SVG_FONT_SIZE } from '../constants'; +import { ObjectEvents } from '../EventTypeDefs'; import { TextStyle, TextStyleMixin } from '../mixins/text_style.mixin'; import { TClassProperties, TFiller } from '../typedefs'; import { graphemeSplit } from '../util/lang_string'; @@ -57,7 +58,9 @@ const additionalProps = [ * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text} * @see {@link Text#initialize} for constructor definition */ -export class Text extends TextStyleMixin { +export class Text< + EventSpec extends ObjectEvents = ObjectEvents +> extends TextStyleMixin { /** * Properties which when set cause object to change dimensions * @type Array diff --git a/src/typedefs.ts b/src/typedefs.ts index 693eeb432c5..50957a1c2de 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -1,10 +1,7 @@ // https://www.typescriptlang.org/docs/handbook/utility-types.html -import type { Control } from './controls/control.class'; import type { Gradient } from './gradient/gradient.class'; import type { Pattern } from './pattern.class'; import type { Point } from './point.class'; -import type { FabricObject } from './shapes/fabricObject.class'; -import type { saveObjectTransform } from './util/misc/objectTransforms'; interface NominalTag { nominalTag?: T; @@ -71,71 +68,11 @@ export const enum SupportedSVGUnit { export type TMat2D = [number, number, number, number, number, number]; -export type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey'; - /** * SVG path commands */ export type PathData = (string | number)[][]; -export type TPointerEvent = MouseEvent | TouchEvent; - -export type TransformAction = ( - eventData: TPointerEvent, - transform: T, - x: number, - y: number -) => R; - -export type TransformActionHandler = - TransformAction; - -export type ControlCallback = ( - eventData: TPointerEvent, - control: Control, - fabricObject: FabricObject -) => R; - -export type ControlCursorCallback = ControlCallback; - -/** - * relative to target's containing coordinate plane - * both agree on every point - */ -export type Transform = { - target: FabricObject; - action: string; - actionHandler: TransformActionHandler; - corner: string; - scaleX: number; - scaleY: number; - skewX: number; - skewY: number; - offsetX: number; - offsetY: number; - originX: TOriginX; - originY: TOriginY; - ex: number; - ey: number; - lastX: number; - lastY: number; - theta: TRadian; - width: number; - height: number; - shiftKey: boolean; - altKey: boolean; - original: ReturnType; -}; - -export type TEvent = { - e: E; -}; - -export type TransformEvent = TEvent & { - transform: Transform; - pointer: Point; -}; - /** * An invalid keyword and an empty string will be handled as the `anonymous` keyword. * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes diff --git a/src/util/fireEvent.ts b/src/util/fireEvent.ts index 8777bd7ed33..1b204254494 100644 --- a/src/util/fireEvent.ts +++ b/src/util/fireEvent.ts @@ -1,6 +1,9 @@ -import { TransformEvent } from '../typedefs'; +import { TModificationEvents, BasicTransformEvent } from '../EventTypeDefs'; -export const fireEvent = (eventName: string, options: TransformEvent) => { +export const fireEvent = ( + eventName: TModificationEvents, + options: BasicTransformEvent +) => { const { transform: { target }, } = options; diff --git a/test/unit/canvas.js b/test/unit/canvas.js index fbd43bdec2f..5a5c4ccee01 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -331,22 +331,24 @@ }); QUnit.test('before:selection:cleared gets target the active object', function(assert) { - var passedTarget; + var deselected; canvas.on('before:selection:cleared', function(options) { - passedTarget = options.target; + deselected = options.deselected; }); var rect = new fabric.Rect(); canvas.add(rect); canvas.setActiveObject(rect); canvas.discardActiveObject(); - assert.equal(passedTarget, rect, 'options.target was the removed object'); + assert.equal(deselected.length, 1, 'options.deselected was the removed object'); + assert.equal(deselected[0], rect, 'options.deselected was the removed object'); var rect1 = new fabric.Rect(); var rect2 = new fabric.Rect(); canvas.add(rect1, rect2); var activeSelection = new fabric.ActiveSelection([rect1, rect2], { canvas: canvas }); canvas.setActiveObject(activeSelection); canvas.discardActiveObject(); - assert.equal(passedTarget, activeSelection, 'removing an activeSelection pass that as a target'); + assert.equal(deselected.length, 1, 'options.deselected was the removed object'); + assert.equal(deselected[0], activeSelection, 'removing an activeSelection pass that as a target'); }); QUnit.test('selection:cleared', function(assert) {