type ResizeListener = (width: number, height: number) => void; interface WH { w: T; h: T; } interface XY { x: T; y: T; } const splitEventNames = (eventNames: string) => eventNames.split(' '); const on = ( target: EventTarget, eventNames: string, listener: (event: T) => any ): void => { splitEventNames(eventNames).forEach((eventName) => { target.addEventListener(eventName, listener as EventListener); }); }; const off = ( target: EventTarget, eventNames: string, listener: (event: T) => any ): void => { splitEventNames(eventNames).forEach((eventName) => { target.removeEventListener(eventName, listener as EventListener); }); }; const clientSize = (elm: HTMLElement): WH => ({ w: elm.clientWidth, h: elm.clientHeight, }); export const resize = (element: HTMLElement) => { const resizeListeners: ResizeListener[] = []; const strMouseTouchDownEvent = 'mousedown touchstart'; const strMouseTouchUpEvent = 'mouseup touchend'; const strMouseTouchMoveEvent = 'mousemove touchmove'; const strSelectStartEvent = 'selectstart'; const dragStartSize: WH = { w: 0, h: 0 }; const dragStartPosition: XY = { x: 0, y: 0 }; const resizeBtn = document.createElement('div'); resizeBtn.classList.add('resizeBtn'); element.append(resizeBtn); element.classList.add('resizer'); // eslint-disable-next-line @typescript-eslint/no-unused-vars let dragResizeBtn: HTMLElement | undefined; let dragResizer: HTMLElement | undefined; const onSelectStart = (event: Event) => { event.preventDefault(); return false; }; const resizerResize = (event: MouseEvent | TouchEvent) => { const isTouchEvent = (event as TouchEvent).touches !== undefined; const mouseOffsetHolder = isTouchEvent ? (event as TouchEvent).touches[0] : (event as MouseEvent); const sizeStyle = { width: dragStartSize.w + mouseOffsetHolder.pageX - dragStartPosition.x, height: dragStartSize.h + mouseOffsetHolder.pageY - dragStartPosition.y, }; dragResizer!.style.width = `${sizeStyle.width}px`; dragResizer!.style.height = `${sizeStyle.height}px`; resizeListeners.forEach((listener: ResizeListener) => { if (listener) { listener(sizeStyle.width, sizeStyle.height); } }); event.stopPropagation(); }; const resizerResized = () => { off(document, strSelectStartEvent, onSelectStart); off(document, strMouseTouchMoveEvent, resizerResize); off(document, strMouseTouchUpEvent, resizerResized); dragResizer = undefined; dragResizeBtn = undefined; }; on(resizeBtn, strMouseTouchDownEvent, (event: MouseEvent | TouchEvent) => { const { currentTarget } = event; const correctButton = (event as MouseEvent).buttons === 1 || event.which === 1; const isTouchEvent = (event as TouchEvent).touches !== undefined; const mouseOffsetHolder = isTouchEvent ? (event as TouchEvent).touches[0] : (event as MouseEvent); if (correctButton || isTouchEvent) { dragStartPosition.x = mouseOffsetHolder.pageX; dragStartPosition.y = mouseOffsetHolder.pageY; dragResizeBtn = currentTarget as HTMLElement; dragResizer = (currentTarget as HTMLElement).parentElement as HTMLElement; const cSize = clientSize(element); dragStartSize.w = cSize.w; dragStartSize.h = cSize.h; on(document, strSelectStartEvent, onSelectStart); on(document, strMouseTouchMoveEvent, resizerResize); on(document, strMouseTouchUpEvent, resizerResized); event.preventDefault(); event.stopPropagation(); } }); return { addResizeListener(listener: ResizeListener) { resizeListeners.push(listener); }, }; };