@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
213 lines • 8.68 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRgbString = exports.getRadius = exports.RadiusDefaults = exports.getSpacing = exports.SpacingDefaults = exports.convertHexToRGB = exports.findFocusableSibling = exports.findFocusableChildren = exports.randomId = exports.useOnClickOutside = exports.useDocumentScrollToggle = exports.needleWarningMessage = exports.removeSpaces = exports.removeNewlines = void 0;
exports.isRefObject = isRefObject;
exports.findUntil = findUntil;
/**
*
* 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/>.
*/
const base_1 = require("@neo4j-ndl/base");
const react_1 = require("react");
const removeNewlines = (input) => input.replace(/(\r\n|\n|\r)/gm, '');
exports.removeNewlines = removeNewlines;
/** Remove extra spaces from sting */
const removeSpaces = (input) => input.replace(/\s+/g, ' ').trim();
exports.removeSpaces = removeSpaces;
const needleWarningMessage = (message) => console.warn(`[🪡 Needle]: ${message}`);
exports.needleWarningMessage = needleWarningMessage;
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)
*/
const useDocumentScrollToggle = () => {
const bodyPadding = (0, react_1.useRef)(0);
return (0, react_1.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`;
}
}, []);
};
exports.useDocumentScrollToggle = useDocumentScrollToggle;
/**
* 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
*/
const useOnClickOutside = (ref, handler) => {
(0, react_1.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
};
exports.useOnClickOutside = useOnClickOutside;
/**
* X - char long pseudo-random string
*/
const randomId = (length) => Math.random()
.toString(36)
.slice(2, length + 2);
exports.randomId = randomId;
// Utility / Type-Guard if the object is a ref
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.
*/
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
*/
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;
};
exports.findFocusableChildren = findFocusableChildren;
/**
* 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
*/
const findFocusableSibling = (parentRef, direction) => {
const { current } = parentRef;
const { activeElement } = document;
if (current === null || activeElement === null) {
return undefined;
}
const focusable = (0, exports.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;
};
exports.findFocusableSibling = findFocusableSibling;
/**
* Convert hex color to rgb format
*
* @param hex color in hex code format
* @returns color in rgb format rgb(_, _, _)
*/
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})`;
};
exports.convertHexToRGB = convertHexToRGB;
exports.SpacingDefaults = {
gap: '4',
padding: '4',
paddingBlockEnd: undefined,
paddingBlockStart: undefined,
paddingInline: undefined,
paddingInlineEnd: undefined,
paddingInlineStart: undefined,
};
const getSpacing = (spacing, property) => {
return spacing
? base_1.tokens.space[spacing || exports.SpacingDefaults[property]]
: undefined;
};
exports.getSpacing = getSpacing;
exports.RadiusDefaults = {
borderRadius: undefined,
};
const getRadius = (spacing, property) => {
return spacing
? base_1.tokens.borderRadius[spacing || exports.RadiusDefaults[property]]
: undefined;
};
exports.getRadius = getRadius;
const parseRgbString = (rgbString) => {
const match = rgbString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (!match) {
throw new Error(`Unable to parse RGB color: ${rgbString}`);
}
return [Number(match[1]), Number(match[2]), Number(match[3])];
};
exports.parseRgbString = parseRgbString;
//# sourceMappingURL=utils.js.map