UNPKG

@sap-ux/ui-components

Version:

SAP UI Components Library

157 lines 5.99 kB
// Default properties which applies for dialog scenario with footer buttons const defaultProps = { target: '.ms-Dialog-actions', container: '.ms-Dialog-main', absolute: true, minPagePadding: 10 }; const TRANSFORMATION_OFFSET = 20; /** * This class is responsible for Dialog/Container element transformation to avoid collision/overlap between Callout/Dropdown Menu and target element. * In example when multi select dropdown is used inside dialog with actions, * then class will apply transformation dialog to make sure that dialog actions and dropdown menu is visible. */ export class CalloutCollisionTransform { /** * Initializes class with options. * * @param source Source element. * @param callout Dropdown menu/callout element. * @param props Transformation properties. */ constructor(source, callout, props = defaultProps) { // Original style properties of container this.originalStyle = {}; this.source = source; this.callout = callout; this.props = props; this.applyTransformation = this.applyTransformation.bind(this); this.resetTransformation = this.resetTransformation.bind(this); this.preventDismissOnEvent = this.preventDismissOnEvent.bind(this); } /** * Method creates placeholder element with given size. * * @param size Size of placeholder. * @returns HTML element for placeholder. */ createPlaceholder(size) { const element = document.createElement('div'); element.classList.add('ts-Callout-transformation'); element.style.height = `${size}px`; return element; } /** * Method returns DOM elements and rectangles of associated elements(container, target, callout). * * @returns DOM elements and rectangles of associated elements. */ getElements() { const source = this.source.current; const container = source?.closest(this.props.container); if (container && source) { const target = container?.querySelector(this.props.target); if (target) { const elements = { container: { dom: container, rect: container.getBoundingClientRect() }, target: { dom: target, rect: target.getBoundingClientRect() }, source: { dom: source, rect: source.getBoundingClientRect() } }; const callout = this.callout.current; if (callout) { elements.callout = { dom: callout, rect: callout.getBoundingClientRect() }; } return elements; } } return undefined; } /** * Method calculates callout overlap with target and applies transformation to make target visible. */ applyTransformation() { const elements = this.getElements(); if (!elements) { return; } const { container, target, callout, source } = elements; if (callout) { const maxHeight = window.innerHeight - defaultProps.minPagePadding; const height = callout.rect.height; const top = source.rect.bottom; // Calculate if action is overlaps let diff = top + height - target.rect.top; let bottomPosition = top + callout.rect.height; if (diff <= 0 || bottomPosition >= maxHeight) { return; } // Apply additional offset to make target more visible bottomPosition += target.rect.height; if (bottomPosition <= maxHeight) { diff += TRANSFORMATION_OFFSET; } // Apply absolute position to fix position and avoid recentering if (this.props.absolute) { const containerStyle = container.dom.style; this.originalStyle = { transform: containerStyle.transform, position: containerStyle.position, top: containerStyle.top, left: containerStyle.left }; containerStyle.transform = ''; containerStyle.position = 'absolute'; containerStyle.top = `${container.rect.top}px`; containerStyle.left = `${container.rect.left}px`; } // Apply placeholder element - gap to make target visible this.placeholder = this.createPlaceholder(diff); target.dom.prepend(this.placeholder); } } /** * Method resets current applied transformation. */ resetTransformation() { const elements = this.getElements(); if (!elements) { return; } const { container } = elements; for (const styleName in this.originalStyle) { const styleValue = this.originalStyle[styleName]; if (typeof styleValue === 'string') { container.dom.style[styleName] = styleValue; } } this.originalStyle = {}; // Remove placeholder element if (this.placeholder) { this.placeholder.remove(); this.placeholder = undefined; } } /** * Method prevents callout dismiss/close if focus/click on target elements. * * @param event Triggered event to check. * @returns Returns true if callout should not be closed. */ preventDismissOnEvent(event) { const elements = this.getElements(); if (event.type === 'focus' && elements?.target.dom.contains(event.target)) { return true; } return false; } } //# sourceMappingURL=CalloutCollisionTransform.js.map