UNPKG

10.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.JumpLinks = void 0;
4const tslib_1 = require("tslib");
5const React = tslib_1.__importStar(require("react"));
6const react_styles_1 = require("@patternfly/react-styles");
7const jump_links_1 = tslib_1.__importDefault(require("@patternfly/react-styles/css/components/JumpLinks/jump-links"));
8const sidebar_1 = tslib_1.__importDefault(require("@patternfly/react-styles/css/components/Sidebar/sidebar"));
9const JumpLinksItem_1 = require("./JumpLinksItem");
10const JumpLinksList_1 = require("./JumpLinksList");
11const util_1 = require("../../helpers/util");
12const Button_1 = require("../Button");
13const angle_right_icon_1 = tslib_1.__importDefault(require('@patternfly/react-icons/dist/js/icons/angle-right-icon'));
14const c_jump_links__toggle_Display_1 = tslib_1.__importDefault(require('@patternfly/react-tokens/dist/js/c_jump_links__toggle_Display'));
15const util_2 = require("../../helpers/util");
16// Recursively find JumpLinkItems and return an array of all their scrollNodes
17const getScrollItems = (children, res) => {
18 React.Children.forEach(children, (child) => {
19 if (util_2.canUseDOM && document.getElementById && document.querySelector && child.type === JumpLinksItem_1.JumpLinksItem) {
20 const scrollNode = child.props.node || child.props.href;
21 if (typeof scrollNode === 'string') {
22 if (scrollNode.startsWith('#')) {
23 // Allow spaces and other special characters as `id`s to be nicer to consumers
24 // https://stackoverflow.com/questions/70579/what-are-valid-values-for-the-id-attribute-in-html
25 res.push(document.getElementById(scrollNode.substr(1)));
26 }
27 else {
28 res.push(document.querySelector(scrollNode));
29 }
30 }
31 else if (scrollNode instanceof HTMLElement) {
32 res.push(scrollNode);
33 }
34 }
35 if ([React.Fragment, JumpLinksList_1.JumpLinksList, JumpLinksItem_1.JumpLinksItem].includes(child.type)) {
36 getScrollItems(child.props.children, res);
37 }
38 });
39 return res;
40};
41function isResponsive(jumpLinks) {
42 // https://github.com/patternfly/patternfly/blob/main/src/patternfly/components/JumpLinks/jump-links.scss#L103
43 return (jumpLinks &&
44 getComputedStyle(jumpLinks)
45 .getPropertyValue(c_jump_links__toggle_Display_1.default.name)
46 .includes('block'));
47}
48const JumpLinks = (_a) => {
49 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"]);
50 const hasScrollSpy = Boolean(scrollableSelector);
51 const [scrollItems, setScrollItems] = React.useState(hasScrollSpy ? getScrollItems(children, []) : []);
52 const [activeIndex, setActiveIndex] = React.useState(activeIndexProp);
53 const [isExpanded, setIsExpanded] = React.useState(isExpandedProp);
54 // Boolean to disable scroll listener from overriding active state of clicked jumplink
55 const isLinkClicked = React.useRef(false);
56 // Allow expanding to be controlled for a niche use case
57 React.useEffect(() => setIsExpanded(isExpandedProp), [isExpandedProp]);
58 const navRef = React.useRef();
59 let scrollableElement;
60 const scrollSpy = React.useCallback(() => {
61 if (!util_2.canUseDOM || !hasScrollSpy || !(scrollableElement instanceof HTMLElement)) {
62 return;
63 }
64 if (isLinkClicked.current) {
65 isLinkClicked.current = false;
66 return;
67 }
68 const scrollPosition = Math.ceil(scrollableElement.scrollTop + offset);
69 window.requestAnimationFrame(() => {
70 let newScrollItems = scrollItems;
71 // Items might have rendered after this component. Do a quick refresh.
72 if (!newScrollItems[0] || newScrollItems.includes(null)) {
73 newScrollItems = getScrollItems(children, []);
74 setScrollItems(newScrollItems);
75 }
76 const scrollElements = newScrollItems
77 .map((e, index) => ({
78 y: e ? e.offsetTop : null,
79 index
80 }))
81 .filter(({ y }) => y !== null)
82 .sort((e1, e2) => e2.y - e1.y);
83 for (const { y, index } of scrollElements) {
84 if (scrollPosition >= y) {
85 return setActiveIndex(index);
86 }
87 }
88 });
89 }, [scrollItems, hasScrollSpy, scrollableElement, offset]);
90 React.useEffect(() => {
91 scrollableElement = document.querySelector(scrollableSelector);
92 if (!(scrollableElement instanceof HTMLElement)) {
93 return;
94 }
95 scrollableElement.addEventListener('scroll', scrollSpy);
96 return () => scrollableElement.removeEventListener('scroll', scrollSpy);
97 }, [scrollableSelector, scrollSpy]);
98 React.useEffect(() => {
99 scrollSpy();
100 }, []);
101 let jumpLinkIndex = 0;
102 const cloneChildren = (children) => !hasScrollSpy
103 ? children
104 : React.Children.map(children, (child) => {
105 if (child.type === JumpLinksItem_1.JumpLinksItem) {
106 const { onClick: onClickProp, isActive: isActiveProp } = child.props;
107 const itemIndex = jumpLinkIndex++;
108 const scrollItem = scrollItems[itemIndex];
109 return React.cloneElement(child, {
110 onClick(ev) {
111 isLinkClicked.current = true;
112 // Items might have rendered after this component. Do a quick refresh.
113 let newScrollItems;
114 if (!scrollItem) {
115 newScrollItems = getScrollItems(children, []);
116 setScrollItems(newScrollItems);
117 }
118 const newScrollItem = scrollItem || newScrollItems[itemIndex];
119 if (newScrollItem) {
120 // we have to support scrolling to an offset due to sticky sidebar
121 const scrollableElement = document.querySelector(scrollableSelector);
122 if (scrollableElement instanceof HTMLElement) {
123 if (isResponsive(navRef.current)) {
124 // Remove class immediately so we can get collapsed height
125 if (navRef.current) {
126 navRef.current.classList.remove(jump_links_1.default.modifiers.expanded);
127 }
128 let stickyParent = navRef.current && navRef.current.parentElement;
129 while (stickyParent && !stickyParent.classList.contains(sidebar_1.default.modifiers.sticky)) {
130 stickyParent = stickyParent.parentElement;
131 }
132 setIsExpanded(false);
133 if (stickyParent) {
134 offset += stickyParent.scrollHeight;
135 }
136 }
137 scrollableElement.scrollTo(0, newScrollItem.offsetTop - offset);
138 }
139 newScrollItem.focus();
140 ev.preventDefault();
141 setActiveIndex(itemIndex);
142 }
143 if (onClickProp) {
144 onClickProp(ev);
145 }
146 },
147 isActive: isActiveProp || activeIndex === itemIndex,
148 children: cloneChildren(child.props.children)
149 });
150 }
151 else if (child.type === React.Fragment) {
152 return cloneChildren(child.props.children);
153 }
154 else if (child.type === JumpLinksList_1.JumpLinksList) {
155 return React.cloneElement(child, { children: cloneChildren(child.props.children) });
156 }
157 return child;
158 });
159 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),
160 React.createElement("div", { className: jump_links_1.default.jumpLinksMain },
161 React.createElement("div", { className: react_styles_1.css('pf-c-jump-links__header') },
162 expandable && (React.createElement("div", { className: jump_links_1.default.jumpLinksToggle },
163 React.createElement(Button_1.Button, { variant: "plain", onClick: () => setIsExpanded(!isExpanded), "aria-label": toggleAriaLabel, "aria-expanded": isExpanded },
164 React.createElement("span", { className: jump_links_1.default.jumpLinksToggleIcon },
165 React.createElement(angle_right_icon_1.default, null)),
166 label && React.createElement("span", { className: react_styles_1.css(jump_links_1.default.jumpLinksToggleText) },
167 " ",
168 label,
169 " ")))),
170 label && alwaysShowLabel && React.createElement("div", { className: react_styles_1.css(jump_links_1.default.jumpLinksLabel) }, label)),
171 React.createElement("ul", { className: jump_links_1.default.jumpLinksList }, cloneChildren(children)))));
172};
173exports.JumpLinks = JumpLinks;
174exports.JumpLinks.displayName = 'JumpLinks';
175//# sourceMappingURL=JumpLinks.js.map
\No newline at end of file