From ed96500a68d324d552e60b22b764b118c2e219f3 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 28 Oct 2022 13:28:58 +0400 Subject: [PATCH] Move Resizer to TS --- src/utils/{Resizer.js => Resizer.ts} | 457 +++++++++++++++++++-------- 1 file changed, 327 insertions(+), 130 deletions(-) rename src/utils/{Resizer.js => Resizer.ts} (56%) diff --git a/src/utils/Resizer.js b/src/utils/Resizer.ts similarity index 56% rename from src/utils/Resizer.js rename to src/utils/Resizer.ts index 80d8329fd..6cc7afbdc 100644 --- a/src/utils/Resizer.js +++ b/src/utils/Resizer.ts @@ -1,75 +1,221 @@ import { bindAll, defaults, isFunction, each } from 'underscore'; import { on, off, normalizeFloat } from './mixins'; -var defaultOpts = { - // Function which returns custom X and Y coordinates of the mouse - mousePosFetcher: null, - // Indicates custom target updating strategy - updateTarget: null, - // Function which gets HTMLElement as an arg and returns it relative position - ratioDefault: 0, - posFetcher: null, - onStart: null, - onMove: null, - onEnd: null, - onUpdateContainer: () => {}, - - // Resize unit step - step: 1, - - // Minimum dimension - minDim: 10, - - // Maximum dimension - maxDim: '', - - // Unit used for height resizing - unitHeight: 'px', - - // Unit used for width resizing - unitWidth: 'px', - - // The key used for height resizing - keyHeight: 'height', - - // The key used for width resizing - keyWidth: 'width', - - // If true, will override unitHeight and unitWidth, on start, with units - // from the current focused element (currently used only in SelectComponent) - currentUnit: 1, - - // With this option active the mousemove event won't be altered when - // the pointer comes over iframes - silentFrames: 0, - - // If true the container of handlers won't be updated - avoidContainerUpdate: 0, - - // If height is 'auto', this setting will preserve it and only update width - keepAutoHeight: false, - - // If width is 'auto', this setting will preserve it and only update height - keepAutoWidth: false, - - // When keepAutoHeight is true and the height has the value 'auto', this is set to true and height isn't updated - autoHeight: false, - - // When keepAutoWidth is true and the width has the value 'auto', this is set to true and width isn't updated - autoWidth: false, - - // Handlers - tl: 1, // Top left - tc: 1, // Top center - tr: 1, // Top right - cl: 1, // Center left - cr: 1, // Center right - bl: 1, // Bottom left - bc: 1, // Bottom center - br: 1, // Bottom right +type Position = { + x: number; + y: number; }; -var createHandler = (name, opts) => { +type RectDim = { + t: number; + l: number; + w: number; + h: number; +}; + +type BoundingRect = { + left: number; + top: number; + width: number; + height: number; +}; + +type CallbackOptions = { + docs: any; + config: any; + el: HTMLElement; + resizer: Resizer; +}; + +interface ResizerOptions { + /** + * Function which returns custom X and Y coordinates of the mouse. + */ + mousePosFetcher?: (ev: Event) => Position; + + /** + * Indicates custom target updating strategy. + */ + updateTarget?: (el: HTMLElement, rect: RectDim, opts: any) => void; + + /** + * Function which gets HTMLElement as an arg and returns it relative position + */ + posFetcher?: (el: HTMLElement, opts: any) => BoundingRect; + + /** + * Indicate if the resizer should keep the default ratio. + * @default false + */ + ratioDefault?: boolean; + + /** + * On resize start callback. + */ + onStart?: (ev: Event, opts: CallbackOptions) => void; + + /** + * On resize move callback. + */ + onMove?: (ev: Event) => void; + + /** + * On resize end callback. + */ + onEnd?: (ev: Event, opts: CallbackOptions) => void; + + /** + * On container update callback. + */ + onUpdateContainer?: (opts: any) => void; + + /** + * Resize unit step. + * @default 1 + */ + step?: number; + + /** + * Minimum dimension. + * @default 10 + */ + minDim?: number; + + /** + * Maximum dimension. + * @default Infinity + */ + maxDim?: number; + + /** + * Unit used for height resizing. + * @default 'px' + */ + unitHeight?: string; + + /** + * Unit used for width resizing. + * @default 'px' + */ + unitWidth?: string; + + /** + * The key used for height resizing. + * @default 'height' + */ + keyHeight?: string; + + /** + * The key used for width resizing. + * @default 'width' + */ + keyWidth?: string; + + /** + * If true, will override unitHeight and unitWidth, on start, with units + * from the current focused element (currently used only in SelectComponent). + * @default true + */ + currentUnit?: boolean; + + /** + * With this option enabled the mousemove event won't be altered when the pointer comes over iframes. + * @default false + */ + silentFrames?: boolean; + + /** + * If true the container of handlers won't be updated. + * @default false + */ + avoidContainerUpdate?: boolean; + + /** + * If height is 'auto', this setting will preserve it and only update the width. + * @default false + */ + keepAutoHeight?: boolean; + + /** + * If width is 'auto', this setting will preserve it and only update the height. + * @default false + */ + keepAutoWidth?: boolean; + + /** + * When keepAutoHeight is true and the height has the value 'auto', this is set to true and height isn't updated. + * @default false + */ + autoHeight?: boolean; + + /** + * When keepAutoWidth is true and the width has the value 'auto', this is set to true and width isn't updated. + * @default false + */ + autoWidth?: boolean; + + /** + * Enable top left handler. + * @default true + */ + tl?: boolean; + + /** + * Enable top center handler. + * @default true + */ + tc?: boolean; + + /** + * Enable top right handler. + * @default true + */ + tr?: boolean; + + /** + * Enable center left handler. + * @default true + */ + cl?: boolean; + + /** + * Enable center right handler. + * @default true + */ + cr?: boolean; + + /** + * Enable bottom left handler. + * @default true + */ + bl?: boolean; + + /** + * Enable bottom center handler. + * @default true + */ + bc?: boolean; + + /** + * Enable bottom right handler. + * @default true + */ + br?: boolean; + + /** + * Class prefix. + */ + prefix?: string; + + /** + * Where to append resize container (default body element). + */ + appendTo?: HTMLElement; +} + +type Handlers = Record; + +const createHandler = (name: string, opts: { prefix?: string } = {}) => { var pfx = opts.prefix || ''; var el = document.createElement('i'); el.className = pfx + 'resizer-h ' + pfx + 'resizer-h-' + name; @@ -77,7 +223,7 @@ var createHandler = (name, opts) => { return el; }; -var getBoundingRect = (el, win) => { +const getBoundingRect = (el: HTMLElement, win?: Window): BoundingRect => { var w = win || window; var rect = el.getBoundingClientRect(); return { @@ -88,15 +234,63 @@ var getBoundingRect = (el, win) => { }; }; -class Resizer { +export default class Resizer { + opts: ResizerOptions; + container?: HTMLElement; + handlers?: Handlers; + el?: HTMLElement; + clickedHandler?: HTMLElement; + selectedHandler?: HTMLElement; + handlerAttr?: string; + startDim?: RectDim; + rectDim?: RectDim; + parentDim?: RectDim; + startPos?: Position; + delta?: Position; + currentPos?: Position; + docs?: Document[]; + keys?: { shift: boolean; ctrl: boolean; alt: boolean }; + mousePosFetcher?: ResizerOptions['mousePosFetcher']; + updateTarget?: ResizerOptions['updateTarget']; + posFetcher?: ResizerOptions['posFetcher']; + onStart?: ResizerOptions['onStart']; + onMove?: ResizerOptions['onMove']; + onEnd?: ResizerOptions['onEnd']; + onUpdateContainer?: ResizerOptions['onUpdateContainer']; + /** * Init the Resizer with options * @param {Object} options */ constructor(opts = {}) { + this.opts = { + ratioDefault: false, + onUpdateContainer: () => {}, + step: 1, + minDim: 10, + maxDim: Infinity, + unitHeight: 'px', + unitWidth: 'px', + keyHeight: 'height', + keyWidth: 'width', + currentUnit: true, + silentFrames: false, + avoidContainerUpdate: false, + keepAutoHeight: false, + keepAutoWidth: false, + autoHeight: false, + autoWidth: false, + tl: true, + tc: true, + tr: true, + cl: true, + cr: true, + bl: true, + bc: true, + br: true, + }; this.setOptions(opts); bindAll(this, 'handleKeyDown', 'handleMouseDown', 'move', 'stop'); - return this; } /** @@ -111,8 +305,11 @@ class Resizer { * Setup options * @param {Object} options */ - setOptions(options = {}) { - this.opts = defaults(options, defaultOpts); + setOptions(options: Partial = {}) { + this.opts = { + ...this.opts, + ...options, + }; this.setup(); } @@ -138,9 +335,10 @@ class Resizer { } // Create handlers - const handlers = {}; + const handlers: Handlers = {}; ['tl', 'tc', 'tr', 'cl', 'cr', 'bl', 'bc', 'br'].forEach( - hdl => (handlers[hdl] = opts[hdl] ? createHandler(hdl, opts) : '') + // @ts-ignore + hdl => (handlers[hdl] = opts[hdl] ? createHandler(hdl, opts) : null) ); for (let n in handlers) { @@ -162,7 +360,7 @@ class Resizer { * Toggle iframes pointer event * @param {Boolean} silent If true, iframes will be silented */ - toggleFrames(silent) { + toggleFrames(silent?: boolean) { if (this.opts.silentFrames) { const frames = document.querySelectorAll('iframe'); each(frames, frame => (frame.style.pointerEvents = silent ? 'none' : '')); @@ -174,8 +372,8 @@ class Resizer { * @param {HTMLElement} el * @return {Boolean} */ - isHandler(el) { - var handlers = this.handlers; + isHandler(el: HTMLElement) { + const { handlers } = this; for (var n in handlers) { if (handlers[n] === el) return true; @@ -197,14 +395,14 @@ class Resizer { * @return {HTMLElement} */ getParentEl() { - return this.el.parentElement; + return this.el?.parentElement; } /** * Returns documents */ getDocumentEl() { - return [this.el.ownerDocument, document]; + return [this.el!.ownerDocument, document]; } /** @@ -213,8 +411,8 @@ class Resizer { * @param {Object} opts Custom options * @return {Object} */ - getElementPos(el, opts = {}) { - var posFetcher = this.posFetcher || ''; + getElementPos(el: HTMLElement, opts = {}) { + const { posFetcher } = this; return posFetcher ? posFetcher(el, opts) : getBoundingRect(el); } @@ -222,14 +420,14 @@ class Resizer { * Focus resizer on the element, attaches handlers to it * @param {HTMLElement} el */ - focus(el) { + focus(el: HTMLElement) { // Avoid focusing on already focused element if (el && el === this.el) { return; } this.el = el; - this.updateContainer({ forceShow: 1 }); + this.updateContainer({ forceShow: true }); on(this.getDocumentEl(), 'pointerdown', this.handleMouseDown); } @@ -237,11 +435,11 @@ class Resizer { * Blur from element */ blur() { - this.container.style.display = 'none'; + this.container!.style.display = 'none'; if (this.el) { off(this.getDocumentEl(), 'pointerdown', this.handleMouseDown); - this.el = null; + delete this.el; } } @@ -249,21 +447,23 @@ class Resizer { * Start resizing * @param {Event} e */ - start(e) { - //Right or middel click + start(ev: Event) { + const e = ev as PointerEvent; + // @ts-ignore Right or middel click if (e.button !== 0) return; e.preventDefault(); e.stopPropagation(); - const el = this.el; + const el = this.el!; const parentEl = this.getParentEl(); const resizer = this; const config = this.opts || {}; const mouseFetch = this.mousePosFetcher; const attrName = 'data-' + config.prefix + 'handler'; - const rect = this.getElementPos(el, { target: 'el' }); - const parentRect = this.getElementPos(parentEl); - this.handlerAttr = e.target.getAttribute(attrName); - this.clickedHandler = e.target; + const rect = this.getElementPos(el!, { target: 'el' }); + const parentRect = this.getElementPos(parentEl!); + const target = e.target as HTMLElement; + this.handlerAttr = target.getAttribute(attrName)!; + this.clickedHandler = target; this.startDim = { t: rect.top, l: rect.left, @@ -296,7 +496,7 @@ class Resizer { on(docs, 'keydown', this.handleKeyDown); on(docs, 'pointerup', this.stop); isFunction(this.onStart) && this.onStart(e, { docs, config, el, resizer }); - this.toggleFrames(1); + this.toggleFrames(true); this.move(e); } @@ -304,7 +504,8 @@ class Resizer { * While resizing * @param {Event} e */ - move(e) { + move(ev: PointerEvent | Event) { + const e = ev as PointerEvent; const onMove = this.onMove; const mouseFetch = this.mousePosFetcher; const currentPos = mouseFetch @@ -315,8 +516,8 @@ class Resizer { }; this.currentPos = currentPos; this.delta = { - x: currentPos.x - this.startPos.x, - y: currentPos.y - this.startPos.y, + x: currentPos.x - this.startPos!.x, + y: currentPos.y - this.startPos!.y, }; this.keys = { shift: e.shiftKey, @@ -325,7 +526,7 @@ class Resizer { }; this.rectDim = this.calc(this); - this.updateRect(0); + this.updateRect(false); // Move callback onMove && onMove(e); @@ -335,14 +536,14 @@ class Resizer { * Stop resizing * @param {Event} e */ - stop(e) { - const { el } = this; + stop(e: Event) { + const el = this.el!; const config = this.opts; const docs = this.docs || this.getDocumentEl(); off(docs, 'pointermove', this.move); off(docs, 'keydown', this.handleKeyDown); off(docs, 'pointerup', this.stop); - this.updateRect(1); + this.updateRect(true); this.toggleFrames(); isFunction(this.onEnd) && this.onEnd(e, { docs, config, el, resizer: this }); delete this.docs; @@ -351,11 +552,11 @@ class Resizer { /** * Update rect */ - updateRect(store) { - const el = this.el; + updateRect(store: boolean) { + const el = this.el!; const resizer = this; const config = this.opts; - const rect = this.rectDim; + const rect = this.rectDim!; const updateTarget = this.updateTarget; const selectedHandler = this.getSelectedHandler(); const { unitHeight, unitWidth, keyWidth, keyHeight } = config; @@ -369,17 +570,17 @@ class Resizer { config, }); } else { - const elStyle = el.style; - elStyle[keyWidth] = rect.w + unitWidth; - elStyle[keyHeight] = rect.h + unitHeight; + const elStyle = el.style as Record; + elStyle[keyWidth!] = rect.w + unitWidth!; + elStyle[keyHeight!] = rect.h + unitHeight!; } this.updateContainer(); } - updateContainer(opt = {}) { + updateContainer(opt: { forceShow?: boolean } = {}) { const { opts, container, el } = this; - const { style } = container; + const { style } = container!; if (!opts.avoidContainerUpdate && el) { // On component resize container fits the tool, @@ -390,8 +591,8 @@ class Resizer { if (opt.forceShow) style.display = 'block'; } - this.onUpdateContainer({ - el: container, + this.onUpdateContainer?.({ + el: container!, resizer: this, opts: { ...opts, @@ -420,7 +621,8 @@ class Resizer { * Handle ESC key * @param {Event} e */ - handleKeyDown(e) { + handleKeyDown(e: Event) { + // @ts-ignore if (e.keyCode === 27) { // Rollback to initial dimensions this.rectDim = this.startDim; @@ -432,13 +634,14 @@ class Resizer { * Handle mousedown to check if it's possible to start resizing * @param {Event} e */ - handleMouseDown(e) { - var el = e.target; + handleMouseDown(e: Event) { + const el = e.target as HTMLElement; + if (this.isHandler(el)) { this.selectedHandler = el; this.start(e); } else if (el !== this.el) { - this.selectedHandler = ''; + delete this.selectedHandler; this.blur(); } } @@ -447,17 +650,17 @@ class Resizer { * All positioning logic * @return {Object} */ - calc(data) { + calc(data: Resizer) { let value; const opts = this.opts || {}; - const step = opts.step; - const startDim = this.startDim; - const minDim = opts.minDim; + const step = opts.step!; + const startDim = this.startDim!; + const minDim = opts.minDim!; const maxDim = opts.maxDim; - const deltaX = data.delta.x; - const deltaY = data.delta.y; - const parentW = this.parentDim.w; - const parentH = this.parentDim.h; + const deltaX = data.delta!.x; + const deltaY = data.delta!.y; + const parentW = this.parentDim!.w; + const parentH = this.parentDim!.h; const unitWidth = this.opts.unitWidth; const unitHeight = this.opts.unitHeight; const startW = unitWidth === '%' ? (startDim.w / 100) * parentW : startDim.w; @@ -471,7 +674,7 @@ class Resizer { if (!data) return; - var attr = data.handlerAttr; + var attr = data.handlerAttr!; if (~attr.indexOf('r')) { value = unitWidth === '%' @@ -510,7 +713,7 @@ class Resizer { } // Enforce aspect ratio (unless shift key is being held) - var ratioActive = opts.ratioDefault ? !data.keys.shift : data.keys.shift; + var ratioActive = opts.ratioDefault ? !data.keys!.shift : data.keys!.shift; if (attr.indexOf('c') < 0 && ratioActive) { var ratio = startDim.w / startDim.h; if (box.w / box.h > ratio) { @@ -530,9 +733,3 @@ class Resizer { return box; } } - -export default { - init(opts) { - return new Resizer(opts); - }, -};