1 | import { __rest } from "tslib";
|
2 | import * as React from 'react';
|
3 | import { css } from '@patternfly/react-styles';
|
4 | import styles from '@patternfly/react-styles/css/components/JumpLinks/jump-links';
|
5 | import sidebarStyles from '@patternfly/react-styles/css/components/Sidebar/sidebar';
|
6 | import { JumpLinksItem } from './JumpLinksItem';
|
7 | import { JumpLinksList } from './JumpLinksList';
|
8 | import { formatBreakpointMods } from '../../helpers/util';
|
9 | import { Button } from '../Button';
|
10 | import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
|
11 | import cssToggleDisplayVar from '@patternfly/react-tokens/dist/esm/c_jump_links__toggle_Display';
|
12 | import { canUseDOM } from '../../helpers/util';
|
13 |
|
14 | const getScrollItems = (children, res) => {
|
15 | React.Children.forEach(children, (child) => {
|
16 | if (canUseDOM && document.getElementById && document.querySelector && child.type === JumpLinksItem) {
|
17 | const scrollNode = child.props.node || child.props.href;
|
18 | if (typeof scrollNode === 'string') {
|
19 | if (scrollNode.startsWith('#')) {
|
20 |
|
21 |
|
22 | res.push(document.getElementById(scrollNode.substr(1)));
|
23 | }
|
24 | else {
|
25 | res.push(document.querySelector(scrollNode));
|
26 | }
|
27 | }
|
28 | else if (scrollNode instanceof HTMLElement) {
|
29 | res.push(scrollNode);
|
30 | }
|
31 | }
|
32 | if ([React.Fragment, JumpLinksList, JumpLinksItem].includes(child.type)) {
|
33 | getScrollItems(child.props.children, res);
|
34 | }
|
35 | });
|
36 | return res;
|
37 | };
|
38 | function isResponsive(jumpLinks) {
|
39 |
|
40 | return (jumpLinks &&
|
41 | getComputedStyle(jumpLinks)
|
42 | .getPropertyValue(cssToggleDisplayVar.name)
|
43 | .includes('block'));
|
44 | }
|
45 | export const JumpLinks = (_a) => {
|
46 | 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 = __rest(_a, ["isCentered", "isVertical", "children", "label", 'aria-label', "scrollableSelector", "activeIndex", "offset", "expandable", "isExpanded", "alwaysShowLabel", "toggleAriaLabel", "className"]);
|
47 | const hasScrollSpy = Boolean(scrollableSelector);
|
48 | const [scrollItems, setScrollItems] = React.useState(hasScrollSpy ? getScrollItems(children, []) : []);
|
49 | const [activeIndex, setActiveIndex] = React.useState(activeIndexProp);
|
50 | const [isExpanded, setIsExpanded] = React.useState(isExpandedProp);
|
51 |
|
52 | const isLinkClicked = React.useRef(false);
|
53 |
|
54 | React.useEffect(() => setIsExpanded(isExpandedProp), [isExpandedProp]);
|
55 | const navRef = React.useRef();
|
56 | let scrollableElement;
|
57 | const scrollSpy = React.useCallback(() => {
|
58 | if (!canUseDOM || !hasScrollSpy || !(scrollableElement instanceof HTMLElement)) {
|
59 | return;
|
60 | }
|
61 | if (isLinkClicked.current) {
|
62 | isLinkClicked.current = false;
|
63 | return;
|
64 | }
|
65 | const scrollPosition = Math.ceil(scrollableElement.scrollTop + offset);
|
66 | window.requestAnimationFrame(() => {
|
67 | let newScrollItems = scrollItems;
|
68 |
|
69 | if (!newScrollItems[0] || newScrollItems.includes(null)) {
|
70 | newScrollItems = getScrollItems(children, []);
|
71 | setScrollItems(newScrollItems);
|
72 | }
|
73 | const scrollElements = newScrollItems
|
74 | .map((e, index) => ({
|
75 | y: e ? e.offsetTop : null,
|
76 | index
|
77 | }))
|
78 | .filter(({ y }) => y !== null)
|
79 | .sort((e1, e2) => e2.y - e1.y);
|
80 | for (const { y, index } of scrollElements) {
|
81 | if (scrollPosition >= y) {
|
82 | return setActiveIndex(index);
|
83 | }
|
84 | }
|
85 | });
|
86 | }, [scrollItems, hasScrollSpy, scrollableElement, offset]);
|
87 | React.useEffect(() => {
|
88 | scrollableElement = document.querySelector(scrollableSelector);
|
89 | if (!(scrollableElement instanceof HTMLElement)) {
|
90 | return;
|
91 | }
|
92 | scrollableElement.addEventListener('scroll', scrollSpy);
|
93 | return () => scrollableElement.removeEventListener('scroll', scrollSpy);
|
94 | }, [scrollableSelector, scrollSpy]);
|
95 | React.useEffect(() => {
|
96 | scrollSpy();
|
97 | }, []);
|
98 | let jumpLinkIndex = 0;
|
99 | const cloneChildren = (children) => !hasScrollSpy
|
100 | ? children
|
101 | : React.Children.map(children, (child) => {
|
102 | if (child.type === JumpLinksItem) {
|
103 | const { onClick: onClickProp, isActive: isActiveProp } = child.props;
|
104 | const itemIndex = jumpLinkIndex++;
|
105 | const scrollItem = scrollItems[itemIndex];
|
106 | return React.cloneElement(child, {
|
107 | onClick(ev) {
|
108 | isLinkClicked.current = true;
|
109 |
|
110 | let newScrollItems;
|
111 | if (!scrollItem) {
|
112 | newScrollItems = getScrollItems(children, []);
|
113 | setScrollItems(newScrollItems);
|
114 | }
|
115 | const newScrollItem = scrollItem || newScrollItems[itemIndex];
|
116 | if (newScrollItem) {
|
117 |
|
118 | const scrollableElement = document.querySelector(scrollableSelector);
|
119 | if (scrollableElement instanceof HTMLElement) {
|
120 | if (isResponsive(navRef.current)) {
|
121 |
|
122 | if (navRef.current) {
|
123 | navRef.current.classList.remove(styles.modifiers.expanded);
|
124 | }
|
125 | let stickyParent = navRef.current && navRef.current.parentElement;
|
126 | while (stickyParent && !stickyParent.classList.contains(sidebarStyles.modifiers.sticky)) {
|
127 | stickyParent = stickyParent.parentElement;
|
128 | }
|
129 | setIsExpanded(false);
|
130 | if (stickyParent) {
|
131 | offset += stickyParent.scrollHeight;
|
132 | }
|
133 | }
|
134 | scrollableElement.scrollTo(0, newScrollItem.offsetTop - offset);
|
135 | }
|
136 | newScrollItem.focus();
|
137 | ev.preventDefault();
|
138 | setActiveIndex(itemIndex);
|
139 | }
|
140 | if (onClickProp) {
|
141 | onClickProp(ev);
|
142 | }
|
143 | },
|
144 | isActive: isActiveProp || activeIndex === itemIndex,
|
145 | children: cloneChildren(child.props.children)
|
146 | });
|
147 | }
|
148 | else if (child.type === React.Fragment) {
|
149 | return cloneChildren(child.props.children);
|
150 | }
|
151 | else if (child.type === JumpLinksList) {
|
152 | return React.cloneElement(child, { children: cloneChildren(child.props.children) });
|
153 | }
|
154 | return child;
|
155 | });
|
156 | return (React.createElement("nav", Object.assign({ className: css(styles.jumpLinks, isCentered && styles.modifiers.center, isVertical && styles.modifiers.vertical, formatBreakpointMods(expandable, styles), isExpanded && styles.modifiers.expanded, className), "aria-label": ariaLabel, ref: navRef }, props),
|
157 | React.createElement("div", { className: styles.jumpLinksMain },
|
158 | React.createElement("div", { className: css('pf-c-jump-links__header') },
|
159 | expandable && (React.createElement("div", { className: styles.jumpLinksToggle },
|
160 | React.createElement(Button, { variant: "plain", onClick: () => setIsExpanded(!isExpanded), "aria-label": toggleAriaLabel, "aria-expanded": isExpanded },
|
161 | React.createElement("span", { className: styles.jumpLinksToggleIcon },
|
162 | React.createElement(AngleRightIcon, null)),
|
163 | label && React.createElement("span", { className: css(styles.jumpLinksToggleText) },
|
164 | " ",
|
165 | label,
|
166 | " ")))),
|
167 | label && alwaysShowLabel && React.createElement("div", { className: css(styles.jumpLinksLabel) }, label)),
|
168 | React.createElement("ul", { className: styles.jumpLinksList }, cloneChildren(children)))));
|
169 | };
|
170 | JumpLinks.displayName = 'JumpLinks';
|
171 |
|
\ | No newline at end of file |