diff --git a/CHANGELOG.md b/CHANGELOG.md index a4667ae7d70..3c70d285c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- BREAKING: refactor `clone(obj, true)` with `cloneDeep(obj)` and remove all `extend`, `clone` calls in favor of object spreads. [#8600](https://github.com/fabricjs/fabric.js/pull/8600) - chore(TS): Fix some error caused by ts-nocheck removals [#8615](https://github.com/fabricjs/fabric.js/pull/8615) - refactor(IText): extract draggable text logic to a delegate [#8598](https://github.com/fabricjs/fabric.js/pull/8598) - chore(TS): Update StaticCanvas to remove ts-nocheck [#8606](https://github.com/fabricjs/fabric.js/pull/8606) diff --git a/index.ts b/index.ts index 330d71ece22..b91b2927f1f 100644 --- a/index.ts +++ b/index.ts @@ -89,7 +89,6 @@ import { stylesToArray, hasStyleChanged, } from './src/util/misc/textStyles'; -import { clone, extend } from './src/util/lang_object'; import { createCanvasElement, createImage, @@ -196,10 +195,6 @@ const util = { stylesToArray, hasStyleChanged, getElementOffset, - object: { - clone, - extend, - }, createCanvasElement, createImage, copyCanvasElement, diff --git a/src/canvas/canvas_gestures.mixin.ts b/src/canvas/canvas_gestures.mixin.ts index e637ee004f2..96884413f56 100644 --- a/src/canvas/canvas_gestures.mixin.ts +++ b/src/canvas/canvas_gestures.mixin.ts @@ -2,210 +2,206 @@ import { scalingEqually } from '../controls/scale'; import { fireEvent } from '../util/fireEvent'; +import { + degreesToRadians, + radiansToDegrees, +} from '../util/misc/radiansDegreesConversion'; +import { Canvas } from './canvas_events'; + +/** + * Adds support for multi-touch gestures using the Event.js library. + * Fires the following custom events: + * - touch:gesture + * - touch:drag + * - touch:orientation + * - touch:shake + * - touch:longpress + */ +Object.assign(Canvas.prototype, { + /** + * Method that defines actions when an Event.js gesture is detected on an object. Currently only supports + * 2 finger gestures. + * @param {Event} e Event object by Event.js + * @param {Event} self Event proxy object by Event.js + */ + __onTransformGesture: function (e, self) { + if ( + this.isDrawingMode || + !e.touches || + e.touches.length !== 2 || + 'gesture' !== self.gesture + ) { + return; + } + + var target = this.findTarget(e); + if ('undefined' !== typeof target) { + this.__gesturesParams = { + e: e, + self: self, + target: target, + }; + + this.__gesturesRenderer(); + } -(function (global) { - var fabric = global.fabric, - degreesToRadians = fabric.util.degreesToRadians, - radiansToDegrees = fabric.util.radiansToDegrees; + this.fire('touch:gesture', { + target: target, + e: e, + self: self, + }); + }, + __gesturesParams: null, + __gesturesRenderer: function () { + if (this.__gesturesParams === null || this._currentTransform === null) { + return; + } + + var self = this.__gesturesParams.self, + t = this._currentTransform, + e = this.__gesturesParams.e; + + t.action = 'scale'; + t.originX = t.originY = 'center'; + + this._scaleObjectBy(self.scale, e); + + if (self.rotation !== 0) { + t.action = 'rotate'; + this._rotateObjectByAngle(self.rotation, e); + } + + this.requestRenderAll(); + + t.action = 'drag'; + }, + + /** + * Method that defines actions when an Event.js drag is detected. + * + * @param {Event} e Event object by Event.js + * @param {Event} self Event proxy object by Event.js + */ + __onDrag: function (e, self) { + this.fire('touch:drag', { + e: e, + self: self, + }); + }, + + /** + * Method that defines actions when an Event.js orientation event is detected. + * + * @param {Event} e Event object by Event.js + * @param {Event} self Event proxy object by Event.js + */ + __onOrientationChange: function (e, self) { + this.fire('touch:orientation', { + e: e, + self: self, + }); + }, + + /** + * Method that defines actions when an Event.js shake event is detected. + * + * @param {Event} e Event object by Event.js + * @param {Event} self Event proxy object by Event.js + */ + __onShake: function (e, self) { + this.fire('touch:shake', { + e: e, + self: self, + }); + }, /** - * Adds support for multi-touch gestures using the Event.js library. - * Fires the following custom events: - * - touch:gesture - * - touch:drag - * - touch:orientation - * - touch:shake - * - touch:longpress + * Method that defines actions when an Event.js longpress event is detected. + * + * @param {Event} e Event object by Event.js + * @param {Event} self Event proxy object by Event.js */ - fabric.util.object.extend( - fabric.Canvas.prototype, - /** @lends fabric.Canvas.prototype */ { - /** - * Method that defines actions when an Event.js gesture is detected on an object. Currently only supports - * 2 finger gestures. - * @param {Event} e Event object by Event.js - * @param {Event} self Event proxy object by Event.js - */ - __onTransformGesture: function (e, self) { - if ( - this.isDrawingMode || - !e.touches || - e.touches.length !== 2 || - 'gesture' !== self.gesture - ) { - return; - } - - var target = this.findTarget(e); - if ('undefined' !== typeof target) { - this.__gesturesParams = { - e: e, - self: self, - target: target, - }; - - this.__gesturesRenderer(); - } - - this.fire('touch:gesture', { - target: target, - e: e, - self: self, - }); - }, - __gesturesParams: null, - __gesturesRenderer: function () { - if (this.__gesturesParams === null || this._currentTransform === null) { - return; - } - - var self = this.__gesturesParams.self, - t = this._currentTransform, - e = this.__gesturesParams.e; - - t.action = 'scale'; - t.originX = t.originY = 'center'; - - this._scaleObjectBy(self.scale, e); - - if (self.rotation !== 0) { - t.action = 'rotate'; - this._rotateObjectByAngle(self.rotation, e); - } - - this.requestRenderAll(); - - t.action = 'drag'; - }, - - /** - * Method that defines actions when an Event.js drag is detected. - * - * @param {Event} e Event object by Event.js - * @param {Event} self Event proxy object by Event.js - */ - __onDrag: function (e, self) { - this.fire('touch:drag', { - e: e, - self: self, - }); - }, - - /** - * Method that defines actions when an Event.js orientation event is detected. - * - * @param {Event} e Event object by Event.js - * @param {Event} self Event proxy object by Event.js - */ - __onOrientationChange: function (e, self) { - this.fire('touch:orientation', { - e: e, - self: self, - }); - }, - - /** - * Method that defines actions when an Event.js shake event is detected. - * - * @param {Event} e Event object by Event.js - * @param {Event} self Event proxy object by Event.js - */ - __onShake: function (e, self) { - this.fire('touch:shake', { - e: e, - self: self, - }); - }, - - /** - * Method that defines actions when an Event.js longpress event is detected. - * - * @param {Event} e Event object by Event.js - * @param {Event} self Event proxy object by Event.js - */ - __onLongPress: function (e, self) { - this.fire('touch:longpress', { - e: e, - self: self, - }); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js gesture - * @param {Event} [self] Inner Event object - */ - _onGesture: function (e, self) { - this.__onTransformGesture(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js drag - * @param {Event} [self] Inner Event object - */ - _onDrag: function (e, self) { - this.__onDrag(e, self); - }, - - /** - * Scales an object by a factor - * @param {Number} s The scale factor to apply to the current scale level - * @param {Event} e Event object by Event.js - */ - _scaleObjectBy: function (s, e) { - var t = this._currentTransform, - target = t.target; - t.gestureScale = s; - target._scaling = true; - return scalingEqually(e, t, 0, 0); - }, - - /** - * Rotates object by an angle - * @param {Number} curAngle The angle of rotation in degrees - * @param {Event} e Event object by Event.js - */ - _rotateObjectByAngle: function (curAngle, e) { - var t = this._currentTransform; - - if (t.target.get('lockRotation')) { - return; - } - t.target.rotate(radiansToDegrees(degreesToRadians(curAngle) + t.theta)); - fireEvent('rotating', { - target: t.target, - e: e, - transform: t, - }); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js orientation change - * @param {Event} [self] Inner Event object - */ - _onOrientationChange: function (e, self) { - this.__onOrientationChange(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onShake: function (e, self) { - this.__onShake(e, self); - }, - - /** - * @private - * @param {Event} [e] Event object fired on Event.js shake - * @param {Event} [self] Inner Event object - */ - _onLongPress: function (e, self) { - this.__onLongPress(e, self); - }, + __onLongPress: function (e, self) { + this.fire('touch:longpress', { + e: e, + self: self, + }); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js gesture + * @param {Event} [self] Inner Event object + */ + _onGesture: function (e, self) { + this.__onTransformGesture(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js drag + * @param {Event} [self] Inner Event object + */ + _onDrag: function (e, self) { + this.__onDrag(e, self); + }, + + /** + * Scales an object by a factor + * @param {Number} s The scale factor to apply to the current scale level + * @param {Event} e Event object by Event.js + */ + _scaleObjectBy: function (s, e) { + var t = this._currentTransform, + target = t.target; + t.gestureScale = s; + target._scaling = true; + return scalingEqually(e, t, 0, 0); + }, + + /** + * Rotates object by an angle + * @param {Number} curAngle The angle of rotation in degrees + * @param {Event} e Event object by Event.js + */ + _rotateObjectByAngle: function (curAngle, e) { + var t = this._currentTransform; + + if (t.target.get('lockRotation')) { + return; } - ); -})(typeof exports !== 'undefined' ? exports : window); + t.target.rotate(radiansToDegrees(degreesToRadians(curAngle) + t.theta)); + fireEvent('rotating', { + target: t.target, + e: e, + transform: t, + }); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js orientation change + * @param {Event} [self] Inner Event object + */ + _onOrientationChange: function (e, self) { + this.__onOrientationChange(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js shake + * @param {Event} [self] Inner Event object + */ + _onShake: function (e, self) { + this.__onShake(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js shake + * @param {Event} [self] Inner Event object + */ + _onLongPress: function (e, self) { + this.__onLongPress(e, self); + }, +}); diff --git a/src/mixins/DraggableTextDelegate.ts b/src/mixins/DraggableTextDelegate.ts index a9f1869706c..532f77edd07 100644 --- a/src/mixins/DraggableTextDelegate.ts +++ b/src/mixins/DraggableTextDelegate.ts @@ -4,7 +4,7 @@ import { DragEventData, DropEventData, TPointerEvent } from '../EventTypeDefs'; import { Point } from '../point.class'; import type { IText } from '../shapes/itext.class'; import { setStyle } from '../util/dom_style'; -import { clone } from '../util/lang_object'; +import { cloneDeep } from '../util/internals/cloneDeep'; import { createCanvasElement } from '../util/misc/dom'; import { isIdentityMatrix } from '../util/misc/matrix'; import { TextStyleDeclaration } from './text_style.mixin'; @@ -127,7 +127,7 @@ export class DraggableTextDelegate { const offset = correction.add(diff).transform(vpt, true); // prepare instance for drag image snapshot by making all non selected text invisible const bgc = target.backgroundColor; - const styles = clone(target.styles, true); + const styles = cloneDeep(target.styles); target.backgroundColor = ''; const styleOverride = { stroke: 'transparent', diff --git a/src/mixins/canvas_animation.mixin.ts b/src/mixins/canvas_animation.mixin.ts index 669363b9db6..ab09ae96616 100644 --- a/src/mixins/canvas_animation.mixin.ts +++ b/src/mixins/canvas_animation.mixin.ts @@ -1,123 +1,121 @@ // @ts-nocheck -import { getEnv } from '../env'; +import { StaticCanvas } from '../canvas/static_canvas.class'; +import { animate } from '../util/animation/animate'; -fabric.util.object.extend( - fabric.StaticCanvas.prototype, - /** @lends fabric.StaticCanvas.prototype */ { - /** - * Animation duration (in ms) for fx* methods - * @type Number - * @default - */ - FX_DURATION: 500, +Object.assign(StaticCanvas.prototype, { + /** + * Animation duration (in ms) for fx* methods + * @type Number + * @default + */ + FX_DURATION: 500, - /** - * Centers object horizontally with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.AnimationContext} context - */ - fxCenterObjectH: function (object, callbacks) { - callbacks = callbacks || {}; + /** + * Centers object horizontally with animation. + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.AnimationContext} context + */ + fxCenterObjectH: function (object, callbacks) { + callbacks = callbacks || {}; - var empty = function () {}, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; + var empty = function () {}, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; - return fabric.util.animate({ - target: this, - startValue: object.getX(), - endValue: this.getCenterPoint().x, - duration: this.FX_DURATION, - onChange: function (value) { - object.setX(value); - _this.requestRenderAll(); - onChange(); - }, - onComplete: function () { - object.setCoords(); - onComplete(); - }, - }); - }, + return animate({ + target: this, + startValue: object.getX(), + endValue: this.getCenterPoint().x, + duration: this.FX_DURATION, + onChange: function (value) { + object.setX(value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function () { + object.setCoords(); + onComplete(); + }, + }); + }, - /** - * Centers object vertically with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.AnimationContext} context - */ - fxCenterObjectV: function (object, callbacks) { - callbacks = callbacks || {}; + /** + * Centers object vertically with animation. + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.AnimationContext} context + */ + fxCenterObjectV: function (object, callbacks) { + callbacks = callbacks || {}; - var empty = function () {}, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; + var empty = function () {}, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; - return fabric.util.animate({ - target: this, - startValue: object.getY(), - endValue: this.getCenterPoint().y, - duration: this.FX_DURATION, - onChange: function (value) { - object.setY(value); - _this.requestRenderAll(); - onChange(); - }, - onComplete: function () { - object.setCoords(); - onComplete(); - }, - }); - }, + return animate({ + target: this, + startValue: object.getY(), + endValue: this.getCenterPoint().y, + duration: this.FX_DURATION, + onChange: function (value) { + object.setY(value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function () { + object.setCoords(); + onComplete(); + }, + }); + }, - /** - * Same as `fabric.Canvas#remove` but animated - * @param {fabric.Object} object Object to remove - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.AnimationContext} context - */ - fxRemove: function (object, callbacks) { - callbacks = callbacks || {}; - var empty = function () {}, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; + /** + * Same as `fabric.Canvas#remove` but animated + * @param {fabric.Object} object Object to remove + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.AnimationContext} context + */ + fxRemove: function (object, callbacks) { + callbacks = callbacks || {}; + var empty = function () {}, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; - return fabric.util.animate({ - target: this, - startValue: object.opacity, - endValue: 0, - duration: this.FX_DURATION, - onChange: function (value) { - object.set('opacity', value); - _this.requestRenderAll(); - onChange(); - }, - onComplete: function () { - _this.remove(object); - onComplete(); - }, - }); - }, + return animate({ + target: this, + startValue: object.opacity, + endValue: 0, + duration: this.FX_DURATION, + onChange: function (value) { + object.set('opacity', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function () { + _this.remove(object); + onComplete(); + }, + }); + }, - /** - * @param {fabric.Object} object Object to straighten - * @return {fabric.Canvas} thisArg - */ - fxStraightenObject: function (object: FabricObject) { - return object.fxStraighten({ - onChange: () => this.requestRenderAll(), - }); - }, - } -); + /** + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + */ + fxStraightenObject: function (object: FabricObject) { + return object.fxStraighten({ + onChange: () => this.requestRenderAll(), + }); + }, +}); diff --git a/src/mixins/stateful.mixin.ts b/src/mixins/stateful.mixin.ts index bb40a02483e..30fcd49e3f9 100644 --- a/src/mixins/stateful.mixin.ts +++ b/src/mixins/stateful.mixin.ts @@ -1,6 +1,6 @@ // @ts-nocheck import type { FabricObject } from '../shapes/Object/Object'; -import { extend } from '../util/lang_object'; +import { cloneDeep } from '../util/internals/cloneDeep'; const originalSet = 'stateProperties'; @@ -77,11 +77,10 @@ export class StatefulMixin { } private saveProps(destination: string, props: (keyof FabricObject)[]) { - const savedProps = props.reduce((o, key) => { - o[key] = this[key]; + props.reduce((o, key) => { + o[key] = this[key] ? cloneDeep(this[key]) : this[key]; return o; - }, {}); - extend(this[destination], savedProps, true); + }, this[destination]); } /** diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts index 8b58e5f7b83..21373d9e04c 100644 --- a/src/shapes/Object/Object.ts +++ b/src/shapes/Object/Object.ts @@ -14,7 +14,7 @@ import type { } from '../../typedefs'; import { classRegistry } from '../../util/class_registry'; import { runningAnimations } from '../../util/animation/AnimationRegistry'; -import { clone } from '../../util/lang_object'; +import { cloneDeep } from '../../util/internals/cloneDeep'; import { capitalize } from '../../util/lang_string'; import { capValue } from '../../util/misc/capValue'; import { createCanvasElement, toDataURL } from '../../util/misc/dom'; @@ -1850,7 +1850,7 @@ export class FabricObject< ...options }: { extraParam?: string; signal?: AbortSignal } = {} ): Promise { - return enlivenObjectEnlivables(clone(object, true), options).then( + return enlivenObjectEnlivables(cloneDeep(object), options).then( (enlivedMap) => { const allOptions = { ...options, ...enlivedMap }; // from the resulting enlived options, extract options.extraParam to arg0 diff --git a/src/shapes/line.class.ts b/src/shapes/line.class.ts index fef5333eb8e..4a2ecc053e6 100644 --- a/src/shapes/line.class.ts +++ b/src/shapes/line.class.ts @@ -1,7 +1,6 @@ import { SHARED_ATTRIBUTES } from '../parser/attributes'; import { parseAttributes } from '../parser/parseAttributes'; import { TClassProperties } from '../typedefs'; -import { clone } from '../util/lang_object'; import { classRegistry } from '../util/class_registry'; import { FabricObject, cacheProperties } from './Object/FabricObject'; import { Point } from '../point.class'; @@ -298,12 +297,16 @@ export class Line extends FabricObject { * @param {Object} object Object to create an instance from * @returns {Promise} */ - static fromObject(object: Record) { - const options = clone(object, true); - options.points = [object.x1, object.y1, object.x2, object.y2]; - return this._fromObject(options, { - extraParam: 'points', - }).then((fabricLine) => { + static fromObject({ x1, y1, x2, y2, ...object }: Record) { + return this._fromObject( + { + ...object, + points: [x1, y1, x2, y2], + }, + { + extraParam: 'points', + } + ).then((fabricLine) => { return fabricLine; }); } diff --git a/src/shapes/text.class.ts b/src/shapes/text.class.ts index ce4dd552f6d..778c5d24651 100644 --- a/src/shapes/text.class.ts +++ b/src/shapes/text.class.ts @@ -1858,7 +1858,7 @@ export class Text< return this._fromObject( { ...object, - styles: stylesFromArray(object.styles, object.text), + styles: stylesFromArray(object.styles || {}, object.text), }, { extraParam: 'text', diff --git a/src/util/internals/cloneDeep.ts b/src/util/internals/cloneDeep.ts new file mode 100644 index 00000000000..8d7125d5696 --- /dev/null +++ b/src/util/internals/cloneDeep.ts @@ -0,0 +1,2 @@ +export const cloneDeep = (object: T): T => + JSON.parse(JSON.stringify(object)); diff --git a/src/util/lang_object.ts b/src/util/lang_object.ts deleted file mode 100644 index 971e61ac082..00000000000 --- a/src/util/lang_object.ts +++ /dev/null @@ -1,60 +0,0 @@ -//@ts-nocheck - -import { getEnv } from '../env'; - -/** - * Copies all enumerable properties of one js object to another - * this does not and cannot compete with generic utils. - * Does not clone or extend FabricObject subclasses. - * This is mostly for internal use and has extra handling for fabricJS objects - * it skips the canvas and group properties in deep cloning. - * @param {Object} destination Where to copy to - * @param {Object} source Where to copy from - * @param {Boolean} [deep] Whether to extend nested objects - * @return {Object} - */ - -export const extend = (destination, source, deep) => { - // the deep clone is for internal use, is not meant to avoid - // javascript traps or cloning html element or self referenced objects. - if (deep) { - if (!getEnv().isLikelyNode && source instanceof Element) { - // avoid cloning deep images, canvases, - destination = source; - } else if (Array.isArray(source)) { - destination = []; - for (let i = 0, len = source.length; i < len; i++) { - destination[i] = extend({}, source[i], deep); - } - } else if (source && typeof source === 'object') { - for (const property in source) { - if (property === 'canvas' || property === 'group') { - // we do not want to clone this props at all. - // we want to keep the keys in the copy - destination[property] = null; - } else if (Object.prototype.hasOwnProperty.call(source, property)) { - destination[property] = extend({}, source[property], deep); - } - } - } else { - // this sounds odd for an extend but is ok for recursive use - destination = source; - } - } else { - for (const property in source) { - destination[property] = source[property]; - } - } - return destination; -}; - -/** - * Creates an empty object and copies all enumerable properties of another object to it - * This method is mostly for internal use, and not intended for duplicating shapes in canvas. - * @param {Object} object Object to clone - * @param {Boolean} [deep] Whether to clone nested objects - * @return {Object} - */ -//TODO: this function return an empty object if you try to clone null -export const clone = (object: T, deep: boolean): T => - deep ? extend({}, object, deep) : { ...object }; diff --git a/src/util/misc/textStyles.ts b/src/util/misc/textStyles.ts index 836181cc1fa..992fc562ce3 100644 --- a/src/util/misc/textStyles.ts +++ b/src/util/misc/textStyles.ts @@ -1,4 +1,14 @@ -import { clone } from '../lang_object'; +import type { + TextStyle, + TextStyleDeclaration, +} from '../../mixins/text_style.mixin'; +import { cloneDeep } from '../internals/cloneDeep'; + +export type TextStyleArray = { + start: number; + end: number; + style: TextStyleDeclaration; +}[]; /** * @param {Object} prevStyle first style to compare @@ -7,8 +17,8 @@ import { clone } from '../lang_object'; * @return {boolean} true if the style changed */ export const hasStyleChanged = ( - prevStyle: any, - thisStyle: any, + prevStyle: TextStyleDeclaration, + thisStyle: TextStyleDeclaration, forTextSpans = false ) => prevStyle.fill !== thisStyle.fill || @@ -33,13 +43,16 @@ export const hasStyleChanged = ( * @param {String} text the text string that the styles are applied to * @return {{start: number, end: number, style: object}[]} */ -export const stylesToArray = (styles: any, text: string) => { +export const stylesToArray = ( + styles: TextStyle, + text: string +): TextStyleArray => { const textLines = text.split('\n'), stylesArray = []; let charIndex = -1, prevStyle = {}; // clone style structure to prevent mutation - styles = clone(styles, true); + styles = cloneDeep(styles); //loop through each textLine for (let i = 0; i < textLines.length; i++) { @@ -79,10 +92,13 @@ export const stylesToArray = (styles: any, text: string) => { * @param {String} text the text string that the styles are applied to * @return {Object} */ -export const stylesFromArray = (styles: any, text: string) => { +export const stylesFromArray = ( + styles: TextStyleArray | TextStyle, + text: string +): TextStyle => { if (!Array.isArray(styles)) { // clone to prevent mutation - return clone(styles, true); + return cloneDeep(styles); } const textLines = text.split('\n'), stylesObject = {} as Record< diff --git a/test/unit/canvas.js b/test/unit/canvas.js index b87b4c126f0..a31e74575e1 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -84,14 +84,14 @@ var upperCanvasEl = canvas.upperCanvasEl; var lowerCanvasEl = canvas.lowerCanvasEl; - function makeRect(options) { + function makeRect(options = {}) { var defaultOptions = { width: 10, height: 10 }; - return new fabric.Rect(fabric.util.object.extend(defaultOptions, options || { })); + return new fabric.Rect({ ...defaultOptions, ...options }); } - function makeTriangle(options) { + function makeTriangle(options = {}) { var defaultOptions = { width: 30, height: 30 }; - return new fabric.Triangle(fabric.util.object.extend(defaultOptions, options || { })); + return new fabric.Triangle({ ...defaultOptions, ...options }); } let ORIGINAL_DPR; diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index 70ff015f501..9a4e69455f2 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -154,9 +154,9 @@ var lowerCanvasEl = canvas.lowerCanvasEl; - function makeRect(options) { + function makeRect(options={}) { var defaultOptions = { width: 10, height: 10 }; - return new fabric.Rect(fabric.util.object.extend(defaultOptions, options || { })); + return new fabric.Rect({ ...defaultOptions, ...options }); } QUnit.module('fabric.StaticCanvas', { diff --git a/test/unit/circle.js b/test/unit/circle.js index eec90d6ebc1..afcdc2b7ec1 100644 --- a/test/unit/circle.js +++ b/test/unit/circle.js @@ -124,17 +124,18 @@ assert.ok(typeof circle.toObject === 'function'); assert.deepEqual(circle.toObject(), defaultProperties); - circle.set('left', 100).set('top', 200).set('radius', 15); - - var augmentedProperties = fabric.util.object.extend(fabric.util.object.clone(defaultProperties), { - left: 100, - top: 200, - width: 30, + circle.set('left', 100); + circle.set('top', 200); + circle.set('radius', 15); + + assert.deepEqual(circle.toObject(), { + ...defaultProperties, + left: 100, + top: 200, + width: 30, height: 30, radius: 15 }); - - assert.deepEqual(circle.toObject(), augmentedProperties); }); QUnit.test('toSVG with full circle', function(assert) { diff --git a/test/unit/ellipse.js b/test/unit/ellipse.js index c3fe6144c21..9bb89aaede7 100644 --- a/test/unit/ellipse.js +++ b/test/unit/ellipse.js @@ -61,9 +61,13 @@ assert.ok(typeof ellipse.toObject === 'function'); assert.deepEqual(ellipse.toObject(), defaultProperties); - ellipse.set('left', 100).set('top', 200).set('rx', 15).set('ry', 25); + ellipse.set('left', 100); + ellipse.set('top', 200); + ellipse.set('rx', 15); + ellipse.set('ry', 25); - var augmentedProperties = fabric.util.object.extend(fabric.util.object.clone(defaultProperties), { + assert.deepEqual(ellipse.toObject(), { + ...defaultProperties, left: 100, top: 200, rx: 15, @@ -72,8 +76,6 @@ height: 50 }); - assert.deepEqual(ellipse.toObject(), augmentedProperties); - ellipse.set('rx', 30); assert.deepEqual(ellipse.width, ellipse.rx * 2); @@ -83,7 +85,8 @@ QUnit.test('isNotVisible', function(assert) { var ellipse = new fabric.Ellipse(); - ellipse.set('rx', 0).set('ry', 0); + ellipse.set('rx', 0); + ellipse.set('ry', 0); assert.equal(ellipse.isNotVisible(), false, 'isNotVisible false when rx/ry are 0 because strokeWidth is > 0'); diff --git a/test/unit/image.js b/test/unit/image.js index 21013ce67c8..0af4bd49984 100644 --- a/test/unit/image.js +++ b/test/unit/image.js @@ -466,12 +466,7 @@ QUnit.test('fromObject', function(assert) { var done = assert.async(); assert.ok(typeof fabric.Image.fromObject === 'function'); - - // should not throw error when no callback is given - var obj = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_IMG_OBJECT), { - src: IMG_SRC - }); - fabric.Image.fromObject(obj).then(function(instance){ + fabric.Image.fromObject({ ...REFERENCE_IMG_OBJECT, src: IMG_SRC }).then(function (instance) { assert.ok(instance instanceof fabric.Image); done(); }); @@ -480,7 +475,8 @@ QUnit.test('fromObject with clipPath and filters', function(assert) { var done = assert.async(); // should not throw error when no callback is given - var obj = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_IMG_OBJECT), { + var obj = { + ...REFERENCE_IMG_OBJECT, src: IMG_SRC, clipPath: (new fabric.Rect({ width: 100, height: 100 })).toObject(), filters: [{ @@ -490,7 +486,7 @@ resizeFilter: { type: 'Resize', } - }); + }; fabric.Image.fromObject(obj).then(function(instance){ assert.ok(instance instanceof fabric.Image); assert.ok(instance.clipPath instanceof fabric.Rect); @@ -506,9 +502,10 @@ var done = assert.async(); assert.ok(typeof fabric.Image.fromObject === 'function'); - var obj = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_IMG_OBJECT), { + var obj = { + ...REFERENCE_IMG_OBJECT, src: IMG_SRC - }); + }; var brightness = { type: 'Brightness', brightness: 0.1 diff --git a/test/unit/itext_click_behaviour.js b/test/unit/itext_click_behaviour.js index f015da89319..99745082cda 100644 --- a/test/unit/itext_click_behaviour.js +++ b/test/unit/itext_click_behaviour.js @@ -19,7 +19,7 @@ assert.equal(cursorState, active, `cursor animation state should be ${active}`); } - function wait(ms = 16) { + function wait(ms = 32) { return new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/test/unit/object_clipPath.js b/test/unit/object_clipPath.js index 220f623b94e..38a2e31800e 100644 --- a/test/unit/object_clipPath.js +++ b/test/unit/object_clipPath.js @@ -53,14 +53,16 @@ assert.deepEqual(emptyObjectRepr, cObj.toObject()); cObj.clipPath = new fabric.Object(); - var expected = fabric.util.object.clone(emptyObjectRepr); - var expectedClipPath = fabric.util.object.clone(emptyObjectRepr); - expectedClipPath = fabric.util.object.extend(expectedClipPath, { - inverted: cObj.clipPath.inverted, + + assert.deepEqual({ + ...emptyObjectRepr, + clipPath: { + ...emptyObjectRepr, + inverted: cObj.clipPath.inverted, absolutePositioned: cObj.clipPath.absolutePositioned, - }); - expected.clipPath = expectedClipPath; - assert.deepEqual(expected, cObj.toObject()); + } + }, cObj.toObject()); + cObj.clipPath.excludeFromExport = true; assert.ok(cObj.toObject().clipPath === undefined); }); diff --git a/test/unit/parser.js b/test/unit/parser.js index 0cf0ecb4de1..122765593b8 100644 --- a/test/unit/parser.js +++ b/test/unit/parser.js @@ -141,10 +141,11 @@ return el; } - function getOptions(options) { - return fabric.util.object.extend(fabric.util.object.clone({ - left: 10, top: 20, width: 30, height: 40 - }), options || { }); + function getOptions(options = {}) { + return { + left: 10, top: 20, width: 30, height: 40, + ...options, + } } var elements = [ diff --git a/test/unit/path.js b/test/unit/path.js index d4ed9f8c67a..896cff737cd 100644 --- a/test/unit/path.js +++ b/test/unit/path.js @@ -127,7 +127,7 @@ updatePath(path, REFERENCE_PATH_OBJECT.path, true); assert.deepEqual(path.toObject(), REFERENCE_PATH_OBJECT); updatePath(path, REFERENCE_PATH_OBJECT.path, false); - var opts = fabric.util.object.clone(REFERENCE_PATH_OBJECT); + var opts = { ...REFERENCE_PATH_OBJECT }; delete opts.path; path.set(opts); updatePath(path, 'M 100 100 L 300 100 L 200 300 z', true); @@ -226,7 +226,7 @@ makePathObject(function(path) { var src = 'http://example.com/'; path.sourcePath = src; - var clonedRef = fabric.util.object.clone(REFERENCE_PATH_OBJECT); + var clonedRef = { ...REFERENCE_PATH_OBJECT }; clonedRef.sourcePath = src; delete clonedRef.path; assert.deepEqual(path.toDatalessObject(), clonedRef, 'if sourcePath the object looses path'); @@ -285,12 +285,13 @@ fabric.Path.fromElement(elPath, function(path) { assert.ok(path instanceof fabric.Path); - assert.deepEqual(path.toObject(), fabric.util.object.extend(REFERENCE_PATH_OBJECT, { + assert.deepEqual(path.toObject(), { + ...REFERENCE_PATH_OBJECT, strokeDashArray: [5, 2], strokeLineCap: 'round', strokeLineJoin: 'bevel', strokeMiterLimit: 5 - })); + }); var ANGLE_DEG = 90; elPath.setAttributeNS(namespace, 'transform', 'rotate(' + ANGLE_DEG + ')'); diff --git a/test/unit/polygon.js b/test/unit/polygon.js index 80e3eeaaa35..7b290f2be1e 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -149,11 +149,10 @@ var polygon = new fabric.Polygon(getPoints()); assert.ok(typeof polygon.toObject === 'function'); - var objectWithOriginalPoints = fabric.util.object.extend(polygon.toObject(), { + assert.deepEqual({ + ...polygon.toObject(), points: getPoints() - }); - - assert.deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT); + }, REFERENCE_OBJECT); }); QUnit.test('toSVG', function(assert) { @@ -176,13 +175,10 @@ QUnit.test('fromElement without points', function(assert) { assert.ok(typeof fabric.Polygon.fromElement === 'function'); - var empty_object = fabric.util.object.extend({}, REFERENCE_OBJECT); - empty_object = fabric.util.object.extend(empty_object, REFERENCE_EMPTY_OBJECT); - var elPolygonWithoutPoints = fabric.getDocument().createElementNS('http://www.w3.org/2000/svg', 'polygon'); fabric.Polygon.fromElement(elPolygonWithoutPoints, function(polygon) { - assert.deepEqual(polygon.toObject(), empty_object); + assert.deepEqual(polygon.toObject(), { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT }); }); }); @@ -190,10 +186,8 @@ var namespace = 'http://www.w3.org/2000/svg'; var elPolygonWithEmptyPoints = fabric.getDocument().createElementNS(namespace, 'polygon'); elPolygonWithEmptyPoints.setAttributeNS(namespace, 'points', ''); - var empty_object = fabric.util.object.extend({}, REFERENCE_OBJECT); - empty_object = fabric.util.object.extend(empty_object, REFERENCE_EMPTY_OBJECT); fabric.Polygon.fromElement(elPolygonWithEmptyPoints, function(polygon) { - assert.deepEqual(polygon.toObject(), empty_object); + assert.deepEqual(polygon.toObject(), { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT }); }); }); @@ -203,13 +197,12 @@ elPolygon.setAttributeNS(namespace, 'points', '10,12 20,22'); fabric.Polygon.fromElement(elPolygon, function(polygon) { assert.ok(polygon instanceof fabric.Polygon); - var expected = fabric.util.object.extend( - fabric.util.object.clone(REFERENCE_OBJECT), { - points: [{ x: 10, y: 12 }, { x: 20, y: 22 }], - left: 10, - top: 12 - }); - assert.deepEqual(polygon.toObject(), expected); + assert.deepEqual(polygon.toObject(), { + ...REFERENCE_OBJECT, + points: [{ x: 10, y: 12 }, { x: 20, y: 22 }], + left: 10, + top: 12 + }); }); }); @@ -233,7 +226,8 @@ { x: 30, y: 30 }, { x: 10, y: 10 } ]; - assert.deepEqual(polygonWithAttrs.toObject(), fabric.util.object.extend(REFERENCE_OBJECT, { + assert.deepEqual(polygonWithAttrs.toObject(), { + ...REFERENCE_OBJECT, width: 20, height: 20, fill: 'rgb(255,255,255)', @@ -247,7 +241,7 @@ points: expectedPoints, top: 10, left: 10, - })); + }); }); }); QUnit.test('fromElement with null', function(assert) { diff --git a/test/unit/polyline.js b/test/unit/polyline.js index 9be187facd6..3e2977c3d32 100644 --- a/test/unit/polyline.js +++ b/test/unit/polyline.js @@ -72,11 +72,11 @@ QUnit.test('toObject', function(assert) { var polyline = new fabric.Polyline(getPoints()); assert.ok(typeof polyline.toObject === 'function'); - var objectWithOriginalPoints = fabric.util.object.extend(polyline.toObject(), { - points: getPoints() - }); - assert.deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT); + assert.deepEqual({ + ...polyline.toObject(), + points: getPoints() + }, REFERENCE_OBJECT); }); QUnit.test('toSVG', function(assert) { @@ -99,10 +99,8 @@ QUnit.test('fromElement without points', function(assert) { assert.ok(typeof fabric.Polyline.fromElement === 'function'); var elPolylineWithoutPoints = fabric.getDocument().createElementNS('http://www.w3.org/2000/svg', 'polyline'); - var empty_object = fabric.util.object.extend({}, REFERENCE_OBJECT); - empty_object = fabric.util.object.extend(empty_object, REFERENCE_EMPTY_OBJECT); fabric.Polyline.fromElement(elPolylineWithoutPoints, function(polyline) { - assert.deepEqual(polyline.toObject(), empty_object); + assert.deepEqual(polyline.toObject(), { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT }); }); }); @@ -111,9 +109,7 @@ var elPolylineWithEmptyPoints = fabric.getDocument().createElementNS(namespace, 'polyline'); elPolylineWithEmptyPoints.setAttributeNS(namespace, 'points', ''); fabric.Polyline.fromElement(elPolylineWithEmptyPoints, function(polyline) { - var empty_object = fabric.util.object.extend({}, REFERENCE_OBJECT); - empty_object = fabric.util.object.extend(empty_object, REFERENCE_EMPTY_OBJECT); - assert.deepEqual(polyline.toObject(), empty_object); + assert.deepEqual(polyline.toObject(), { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT }); }); }); @@ -124,10 +120,11 @@ elPolyline.setAttributeNS(namespace, 'stroke-width', 1); fabric.Polyline.fromElement(elPolyline, function(polyline) { assert.ok(polyline instanceof fabric.Polyline); - var obj = fabric.util.object.extend({}, REFERENCE_OBJECT); - obj.top = 12; - obj.left = 10; - assert.deepEqual(polyline.toObject(), obj); + assert.deepEqual(polyline.toObject(), { + ...REFERENCE_OBJECT, + left: 10, + top: 12 + }); }); }); @@ -147,7 +144,8 @@ fabric.Polyline.fromElement(elPolylineWithAttrs, function(polylineWithAttrs) { var expectedPoints = [{x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 10, y: 10}]; - assert.deepEqual(polylineWithAttrs.toObject(), fabric.util.object.extend(REFERENCE_OBJECT, { + assert.deepEqual(polylineWithAttrs.toObject(), { + ...REFERENCE_OBJECT, width: 20, height: 20, fill: 'rgb(255,255,255)', @@ -161,7 +159,7 @@ points: expectedPoints, left: 10, top: 10, - })); + }); }); }); diff --git a/test/unit/rect.js b/test/unit/rect.js index d1149615c48..e4869bdae28 100644 --- a/test/unit/rect.js +++ b/test/unit/rect.js @@ -78,10 +78,12 @@ assert.ok(rect instanceof fabric.Rect); assert.deepEqual(rect.toObject(), REFERENCE_RECT); - var expectedObject = fabric.util.object.extend({ }, REFERENCE_RECT); - expectedObject.fill = {type: 'linear',coords: {x1: 0,y1: 0,x2: 200,y2: 0},colorStops: [{offset: '0',color: 'rgb(255,0,0)',opacity: 1},{offset: '1',color: 'rgb(0,0,255)',opacity: 1}],offsetX: 0,offsetY: 0}; - expectedObject.stroke = {type: 'linear',coords: {x1: 0,y1: 0,x2: 200,y2: 0},colorStops: [{offset: '0',color: 'rgb(255,0,0)',opacity: 1},{offset: '1',color: 'rgb(0,0,255)',opacity: 1}],offsetX: 0,offsetY: 0}; - return fabric.Rect.fromObject(expectedObject).then(function(rect2) { + var expectedObject = { + ...REFERENCE_RECT, + fill: { type: 'linear', coords: { x1: 0, y1: 0, x2: 200, y2: 0 }, colorStops: [{ offset: '0', color: 'rgb(255,0,0)', opacity: 1 }, { offset: '1', color: 'rgb(0,0,255)', opacity: 1 }], offsetX: 0, offsetY: 0 }, + stroke: { type: 'linear', coords: { x1: 0, y1: 0, x2: 200, y2: 0 }, colorStops: [{ offset: '0', color: 'rgb(255,0,0)', opacity: 1 }, { offset: '1', color: 'rgb(0,0,255)', opacity: 1 }], offsetX: 0, offsetY: 0 } + }; + return fabric.Rect.fromObject(expectedObject).then(function (rect2) { assert.ok(rect2.fill instanceof fabric.Gradient); assert.ok(rect2.stroke instanceof fabric.Gradient); done(); @@ -106,10 +108,8 @@ var elRect = fabric.getDocument().createElementNS('http://www.w3.org/2000/svg', 'rect'); fabric.Rect.fromElement(elRect, function(rect) { - var expectedObject = fabric.util.object.extend({ }, REFERENCE_RECT); - expectedObject.visible = false; assert.ok(rect instanceof fabric.Rect); - assert.deepEqual(rect.toObject(), expectedObject); + assert.deepEqual(rect.toObject(), { ...REFERENCE_RECT, visible: false }); }); }); @@ -136,7 +136,8 @@ fabric.Rect.fromElement(elRectWithAttrs, function(rectWithAttrs) { assert.ok(rectWithAttrs instanceof fabric.Rect); assert.equal(rectWithAttrs.strokeUniform, true, 'strokeUniform is parsed'); - var expectedObject = fabric.util.object.extend(REFERENCE_RECT, { + var expectedObject = { + ...REFERENCE_RECT, left: 10, top: 20, width: 222, @@ -152,7 +153,7 @@ rx: 11, ry: 12, strokeUniform: true - }); + }; assert.deepEqual(rectWithAttrs.toObject(), expectedObject); }); }); diff --git a/test/unit/text.js b/test/unit/text.js index ed451c1a1b1..2b99c005278 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -208,14 +208,15 @@ fabric.Text.fromElement(elText, function(text) { assert.ok(text instanceof fabric.Text); - var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), { + var expectedObject = { + ...REFERENCE_TEXT_OBJECT, left: 0, top: -14.05, width: 8, height: 18.08, fontSize: 16, originX: 'left' - }); + }; assert.deepEqual(text.toObject(), expectedObject, 'parsed object is what expected'); }); }); @@ -249,7 +250,8 @@ assert.ok(textWithAttrs instanceof fabric.Text); - var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), { + var expectedObject = { + ...REFERENCE_TEXT_OBJECT, /* left varies slightly due to node-canvas rendering */ left: fabric.util.toFixed(textWithAttrs.left + '', 2), top: -88.03, @@ -270,7 +272,7 @@ fontWeight: 'bold', fontSize: 123, underline: true, - }); + }; assert.deepEqual(textWithAttrs.toObject(), expectedObject); }); }); diff --git a/test/unit/util.js b/test/unit/util.js index 9c913b99e76..fef1b0b9c8a 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -171,59 +171,6 @@ assert.equal(capitalize('2foo'), '2foo'); }); - QUnit.test('fabric.util.object.extend', function(assert) { - var extend = fabric.util.object.extend; - - assert.ok(typeof extend === 'function'); - - var destination = { x: 1 }, - source = { y: 2 }; - - extend(destination, source); - - assert.equal(destination.x, 1); - assert.equal(destination.y, 2); - assert.equal(source.x, undefined); - assert.equal(source.y, 2); - - destination = { x: 1 }; - source = { x: 2 }; - - extend(destination, source); - - assert.equal(destination.x, 2); - assert.equal(source.x, 2); - }); - - QUnit.test('fabric.util.object.extend deep', function(assert) { - var extend = fabric.util.object.extend; - var d = function() { }; - var destination = { x: 1 }, - source = { y: 2, a: { b: 1, c: [1, 2, 3, d] } }; - - extend(destination, source, true); - - assert.equal(destination.x, 1, 'x is still in destination'); - assert.equal(destination.y, 2, 'y has been added'); - assert.deepEqual(destination.a, source.a, 'a has been copied deeply'); - assert.notEqual(destination.a, source.a, 'is not the same object'); - assert.ok(typeof source.a.c[3] === 'function', 'is a function'); - assert.equal(destination.a.c[3], source.a.c[3], 'functions get referenced'); - }); - - QUnit.test('fabric.util.object.clone', function(assert) { - var clone = fabric.util.object.clone; - - assert.ok(typeof clone === 'function'); - - var obj = { x: 1, y: [1, 2, 3] }, - _clone = clone(obj); - - assert.equal(_clone.x, 1); - assert.notEqual(obj, _clone); - assert.equal(_clone.y, obj.y); - }); - QUnit.test('Function.prototype.bind', function(assert) { assert.ok(typeof Function.prototype.bind === 'function');