UNPKG

@print-one/grapesjs

Version:

Free and Open Source Web Builder Framework

261 lines (212 loc) 7.2 kB
import { each, isArray, isString, isUndefined } from 'underscore'; import { ObjectAny } from '../common'; type vNode = { tag?: string; attributes?: ObjectAny; children?: vNode[]; }; type ChildHTML = HTMLElement | string; type ClassNameInputType = string | number | boolean | null | undefined; type ClassNameInput = ClassNameInputType | Array<ClassNameInputType>; const KEY_TAG = 'tag'; const KEY_ATTR = 'attributes'; const KEY_CHILD = 'children'; export const motionsEv = 'transitionend oTransitionEnd transitionend webkitTransitionEnd'; export const isDoc = (el?: Node): el is Document => el?.nodeType === Node.DOCUMENT_NODE; export const removeEl = (el?: HTMLElement) => { const parent = el && el.parentNode; parent && parent.removeChild(el); }; export function cx(...inputs: ClassNameInput[]): string { const inp = Array.isArray(inputs[0]) ? inputs[0] : [...inputs]; return inp.filter(Boolean).join(' '); } export const find = (el: HTMLElement, query: string) => el.querySelectorAll(query); export const attrUp = (el?: HTMLElement, attrs: ObjectAny = {}) => el && el.setAttribute && each(attrs, (value, key) => el.setAttribute(key, value)); export const isVisible = (el?: HTMLElement) => { return el && !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length); }; export const empty = (node: HTMLElement) => { while (node.firstChild) node.removeChild(node.firstChild); }; export const replaceWith = (oldEl: HTMLElement, newEl: HTMLElement) => { oldEl.parentNode?.replaceChild(newEl, oldEl); }; export const appendAtIndex = (parent: HTMLElement | DocumentFragment, child: ChildHTML, index?: number) => { const { childNodes } = parent; const total = childNodes.length; const at = isUndefined(index) ? total : index; if (isString(child)) { // @ts-ignore parent.insertAdjacentHTML('beforeEnd', child); child = parent.lastChild as HTMLElement; parent.removeChild(child); } if (at >= total) { parent.appendChild(child); } else { parent.insertBefore(child, childNodes[at]); } }; export const append = (parent: HTMLElement, child: ChildHTML) => appendAtIndex(parent, child); export const createEl = (tag: string, attrs: ObjectAny = {}, child?: ChildHTML) => { const el = document.createElement(tag); attrs && each(attrs, (value, key) => el.setAttribute(key, value)); if (child) { if (isString(child)) el.innerHTML = child; else el.appendChild(child); } return el; }; export const createText = (str: string) => document.createTextNode(str); // Unfortunately just creating `KeyboardEvent(e.type, e)` is not enough, // the keyCode/which will be always `0`. Even if it's an old/deprecated // property keymaster (and many others) still use it... using `defineProperty` // hack seems the only way export const createCustomEvent = (e: any, cls: any) => { let oEvent: any; const { type } = e; try { // @ts-ignore oEvent = new window[cls](type, e); } catch (err) { oEvent = document.createEvent(cls); oEvent.initEvent(type, true, true); } oEvent._parentEvent = e; if (type.indexOf('key') === 0) { oEvent.keyCodeVal = e.keyCode; ['keyCode', 'which'].forEach(prop => { Object.defineProperty(oEvent, prop, { get() { return this.keyCodeVal; }, }); }); } return oEvent; }; /** * Append an array of vNodes to an element * @param {HTMLElement} node HTML element * @param {Array} vNodes Array of node objects */ export const appendVNodes = (node: HTMLElement, vNodes: vNode | vNode[] = []) => { const vNodesArr = Array.isArray(vNodes) ? vNodes : [vNodes]; vNodesArr.forEach(vnode => { const tag = vnode[KEY_TAG] || 'div'; const attr = vnode[KEY_ATTR] || {}; const el = document.createElement(tag); each(attr, (value, key) => { el.setAttribute(key, value); }); node.appendChild(el); }); }; /** * Check if element is a text node * @param {Node} el * @return {Boolean} */ export const isTextNode = (el?: Node): el is Text => el?.nodeType === Node.TEXT_NODE; /** * Check if element is a comment node * @param {Node} el * @return {Boolean} */ export const isCommentNode = (el?: Node): el is Comment => el?.nodeType === Node.COMMENT_NODE; /** * Check if taggable node * @param {Node} el * @return {Boolean} */ export const isTaggableNode = (el?: Node) => el && !isTextNode(el) && !isCommentNode(el); export const getBoundingRect = (el: HTMLElement) => { let top = el.offsetTop; let left = el.offsetLeft; const width = el.offsetWidth; const height = el.offsetHeight; let currentEl = el; while ((currentEl = currentEl.offsetParent as HTMLElement)) { top += currentEl.offsetTop; left += currentEl.offsetLeft; } return { top, left, width, height, bottom: top + height, right: left + width, x: left, y: top, }; }; /** * Get DOMRect of the element. * @param el * @returns {DOMRect} */ export const getElRect = (el?: HTMLElement, nativeBoundingRect = true) => { const def = { top: 0, left: 0, width: 0, height: 0, }; if (!el) return def; let rectText; if (isTextNode(el)) { const range = document.createRange(); range.selectNode(el); rectText = nativeBoundingRect ? range.getBoundingClientRect() : getBoundingRect(range as unknown as HTMLElement); range.detach(); } return nativeBoundingRect ? rectText || (el.getBoundingClientRect ? el.getBoundingClientRect() : def) : rectText || getBoundingRect(el); }; /** * Get document scroll coordinates */ export const getDocumentScroll = (el?: HTMLElement) => { const doc = el?.ownerDocument || document; const docEl = doc.documentElement; const win = doc.defaultView || window; return { x: (win.pageXOffset || docEl.scrollLeft || 0) - (docEl.clientLeft || 0), y: (win.pageYOffset || docEl.scrollTop || 0) - (docEl.clientTop || 0), }; }; export const getKeyCode = (ev: KeyboardEvent) => ev.which || ev.keyCode; export const getKeyChar = (ev: KeyboardEvent) => String.fromCharCode(getKeyCode(ev)); export const getPointerEvent = (ev: any): PointerEvent => (ev.touches && ev.touches[0] ? ev.touches[0] : ev); export const isEscKey = (ev: KeyboardEvent) => getKeyCode(ev) === 27; export const isEnterKey = (ev: KeyboardEvent) => getKeyCode(ev) === 13; export const hasCtrlKey = (ev: WheelEvent) => ev.ctrlKey; export const hasModifierKey = (ev: WheelEvent) => hasCtrlKey(ev) || ev.metaKey; export const on = <E extends Event = Event>( el: EventTarget | EventTarget[], ev: string, fn: (ev: E) => void, opts?: boolean | AddEventListenerOptions ) => { const evs = ev.split(/\s+/); const els = isArray(el) ? el : [el]; evs.forEach(ev => { els.forEach(el => el?.addEventListener(ev, fn as EventListener, opts)); }); }; export const off = <E extends Event = Event>( el: EventTarget | EventTarget[], ev: string, fn: (ev: E) => void, opts?: boolean | AddEventListenerOptions ) => { const evs = ev.split(/\s+/); const els = isArray(el) ? el : [el]; evs.forEach(ev => { els.forEach(el => el?.removeEventListener(ev, fn as EventListener, opts)); }); };