UNPKG

tui-dom

Version:
553 lines (471 loc) 13.4 kB
/** * @fileoverview DOM manipulation utility module * @author NHN Ent. FE Development Lab <dl_javascript@nhnent.com> */ import * as domevent from './domevent'; import snippet from 'tui-code-snippet'; const aps = Array.prototype.slice; const trim = str => str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); /** * Setting element style * @param {(HTMLElement|SVGElement)} element - element to setting style * @param {(string|object)} key - style prop name or {prop: value} pair object * @param {string} [value] - style value * @name css * @memberof tui.dom * @function * @api */ export function css(element, key, value) { const {style} = element; if (snippet.isString(key)) { style[key] = value; return; } snippet.forEach(key, (v, k) => { style[k] = v; }); } /** * Get HTML element's design classes. * @param {(HTMLElement|SVGElement)} element target element * @returns {string} element css class name * @name getClass * @memberof tui.dom * @function * @api */ export function getClass(element) { if (!element || !element.className) { return ''; } if (snippet.isUndefined(element.className.baseVal)) { return element.className; } return element.className.baseVal; } /** * Check element has specific css class * @param {(HTMLElement|SVGElement)} element - target element * @param {string} cssClass - css class * @returns {boolean} * @name hasClass * @memberof tui.dom * @function * @api */ export function hasClass(element, cssClass) { if (element.classList) { return element.classList.contains(cssClass); } const origin = getClass(element).split(/\s+/); return snippet.inArray(cssClass, origin) > -1; } /** * Set className value * @param {(HTMLElement|SVGElement)} element - target element * @param {(string|string[])} cssClass - class names * @ignore */ function setClassName(element, cssClass) { cssClass = snippet.isArray(cssClass) ? cssClass.join(' ') : cssClass; cssClass = trim(cssClass); if (snippet.isUndefined(element.className.baseVal)) { element.className = cssClass; return; } element.className.baseVal = cssClass; } /** * Add css class to element * @param {(HTMLElement|SVGElement)} element - target element * @param {...string} cssClass - css classes to add * @name addClass * @memberof tui.dom * @function */ export function addClass(element) { let cssClass = aps.call(arguments, 1); // eslint-disable-line prefer-rest-params if (element.classList) { const {classList} = element; snippet.forEach(cssClass, name => { classList.add(name); }); return; } const origin = getClass(element); if (origin) { cssClass = [].concat(origin.split(/\s+/), cssClass); } const newClass = []; snippet.forEach(cssClass, cls => { if (snippet.inArray(cls, newClass) < 0) { newClass.push(cls); } }); setClassName(element, newClass); } /** * Toggle css class * @param {(HTMLElement|SVGElement)} element - target element * @param {...string} cssClass - css classes to toggle * @name toggleClass * @memberof tui.dom * @function */ export function toggleClass(element) { const cssClass = aps.call(arguments, 1); // eslint-disable-line prefer-rest-params if (element.classList) { snippet.forEach(cssClass, name => { element.classList.toggle(name); }); return; } const newClass = getClass(element).split(/\s+/); snippet.forEach(cssClass, name => { const idx = snippet.inArray(name, newClass); if (idx > -1) { newClass.splice(idx, 1); } else { newClass.push(name); } }); setClassName(element, newClass); } /** * Remove css class from element * @param {(HTMLElement|SVGElement)} element - target element * @param {...string} cssClass - css classes to remove * @name removeClass * @memberof tui.dom * @function */ export function removeClass(element) { // eslint-disable-line const cssClass = aps.call(arguments, 1); // eslint-disable-line prefer-rest-params if (element.classList) { const {classList} = element; snippet.forEach(cssClass, name => { classList.remove(name); }); return; } const origin = getClass(element).split(/\s+/); const newClass = snippet.filter( origin, name => snippet.inArray(name, cssClass) < 0 ); setClassName(element, newClass); } /** * getBoundingClientRect polyfill * @param {HTMLElement} element - target element * @returns {object} rect object * @name getRect * @memberof tui.dom * @function */ export function getRect(element) { const rect = element.getBoundingClientRect(); const {top, right, bottom, left} = rect; let {width, height} = rect; if (snippet.isUndefined(width) || snippet.isUndefined(height)) { width = element.offsetWidth; height = element.offsetHeight; } return {top, right, bottom, left, width, height}; } /** * Convert uppercase letter to hyphen lowercase character * @param {string} match - match from String.prototype.replace method * @returns {string} * @name upperToHyphenLower * @memberof tui.dom * @function */ function upperToHyphenLower(match) { return `-${match.toLowerCase()}`; } /** * Set data attribute to target element * @param {HTMLElement} element - element to set data attribute * @param {string} key - key * @param {string} value - value * @name setData * @memberof tui.dom * @function */ export function setData(element, key, value) { if (element.dataset) { element.dataset[key] = value; return; } key = key.replace(/([A-Z])/g, upperToHyphenLower); element.setAttribute(`data-${key}`, value); } /** * Get data value from data-attribute * @param {HTMLElement} element - target element * @param {string} key - key * @returns {string} value * @name getData * @memberof tui.dom * @function */ export function getData(element, key) { if (element.dataset) { return element.dataset[key]; } key = key.replace(/([A-Z])/g, upperToHyphenLower); return element.getAttribute(`data-${key}`); } /** * Remove data property * @param {HTMLElement} element - target element * @param {string} key - key * @name removeData * @memberof tui.dom * @function */ export function removeData(element, key) { if (element.dataset) { delete element.dataset[key]; return; } key = key.replace(/([A-Z])/g, upperToHyphenLower); element.removeAttribute(`data-${key}`); } /** * Remove element from parent node. * @param {HTMLElement} element - element to remove. * @name removeElement * @memberof tui.dom * @function */ export function removeElement(element) { if (element && element.parentNode) { element.parentNode.removeChild(element); } } /** * Set element bound * @param {HTMLElement} element - element to change bound * @param {object} bound - bound object * @param {number} [bound.top] - top pixel * @param {number} [bound.right] - right pixel * @param {number} [bound.bottom] - bottom pixel * @param {number} [bound.left] - left pixel * @param {number} [bound.width] - width pixel * @param {number} [bound.height] - height pixel * @name setBound * @memberof tui.dom * @function */ export function setBound(element, {top, right, bottom, left, width, height} = {}) { const args = {top, right, bottom, left, width, height}; const newBound = {}; snippet.forEach(args, (value, prop) => { if (snippet.isExisty(value)) { newBound[prop] = snippet.isNumber(value) ? `${value}px` : value; } }); snippet.extend(element.style, newBound); } const elProto = Element.prototype; const matchSelector = elProto.matches || elProto.webkitMatchesSelector || elProto.mozMatchesSelector || elProto.msMatchesSelector || function(selector) { const doc = this.document || this.ownerDocument; return snippet.inArray(this, findAll(doc, selector)) > -1; }; /** * Check element match selector * @param {HTMLElement} element - element to check * @param {string} selector - selector to check * @returns {boolean} is selector matched to element? * @name matches * @memberof tui.dom * @function */ export function matches(element, selector) { return matchSelector.call(element, selector); } /** * Find parent element recursively * @param {HTMLElement} element - base element to start find * @param {string} selector - selector string for find * @returns {HTMLElement} - element finded or null * @name closest * @memberof tui.dom * @function */ export function closest(element, selector) { let parent = element.parentNode; if (matches(element, selector)) { return element; } while (parent && parent !== document) { if (matches(parent, selector)) { return parent; } parent = parent.parentNode; } return null; } /** * Find single element * @param {(HTMLElement|string)} [element=document] - base element to find * @param {string} [selector] - css selector * @returns {HTMLElement} * @name find * @memberof tui.dom * @function */ export function find(element, selector) { if (snippet.isString(element)) { return document.querySelector(element); } return element.querySelector(selector); } /** * Find multiple element * @param {(HTMLElement|string)} [element=document] - base element to * find * @param {string} [selector] - css selector * @returns {HTMLElement[]} * @name findAll * @memberof tui.dom * @function */ export function findAll(element, selector) { if (snippet.isString(element)) { return snippet.toArray(document.querySelectorAll(element)); } return snippet.toArray(element.querySelectorAll(selector)); } /** * Stop event propagation. * @param {Event} e - event object * @name stopPropagation * @memberof tui.dom * @function */ export function stopPropagation(e) { if (e.stopPropagation) { e.stopPropagation(); return; } e.cancelBubble = true; } /** * Prevent default action * @param {Event} e - event object * @name preventDefault * @memberof tui.dom * @function */ export function preventDefault(e) { if (e.preventDefault) { e.preventDefault(); return; } e.returnValue = false; } /** * Check specific CSS style is available. * @param {array} props property name to testing * @returns {(string|boolean)} return true when property is available * @name testCSSProp * @memberof tui.dom * @function * @example * //-- #1. Get Module --// * var domUtil = require('tui-dom'); // node, commonjs * var domUtil = tui.dom; // distribution file * * //-- #2. Use property --// * var props = ['transform', '-webkit-transform']; * domutil.testCSSProp(props); // 'transform' */ function testCSSProp(props) { const {style} = document.documentElement; const len = props.length; for (let i = 0; i < len; i += 1) { if (props[i] in style) { return props[i]; } } return false; } let prevSelectStyle = ''; const SUPPORT_SELECTSTART = 'onselectstart' in document; const userSelectProperty = testCSSProp([ 'userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect' ]); /** * Disable browser's text selection behaviors. * @param {HTMLElement} [el] - target element. if not supplied, use `document` * @name disableTextSelection * @memberof tui.dom * @function */ export function disableTextSelection(el = document) { let style; if (SUPPORT_SELECTSTART) { domevent.on(el, 'selectstart', preventDefault); } else { el = (el === document) ? document.documentElement : el; style = el.style; prevSelectStyle = style[userSelectProperty]; style[userSelectProperty] = 'none'; } } /** * Enable browser's text selection behaviors. * @param {HTMLElement} [el] - target element. if not supplied, use `document` * @name enableTextSelection * @memberof tui.dom * @function */ export function enableTextSelection(el = document) { if (SUPPORT_SELECTSTART) { domevent.off(el, 'selectstart', preventDefault); } else { el = (el === document) ? document.documentElement : el; el.style[userSelectProperty] = prevSelectStyle; } } /** * Represents the text content of a node and its descendants * @param {HTMLElement} element - html element * @returns {string} text content * @name textContent * @memberof tui.dom * @function */ export function textContent(element) { if (snippet.isExisty(element.textContent)) { return element.textContent; } return element.innerText; } /** * Insert element to next of target element * @param {HTMLElement} element - html element to insert * @param {HTMLElement} target - target element * @name insertAfter * @memberof tui.dom * @function */ export function insertAfter(element, target) { const parent = target.parentNode; if (target === parent.lastChild) { parent.appendChild(element); } else { parent.insertBefore(element, target.nextSibling); } }