@patternfly/react-core
Version:
This library provides a set of common React components for use with the PatternFly reference implementation.
175 lines • 10.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.JumpLinks = void 0;
const tslib_1 = require("tslib");
const React = tslib_1.__importStar(require("react"));
const react_styles_1 = require("@patternfly/react-styles");
const jump_links_1 = tslib_1.__importDefault(require("@patternfly/react-styles/css/components/JumpLinks/jump-links"));
const sidebar_1 = tslib_1.__importDefault(require("@patternfly/react-styles/css/components/Sidebar/sidebar"));
const JumpLinksItem_1 = require("./JumpLinksItem");
const JumpLinksList_1 = require("./JumpLinksList");
const util_1 = require("../../helpers/util");
const Button_1 = require("../Button");
const angle_right_icon_1 = tslib_1.__importDefault(require('@patternfly/react-icons/dist/js/icons/angle-right-icon'));
const c_jump_links__toggle_Display_1 = tslib_1.__importDefault(require('@patternfly/react-tokens/dist/js/c_jump_links__toggle_Display'));
const util_2 = require("../../helpers/util");
// Recursively find JumpLinkItems and return an array of all their scrollNodes
const getScrollItems = (children, res) => {
React.Children.forEach(children, (child) => {
if (util_2.canUseDOM && document.getElementById && document.querySelector && child.type === JumpLinksItem_1.JumpLinksItem) {
const scrollNode = child.props.node || child.props.href;
if (typeof scrollNode === 'string') {
if (scrollNode.startsWith('#')) {
// Allow spaces and other special characters as `id`s to be nicer to consumers
// https://stackoverflow.com/questions/70579/what-are-valid-values-for-the-id-attribute-in-html
res.push(document.getElementById(scrollNode.substr(1)));
}
else {
res.push(document.querySelector(scrollNode));
}
}
else if (scrollNode instanceof HTMLElement) {
res.push(scrollNode);
}
}
if ([React.Fragment, JumpLinksList_1.JumpLinksList, JumpLinksItem_1.JumpLinksItem].includes(child.type)) {
getScrollItems(child.props.children, res);
}
});
return res;
};
function isResponsive(jumpLinks) {
// https://github.com/patternfly/patternfly/blob/main/src/patternfly/components/JumpLinks/jump-links.scss#L103
return (jumpLinks &&
getComputedStyle(jumpLinks)
.getPropertyValue(c_jump_links__toggle_Display_1.default.name)
.includes('block'));
}
const JumpLinks = (_a) => {
var { isCentered, isVertical, children, label, 'aria-label': ariaLabel = typeof label === 'string' ? label : null, scrollableSelector, activeIndex: activeIndexProp = 0, offset = 0, expandable, isExpanded: isExpandedProp = false, alwaysShowLabel = true, toggleAriaLabel = 'Toggle jump links', className } = _a, props = tslib_1.__rest(_a, ["isCentered", "isVertical", "children", "label", 'aria-label', "scrollableSelector", "activeIndex", "offset", "expandable", "isExpanded", "alwaysShowLabel", "toggleAriaLabel", "className"]);
const hasScrollSpy = Boolean(scrollableSelector);
const [scrollItems, setScrollItems] = React.useState(hasScrollSpy ? getScrollItems(children, []) : []);
const [activeIndex, setActiveIndex] = React.useState(activeIndexProp);
const [isExpanded, setIsExpanded] = React.useState(isExpandedProp);
// Boolean to disable scroll listener from overriding active state of clicked jumplink
const isLinkClicked = React.useRef(false);
// Allow expanding to be controlled for a niche use case
React.useEffect(() => setIsExpanded(isExpandedProp), [isExpandedProp]);
const navRef = React.useRef();
let scrollableElement;
const scrollSpy = React.useCallback(() => {
if (!util_2.canUseDOM || !hasScrollSpy || !(scrollableElement instanceof HTMLElement)) {
return;
}
if (isLinkClicked.current) {
isLinkClicked.current = false;
return;
}
const scrollPosition = Math.ceil(scrollableElement.scrollTop + offset);
window.requestAnimationFrame(() => {
let newScrollItems = scrollItems;
// Items might have rendered after this component. Do a quick refresh.
if (!newScrollItems[0] || newScrollItems.includes(null)) {
newScrollItems = getScrollItems(children, []);
setScrollItems(newScrollItems);
}
const scrollElements = newScrollItems
.map((e, index) => ({
y: e ? e.offsetTop : null,
index
}))
.filter(({ y }) => y !== null)
.sort((e1, e2) => e2.y - e1.y);
for (const { y, index } of scrollElements) {
if (scrollPosition >= y) {
return setActiveIndex(index);
}
}
});
}, [scrollItems, hasScrollSpy, scrollableElement, offset]);
React.useEffect(() => {
scrollableElement = document.querySelector(scrollableSelector);
if (!(scrollableElement instanceof HTMLElement)) {
return;
}
scrollableElement.addEventListener('scroll', scrollSpy);
return () => scrollableElement.removeEventListener('scroll', scrollSpy);
}, [scrollableSelector, scrollSpy]);
React.useEffect(() => {
scrollSpy();
}, []);
let jumpLinkIndex = 0;
const cloneChildren = (children) => !hasScrollSpy
? children
: React.Children.map(children, (child) => {
if (child.type === JumpLinksItem_1.JumpLinksItem) {
const { onClick: onClickProp, isActive: isActiveProp } = child.props;
const itemIndex = jumpLinkIndex++;
const scrollItem = scrollItems[itemIndex];
return React.cloneElement(child, {
onClick(ev) {
isLinkClicked.current = true;
// Items might have rendered after this component. Do a quick refresh.
let newScrollItems;
if (!scrollItem) {
newScrollItems = getScrollItems(children, []);
setScrollItems(newScrollItems);
}
const newScrollItem = scrollItem || newScrollItems[itemIndex];
if (newScrollItem) {
// we have to support scrolling to an offset due to sticky sidebar
const scrollableElement = document.querySelector(scrollableSelector);
if (scrollableElement instanceof HTMLElement) {
if (isResponsive(navRef.current)) {
// Remove class immediately so we can get collapsed height
if (navRef.current) {
navRef.current.classList.remove(jump_links_1.default.modifiers.expanded);
}
let stickyParent = navRef.current && navRef.current.parentElement;
while (stickyParent && !stickyParent.classList.contains(sidebar_1.default.modifiers.sticky)) {
stickyParent = stickyParent.parentElement;
}
setIsExpanded(false);
if (stickyParent) {
offset += stickyParent.scrollHeight;
}
}
scrollableElement.scrollTo(0, newScrollItem.offsetTop - offset);
}
newScrollItem.focus();
ev.preventDefault();
setActiveIndex(itemIndex);
}
if (onClickProp) {
onClickProp(ev);
}
},
isActive: isActiveProp || activeIndex === itemIndex,
children: cloneChildren(child.props.children)
});
}
else if (child.type === React.Fragment) {
return cloneChildren(child.props.children);
}
else if (child.type === JumpLinksList_1.JumpLinksList) {
return React.cloneElement(child, { children: cloneChildren(child.props.children) });
}
return child;
});
return (React.createElement("nav", Object.assign({ className: react_styles_1.css(jump_links_1.default.jumpLinks, isCentered && jump_links_1.default.modifiers.center, isVertical && jump_links_1.default.modifiers.vertical, util_1.formatBreakpointMods(expandable, jump_links_1.default), isExpanded && jump_links_1.default.modifiers.expanded, className), "aria-label": ariaLabel, ref: navRef }, props),
React.createElement("div", { className: jump_links_1.default.jumpLinksMain },
React.createElement("div", { className: react_styles_1.css('pf-c-jump-links__header') },
expandable && (React.createElement("div", { className: jump_links_1.default.jumpLinksToggle },
React.createElement(Button_1.Button, { variant: "plain", onClick: () => setIsExpanded(!isExpanded), "aria-label": toggleAriaLabel, "aria-expanded": isExpanded },
React.createElement("span", { className: jump_links_1.default.jumpLinksToggleIcon },
React.createElement(angle_right_icon_1.default, null)),
label && React.createElement("span", { className: react_styles_1.css(jump_links_1.default.jumpLinksToggleText) },
" ",
label,
" ")))),
label && alwaysShowLabel && React.createElement("div", { className: react_styles_1.css(jump_links_1.default.jumpLinksLabel) }, label)),
React.createElement("ul", { className: jump_links_1.default.jumpLinksList }, cloneChildren(children)))));
};
exports.JumpLinks = JumpLinks;
exports.JumpLinks.displayName = 'JumpLinks';
//# sourceMappingURL=JumpLinks.js.map
;