UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

213 lines 8.68 kB
"use strict"; 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