/* * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ import { window } from "./browser"; import { PREVENT_DRAG_CSSPROPS } from "./const"; import { PanInputOption } from "./inputType/PanInput"; import { PinchInputOption } from "./inputType/PinchInput"; import { ObjectInterface } from "./types"; import { DIRECTION_NONE, DIRECTION_VERTICAL, DIRECTION_HORIZONTAL, DIRECTION_ALL, } from "./const"; declare let jQuery: any; export const toArray = (nodes: NodeList): HTMLElement[] => { // const el = Array.prototype.slice.call(nodes); // for IE8 const el = []; for (let i = 0, len = nodes.length; i < len; i++) { el.push(nodes[i]); } return el; }; export const $ = (param, multi = false) => { let el; if (typeof param === "string") { // String (HTML, Selector) // check if string is HTML tag format const match = param.match(/^<([a-z]+)\s*([^>]*)>/); // creating element if (match) { // HTML const dummy = document.createElement("div"); dummy.innerHTML = param; el = toArray(dummy.childNodes); } else { // Selector el = toArray(document.querySelectorAll(param)); } if (!multi) { el = el.length >= 1 ? el[0] : undefined; } } else if (param === window) { // window el = param; } else if ("value" in param || "current" in param) { el = param.value! || param.current!; } else if (param.nodeName && (param.nodeType === 1 || param.nodeType === 9)) { // HTMLElement, Document el = param; } else if ( ("jQuery" in window && param instanceof jQuery) || param.constructor.prototype.jquery ) { // jQuery el = multi ? param.toArray() : param.get(0); } else if (Array.isArray(param)) { el = param.map((v) => $(v)); if (!multi) { el = el.length >= 1 ? el[0] : undefined; } } return el; }; let raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame; let caf = window.cancelAnimationFrame || window.webkitCancelAnimationFrame; if (raf && !caf) { const keyInfo = {}; const oldraf = raf; raf = (callback: FrameRequestCallback) => { const wrapCallback = (timestamp: number) => { if (keyInfo[key]) { callback(timestamp); } }; const key = oldraf(wrapCallback); keyInfo[key] = true; return key; }; caf = (key: number) => { delete keyInfo[key]; }; } else if (!(raf && caf)) { raf = (callback: FrameRequestCallback) => { return window.setTimeout(() => { callback( ((window.performance && window.performance.now && window.performance.now()) as number) || new Date().getTime() ); }, 16); }; caf = window.clearTimeout; } /** * A polyfill for the window.requestAnimationFrame() method. * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame * @private */ export const requestAnimationFrame = (fp) => { return raf(fp); }; /** * A polyfill for the window.cancelAnimationFrame() method. It cancels an animation executed through a call to the requestAnimationFrame() method. * @param {Number} key − The ID value returned through a call to the requestAnimationFrame() method. requestAnimationFrame() 메서드가 반환한 아이디 값 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame * @private */ export const cancelAnimationFrame = (key) => { caf(key); }; export const map = ( obj: ObjectInterface, callback: (value: T, key: string) => U ): ObjectInterface => { const tranformed: ObjectInterface = {}; for (const k in obj) { if (k) { tranformed[k] = callback(obj[k], k); } } return tranformed; }; export const filter = ( obj: ObjectInterface, callback: (value: T, key: string) => boolean ): ObjectInterface => { const filtered: ObjectInterface = {}; for (const k in obj) { if (k && callback(obj[k], k)) { filtered[k] = obj[k]; } } return filtered; }; export const every = ( obj: ObjectInterface, callback: (value: T, key: string) => boolean ) => { for (const k in obj) { if (k && !callback(obj[k], k)) { return false; } } return true; }; export const equal = ( target: ObjectInterface, base: ObjectInterface ): boolean => { return every(target, (v, k) => v === base[k]); }; const roundNumFunc = {}; export const roundNumber = (num: number, roundUnit: number) => { // Cache for performance if (!roundNumFunc[roundUnit]) { roundNumFunc[roundUnit] = getRoundFunc(roundUnit); } return roundNumFunc[roundUnit](num); }; export const roundNumbers = ( num: ObjectInterface, roundUnit: ObjectInterface | number ): ObjectInterface => { if (!num || !roundUnit) { return num; } return map(num, (value, key) => roundNumber( value, typeof roundUnit === "number" ? roundUnit : roundUnit[key] ) ); }; export const getDecimalPlace = (val: number): number => { if (!isFinite(val)) { return 0; } const v = `${val}`; if (v.indexOf("e") >= 0) { // Exponential Format // 1e-10, 1e-12 let p = 0; let e = 1; while (Math.round(val * e) / e !== val) { e *= 10; p++; } return p; } // In general, following has performance benefit. // https://jsperf.com/precision-calculation return v.indexOf(".") >= 0 ? v.length - v.indexOf(".") - 1 : 0; }; export const inversePow = (n: number) => { // replace Math.pow(10, -n) to solve floating point issue. // eg. Math.pow(10, -4) => 0.00009999999999999999 return 1 / Math.pow(10, n); }; export const getRoundFunc = (v: number) => { const p = v < 1 ? Math.pow(10, getDecimalPlace(v)) : 1; return (n: number) => { if (v === 0) { return 0; } return Math.round(Math.round(n / v) * v * p) / p; }; }; export const getAngle = (posX: number, posY: number) => { return (Math.atan2(posY, posX) * 180) / Math.PI; }; export const isCssPropsFromAxes = (originalCssProps: { [key: string]: string; }) => { let same = true; Object.keys(PREVENT_DRAG_CSSPROPS).forEach((prop) => { if ( !originalCssProps || originalCssProps[prop] !== PREVENT_DRAG_CSSPROPS[prop] ) { same = false; } }); return same; }; export const getDirection = ( useHorizontal: boolean, useVertical: boolean ): number => { if (useHorizontal && useVertical) { return DIRECTION_ALL; } else if (useHorizontal) { return DIRECTION_HORIZONTAL; } else if (useVertical) { return DIRECTION_VERTICAL; } else { return DIRECTION_NONE; } }; export const useDirection = ( checkType: number, direction: number, userDirection?: number ): boolean => { if (userDirection) { return !!( direction === DIRECTION_ALL || (direction & checkType && userDirection & checkType) ); } else { return !!(direction & checkType); } }; export const setCssProps = ( element: HTMLElement, option: PanInputOption | PinchInputOption, direction: number ): { [key: string]: string } => { const touchActionMap = { [DIRECTION_NONE]: "auto", [DIRECTION_ALL]: "none", [DIRECTION_VERTICAL]: "pan-x", [DIRECTION_HORIZONTAL]: "pan-y", }; const oldCssProps = {}; if (element && element.style) { const touchAction = option.touchAction ? option.touchAction : touchActionMap[direction]; const newCssProps = { ...PREVENT_DRAG_CSSPROPS, "touch-action": element.style["touch-action"] === "none" ? "none" : touchAction, }; Object.keys(newCssProps).forEach((prop) => { oldCssProps[prop] = element.style[prop]; }); // Old style props like user-select can be corrupted if you change the style directly in the logic above. Object.keys(newCssProps).forEach((prop) => { element.style[prop] = newCssProps[prop]; }); } return oldCssProps; }; export const revertCssProps = ( element: HTMLElement, originalCssProps: { [key: string]: string } ): { [key: string]: string } => { if (element && element.style && originalCssProps) { Object.keys(originalCssProps).forEach((prop) => { element.style[prop] = originalCssProps[prop]; }); } return; };