@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
164 lines • 6.74 kB
JavaScript
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useCallback, useEffect, useRef, } from 'react';
export const removeNewlines = (input) => input.replace(/(\r\n|\n|\r)/gm, '');
/** Remove extra spaces from sting */
export const removeSpaces = (input) => input.replace(/\s+/g, ' ').trim();
export const needleWarningMessage = (message) => console.warn(`[🪡 Needle]: ${message}`);
const getScrollbarSize = (doc) => {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
const documentWidth = doc.documentElement.clientWidth;
const scrollBarSize = Math.abs(window.innerWidth - documentWidth);
// Firefox with 110% and 120% zoom level return 1px even if there is no scrollbar
return scrollBarSize > 1 ? scrollBarSize : 0;
};
const getPaddingRight = (element) => parseInt(window.getComputedStyle(element).paddingRight, 10) || 0;
/**
* Toggles scroll on the provided document.
* Useful for disabling scroll when a popup is open (ie ContextMenu/Modal)
*/
export const useDocumentScrollToggle = () => {
const bodyPadding = useRef(0);
return useCallback((disable, doc = document) => {
if (disable) {
const existingPaddingRight = getPaddingRight(doc.body);
bodyPadding.current = existingPaddingRight;
const newPaddingRight = existingPaddingRight + getScrollbarSize(doc);
doc.body.style.overflow = 'hidden';
doc.body.style.paddingRight = `${newPaddingRight}px`;
}
else {
doc.body.style.overflow = '';
doc.body.style.paddingRight = `${bodyPadding.current}px`;
}
}, []);
};
/**
* Detect if there is a click event outside
* of the provided element
* Source:
* https://hashnode.com/post/useonclickoutside-custom-hook-to-detect-the-mouse-click-on-outside-typescript-ckrejmy3h0k5r91s18iu42t28
*/
export const useOnClickOutside = (ref, handler) => {
useEffect(() => {
const listener = (event) => {
const el = ref === null || ref === void 0 ? void 0 : ref.current;
if (!el || el.contains((event === null || event === void 0 ? void 0 : event.target) || null)) {
return;
}
handler(event); // Call the handler only if the click is outside of the element passed.
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.addEventListener('mousedown', listener);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.addEventListener('touchstart', listener);
return () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.removeEventListener('mousedown', listener);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]); // Reload only if ref or handler changes
};
/**
* X - char long pseudo-random string
*/
export const randomId = (length) => Math.random()
.toString(36)
.slice(2, length + 2);
// Utility / Type-Guard if the object is a ref
export function isRefObject(obj) {
return obj && typeof obj === 'object' && 'current' in obj;
}
/**
* Equivalent to prevUntil/nextUntil in jQuery
* https://api.jquery.com/prevUntil/
* https://api.jquery.com/nextUntil/
*
* Additional functionality is added to circle back
* to the beginning of a list of siblings if it reaches the end
* or vice versa.
*/
export function findUntil(direction, el, matchSelector) {
const element = el;
if (!element.parentElement)
return null;
const allSiblings = [...element.parentElement.children].filter((sibling) => sibling.matches(matchSelector));
const index = allSiblings.findIndex((sibling) => sibling.isEqualNode(element));
if (index === -1)
return null;
const newIndex = direction === 'next'
? (index + 1) % allSiblings.length
: (index - 1 + allSiblings.length) % allSiblings.length;
return allSiblings[newIndex];
}
/**
* Find all html elements that are focusable given a parent element
* @param parentElement the parent element
* @returns an array of HTML elements
*/
export const findFocusableChildren = (parentElement) => {
const focusableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), input[type=checkbox]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
const focusable = Array.from(parentElement.querySelectorAll(focusableElements)).filter((element) => {
if (element instanceof HTMLElement) {
return true;
}
return false;
});
return focusable;
};
/**
* Using the element that currently has focus, finds the previous sibling of the currently focused element that is focusable
* @param parentRef the parent element
* @returns the previous focusable element
*/
export const findFocusableSibling = (parentRef, direction) => {
const { current } = parentRef;
const { activeElement } = document;
if (current === null || activeElement === null) {
return undefined;
}
const focusable = findFocusableChildren(current);
const index = focusable.indexOf(activeElement);
if (index > -1) {
const siblingElement = focusable[index + (direction === 'next' ? 1 : -1)];
if (siblingElement === undefined ||
!(siblingElement instanceof HTMLElement)) {
return undefined;
}
return siblingElement;
}
return undefined;
};
/**
* Convert hex color to rgb format
*
* @param hex color in hex code format
* @returns color in rgb format rgb(_, _, _)
*/
export const convertHexToRGB = (hex) => {
hex = hex.replace(/^#/, '');
const red = parseInt(hex.substring(0, 2), 16);
const green = parseInt(hex.substring(2, 4), 16);
const blue = parseInt(hex.substring(4, 6), 16);
return `rgb(${red}, ${green}, ${blue})`;
};
//# sourceMappingURL=utils.js.map