UNPKG

@joint/core

Version:

JavaScript diagramming library

395 lines (357 loc) 11.6 kB
import { camelCase } from '../../util/utilHelpers.mjs'; import $ from './Dom.mjs'; import V from '../../V/index.mjs'; import { dataPriv, dataUser } from './vars.mjs'; // Manipulation function cleanNodesData(nodes) { let i = nodes.length; while (i--) cleanNodeData(nodes[i]); } function cleanNodeData(node) { $.event.remove(node); dataPriv.remove(node); dataUser.remove(node); } function removeNodes(nodes) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (node.parentNode) { node.parentNode.removeChild(node); } } } export function remove() { for (let i = 0; i < this.length; i++) { const node = this[i]; cleanNodeData(node); cleanNodesData(node.getElementsByTagName('*')); } removeNodes(this); return this; } export function detach() { removeNodes(this); return this; } export function empty() { for (let i = 0; i < this.length; i++) { const node = this[i]; if (node.nodeType === 1) { cleanNodesData(node.getElementsByTagName('*')); // Remove any remaining nodes node.textContent = ''; } } return this; } export function clone() { const clones = []; for (let i = 0; i < this.length; i++) { clones.push(this[i].cloneNode(true)); } return this.pushStack(clones); } export function html(html) { const [el] = this; if (!el) return null; if (arguments.length === 0) return el.innerHTML; if (html === undefined) return this; // do nothing cleanNodesData(el.getElementsByTagName('*')); if (typeof html === 'string' || typeof html === 'number') { el.innerHTML = html; } else { el.innerHTML = ''; return this.append(html); } return this; } export function append(...nodes) { const [parent] = this; if (!parent) return this; nodes.forEach((node) => { if (!node) return; if (typeof node === 'string') { parent.append(...$.parseHTML(node)); } else if (node.toString() === '[object Object]') { // $ object this.append(...Array.from(node)); } else if (Array.isArray(node)) { this.append(...node); } else { // DOM node parent.appendChild(node); } }); return this; } export function prepend(...nodes) { const [parent] = this; if (!parent) return this; nodes.forEach((node) => { if (!node) return; if (typeof node === 'string') { parent.prepend(...$.parseHTML(node)); } else if (node.toString() === '[object Object]') { // $ object this.prepend(...Array.from(node)); } else if (Array.isArray(node)) { this.prepend(...node); } else { // DOM node parent.insertBefore(node, parent.firstChild); } }); return this; } export function appendTo(parent) { $(parent).append(this); return this; } export function prependTo(parent) { $(parent).prepend(this); return this; } // Styles and attributes const requireUnits = {}; [ 'width', 'height', 'top', 'bottom', 'left', 'right', 'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight', ].forEach((cssProp) => { requireUnits[cssProp] = true; }); function setCSSProperty(el, name, value) { if (typeof value === 'number' && requireUnits[camelCase(name)]) { value += 'px'; } el.style[name] = value; } export function css(name, value) { let styles; if (typeof name === 'string') { if (value === undefined) { const [el] = this; if (!el) return null; return el.style[name]; } else { styles = { [name]: value }; } } else if (!name) { throw new Error('no styles provided'); } else { styles = name; } for (let style in styles) { if (styles.hasOwnProperty(style)) { for (let i = 0; i < this.length; i++) { setCSSProperty(this[i], style, styles[style]); } } } return this; } export function data(name, value) { if (arguments.length < 2) { const [el] = this; if (!el) return null; if (name === undefined) { return el.dataset; } return el.dataset[name]; } for (let i = 0; i < this.length; i++) { this[i].dataset[name] = value; } return this; } // Classes function setNodesClass(method, nodes, args) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; V.prototype[method].apply({ node }, args); } } export function removeClass() { setNodesClass('removeClass', this, arguments); return this; } export function addClass() { setNodesClass('addClass', this, arguments); return this; } export function toggleClass() { setNodesClass('toggleClass', this, arguments); return this; } export function hasClass() { const [node] = this; if (!node) return false; return V.prototype.hasClass.apply({ node }, arguments); } // Traversing export function children(selector) { const matches = []; for(let i = 0; i < this.length; i++) { const node = this[i]; let children = Array.from(node.children); if (typeof selector === 'string') { children = children.filter(child => child.matches(selector)); } matches.push(...children); } return this.pushStack(matches); } export function closest(selector) { const closest = []; for (let i = 0; i < this.length; i++) { const el = this[i]; if (typeof selector === 'string') { const closestEl = el.closest(selector); if (closestEl) { closest.push(closestEl); } } else { const [ancestorEl] = $(selector); if (ancestorEl && ancestorEl.contains(el)) { closest.push(ancestorEl); } } } return this.pushStack(closest); } // Events export function on(types, selector, data, fn) { $.event.on(this, types, selector, data, fn); return this; } export function one(types, selector, data, fn) { $.event.on(this, types, selector, data, fn, 1); return this; } export function off(types, selector, fn) { if (types && types.preventDefault && types.handleObj) { // ( event ) dispatched $.Event const handleObj = types.handleObj; $(types.delegateTarget).off( handleObj.namespace ? handleObj.origType + '.' + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if (typeof types === 'object') { // ( types-object [, selector] ) for (let type in types) { this.off(type, selector, types[type]); } return this; } if (selector === false || typeof selector === 'function') { // ( types [, fn] ) fn = selector; selector = undefined; } for (let i = 0; i < this.length; i++) { $.event.remove(this[i], types, fn, selector); } return this; } // Measurements export function width() { const [el] = this; if (el === window) return el.document.documentElement.clientWidth; else if (!el) return undefined; const styles = window.getComputedStyle(el); const height = el.offsetWidth; const borderTopWidth = parseFloat(styles.borderTopWidth); const borderBottomWidth = parseFloat(styles.borderBottomWidth); const paddingTop = parseFloat(styles.paddingTop); const paddingBottom = parseFloat(styles.paddingBottom); return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom; } export function height() { const [el] = this; if (el === window) return el.document.documentElement.clientHeight; if (!el) return undefined; const styles = window.getComputedStyle(el); const width = el.offsetHeight; const borderLeftWidth = parseFloat(styles.borderLeftWidth); const borderRightWidth = parseFloat(styles.borderRightWidth); const paddingLeft = parseFloat(styles.paddingLeft); const paddingRight = parseFloat(styles.paddingRight); return width - borderLeftWidth - borderRightWidth - paddingLeft - paddingRight; } export function position() { const [el] = this; if (!el) return; let $el = $(el); let offsetParent; let offset; let doc; let parentOffset = { top: 0, left: 0 }; // position:fixed elements are offset from the viewport, which itself always has zero offset if ($el.css('position') === 'fixed') { // Assume position:fixed implies availability of getBoundingClientRect offset = el.getBoundingClientRect(); } else { offset = $el.offset(); // Account for the *real* offset parent, which can be the document or its root element // when a statically positioned element is identified doc = el.ownerDocument; offsetParent = el.offsetParent || doc.documentElement; const isStaticallyPositioned = (el) => { const { position } = getComputedStyle(el); return position === 'static'; }; while (offsetParent && offsetParent !== doc.documentElement && isStaticallyPositioned(offsetParent)) { offsetParent = offsetParent.offsetParent || doc.documentElement; } if (offsetParent && offsetParent !== el && offsetParent.nodeType === 1 && !isStaticallyPositioned(offsetParent)) { // Incorporate borders into its offset, since they are outside its content origin const offsetParentStyles = window.getComputedStyle(offsetParent); const borderTopWidth = parseFloat(offsetParentStyles.borderTopWidth) || 0; const borderLeftWidth = parseFloat(offsetParentStyles.borderLeftWidth) || 0; parentOffset = $(offsetParent).offset(); parentOffset.top += borderTopWidth; parentOffset.left += borderLeftWidth; } } const marginTop = parseFloat(window.getComputedStyle(el).marginTop) || 0; const marginLeft = parseFloat(window.getComputedStyle(el).marginLeft) || 0; // Subtract parent offsets and element margins return { top: offset.top - parentOffset.top - marginTop, left: offset.left - parentOffset.left - marginLeft }; } export function offset(coordinates) { const [el] = this; // Getter if (coordinates === undefined) { if (!el) return null; if (!el.getClientRects().length) { return { top: 0, left: 0 }; } const rect = el.getBoundingClientRect(); return { top: rect.top + window.scrollY, left: rect.left + window.scrollX }; } // Setter if (!el) return this; const currentStyle = window.getComputedStyle(el); if (currentStyle.position === 'static') { this.css('position', 'relative'); } const currentOffset = this.offset(); const topDifference = coordinates.top - currentOffset.top; const leftDifference = coordinates.left - currentOffset.left; this.css({ top: (parseFloat(currentStyle.top) || 0) + topDifference + 'px', left: (parseFloat(currentStyle.left) || 0) + leftDifference + 'px' }); return this; }