@ng-doc/ui-kit
Version:
<!-- PROJECT LOGO --> <br /> <div align="center"> <a href="https://github.com/ng-doc/ng-doc"> <img src="https://ng-doc.com/assets/images/ng-doc.svg?raw=true" alt="Logo" height="150px"> </a>
283 lines (277 loc) • 10.9 kB
JavaScript
import { asArray } from '@ng-doc/core/helpers/as-array';
class NgDocFocusUtils {
static isNativeKeyboardFocusable(element) {
if (element.hasAttribute('disabled') || element.getAttribute('tabIndex') === '-1') {
return false;
}
if ((element instanceof HTMLElement && element.isContentEditable) ||
element.getAttribute('tabIndex') === '0') {
return true;
}
switch (element.tagName) {
case 'BUTTON':
case 'SELECT':
case 'TEXTAREA':
return true;
case 'VIDEO':
case 'AUDIO':
return element.hasAttribute('controls');
case 'INPUT':
return element.getAttribute('type') !== 'hidden';
case 'A':
case 'LINK':
return element.hasAttribute('href');
default:
return false;
}
}
static getClosestKeyboardFocusable(initial, root, forward = true) {
if (!root.ownerDocument) {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const svgNodeFilter = ((node) => 'ownerSVGElement' in node ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT);
const treeWalker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, svgNodeFilter);
treeWalker.currentNode = initial;
while (forward ? treeWalker.nextNode() : treeWalker.previousNode()) {
if (treeWalker.currentNode instanceof HTMLElement) {
initial = treeWalker.currentNode;
}
if (NgDocFocusUtils.isNativeKeyboardFocusable(initial)) {
return initial;
}
}
return null;
}
static focusClosestElement(initial, root, forward = true) {
const focusable = NgDocFocusUtils.getClosestKeyboardFocusable(initial, root, forward);
if (focusable) {
focusable.focus();
}
}
}
const NG_DOC_ARROW_MARGIN = 32;
const POSITION_DESCRIPTION = {
'top-left': {
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom',
},
'top-center': {
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom',
},
'top-right': {
originX: 'end',
originY: 'top',
overlayX: 'end',
overlayY: 'bottom',
},
'bottom-left': {
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top',
},
'bottom-center': {
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top',
},
'bottom-right': {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top',
},
'left-top': {
originX: 'start',
originY: 'top',
overlayX: 'end',
overlayY: 'top',
},
'left-center': {
originX: 'start',
originY: 'center',
overlayX: 'end',
overlayY: 'center',
},
'left-bottom': {
originX: 'start',
originY: 'bottom',
overlayX: 'end',
overlayY: 'bottom',
},
'right-top': {
originX: 'end',
originY: 'top',
overlayX: 'start',
overlayY: 'top',
},
'right-center': {
originX: 'end',
originY: 'center',
overlayX: 'start',
overlayY: 'center',
},
'right-bottom': {
originX: 'end',
originY: 'bottom',
overlayX: 'start',
overlayY: 'bottom',
},
};
class NgDocOverlayUtils {
static getConnectedPosition(dropdownPositions, origin, offset = 0, withPointer = false) {
return asArray(dropdownPositions).map((position) => {
const connectedPosition = NgDocOverlayUtils.toConnectedPosition(position);
const marginMultiplier = NgDocOverlayUtils.getMarginMultiplier(connectedPosition);
const marginX = !NgDocOverlayUtils.isVerticalPosition(connectedPosition)
? offset * marginMultiplier
: 0;
const marginY = NgDocOverlayUtils.isVerticalPosition(connectedPosition)
? offset * marginMultiplier
: 0;
connectedPosition.offsetX = connectedPosition.offsetX || 0;
connectedPosition.offsetY = connectedPosition.offsetY || 0;
connectedPosition.offsetX +=
(withPointer ? NgDocOverlayUtils.getOffsetX(origin, connectedPosition) : 0) + marginX;
connectedPosition.offsetY +=
(withPointer ? NgDocOverlayUtils.getOffsetY(origin, connectedPosition) : 0) + marginY;
return connectedPosition;
});
}
static toConnectedPosition(position) {
return typeof position === 'string' ? { ...POSITION_DESCRIPTION[position] } : { ...position };
}
static toConnectedPositions(positions) {
return positions.map(NgDocOverlayUtils.toConnectedPosition);
}
static getOffsetX(origin, position) {
const isVertical = NgDocOverlayUtils.isVerticalPosition(position);
const offsetMultiplier = NgDocOverlayUtils.getOffsetMultiplier(position);
const isCenter = NgDocOverlayUtils.isCenterPosition(position);
const width = (position.originX === 'center' && position.overlayX !== 'center') ||
NgDocOverlayUtils.overlayIsOutByX(position)
? NG_DOC_ARROW_MARGIN - 24
: origin.offsetWidth;
return ((isVertical && !isCenter ? Math.max(NG_DOC_ARROW_MARGIN - width, 0) : 0) * offsetMultiplier);
}
static getOffsetY(origin, position) {
const isVertical = NgDocOverlayUtils.isVerticalPosition(position);
const offsetMultiplier = NgDocOverlayUtils.getOffsetMultiplier(position);
const isCenter = NgDocOverlayUtils.isCenterPosition(position);
const height = (position.originY === 'center' && position.overlayY !== 'center') ||
NgDocOverlayUtils.overlayIsOutByY(position)
? NG_DOC_ARROW_MARGIN - 24
: origin.offsetHeight;
return ((!isVertical && !isCenter ? Math.max(NG_DOC_ARROW_MARGIN - height, 0) : 0) * offsetMultiplier);
}
static overlayIsOutByX(position) {
return ((position.originX === 'start' && position.overlayX === 'end') ||
(position.originX === 'end' && position.overlayX === 'start'));
}
static overlayIsOutByY(position) {
return ((position.originY === 'top' && position.overlayY === 'bottom') ||
(position.originY === 'bottom' && position.overlayY === 'top'));
}
static getOffsetMultiplier(position) {
return (NgDocOverlayUtils.isVerticalPosition(position) && position.overlayX === 'end') ||
(!NgDocOverlayUtils.isVerticalPosition(position) && position.overlayY === 'bottom')
? 1
: -1;
}
static getMarginMultiplier(position) {
return ['right', 'bottom'].includes(NgDocOverlayUtils.getRelativePosition(position) || '')
? 1
: -1;
}
static isVerticalPosition(position) {
return ['bottom', 'top'].includes(NgDocOverlayUtils.getRelativePosition(position) || '');
}
static isCenterPosition(position) {
return position.overlayX === 'center' || position.overlayY === 'center';
}
static getPositionAlign(position) {
if (NgDocOverlayUtils.isVerticalPosition(position)) {
return position.overlayX === 'start' ? 'left' : position.overlayX === 'end' ? 'right' : null;
}
else {
return position.originY === 'top' ? 'top' : position.originY === 'bottom' ? 'bottom' : null;
}
}
static getRelativePosition(pos) {
const position = NgDocOverlayUtils.toConnectedPosition(pos);
if (position.originY === 'bottom' && position.overlayY === 'top') {
return 'bottom';
}
if (position.originY === 'top' && position.overlayY === 'bottom') {
return 'top';
}
if (position.originX === 'start' && position.overlayX === 'end') {
return 'left';
}
if (position.originX === 'end' && position.overlayX === 'start') {
return 'right';
}
return null;
}
static getOverlayPosition(positionPair) {
const existsPosition = Object.keys(POSITION_DESCRIPTION).find((key) => {
const positionDescription = POSITION_DESCRIPTION[key];
return (positionPair.originX === positionDescription.originX &&
positionPair.originY === positionDescription.originY &&
positionPair.overlayX === positionDescription.overlayX &&
positionPair.overlayY === positionDescription.overlayY);
});
return existsPosition ? existsPosition : positionPair;
}
}
class NgDocPositionUtils {
/**
* Getting the position of the element relative to the viewPort, this function is faster than BoundingClientRect,
* it also takes into account the change in the position of the element through transform
* @param element
*/
static getElementPosition(element) {
let xPos = 0;
let yPos = 0;
while (element) {
if (element === document.body) {
const documentElement = document.documentElement;
xPos +=
documentElement.offsetLeft - documentElement.scrollLeft + documentElement.clientLeft;
yPos += documentElement.offsetTop - documentElement.scrollTop + documentElement.clientTop;
element = null;
}
else {
const elementMatrix = new DOMMatrix(element.style.transform);
xPos += element.offsetLeft - element.scrollLeft + element.clientLeft + elementMatrix.m41;
yPos += element.offsetTop - element.scrollTop + element.clientTop + elementMatrix.m42;
element = NgDocPositionUtils.getOffsetParent(element);
}
}
return { x: xPos, y: yPos };
}
/**
* An implementation of the element.offsetParent function, this implementation closes a bug in Firefox when it
* returns an offsetParent for elements with position: fixed
* @param element
*/
static getOffsetParent(element) {
const computerStyles = getComputedStyle(element);
if (computerStyles.position === 'fixed' || computerStyles.display === 'none') {
return null;
}
return element.offsetParent;
}
}
/**
* Generated bundle index. Do not edit.
*/
export { NG_DOC_ARROW_MARGIN, NgDocFocusUtils, NgDocOverlayUtils, NgDocPositionUtils };
//# sourceMappingURL=ng-doc-ui-kit-utils.mjs.map