Anchor.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. "use strict";
  2. "use client";
  3. var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
  4. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
  5. Object.defineProperty(exports, "__esModule", {
  6. value: true
  7. });
  8. exports.default = void 0;
  9. var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
  10. var React = _interopRequireWildcard(require("react"));
  11. var _classnames = _interopRequireDefault(require("classnames"));
  12. var _useEvent = _interopRequireDefault(require("rc-util/lib/hooks/useEvent"));
  13. var _scrollIntoViewIfNeeded = _interopRequireDefault(require("scroll-into-view-if-needed"));
  14. var _getScroll = _interopRequireDefault(require("../_util/getScroll"));
  15. var _scrollTo = _interopRequireDefault(require("../_util/scrollTo"));
  16. var _warning = require("../_util/warning");
  17. var _affix = _interopRequireDefault(require("../affix"));
  18. var _context = require("../config-provider/context");
  19. var _useCSSVarCls = _interopRequireDefault(require("../config-provider/hooks/useCSSVarCls"));
  20. var _AnchorLink = _interopRequireDefault(require("./AnchorLink"));
  21. var _context2 = _interopRequireDefault(require("./context"));
  22. var _style = _interopRequireDefault(require("./style"));
  23. function getDefaultContainer() {
  24. return window;
  25. }
  26. function getOffsetTop(element, container) {
  27. if (!element.getClientRects().length) {
  28. return 0;
  29. }
  30. const rect = element.getBoundingClientRect();
  31. if (rect.width || rect.height) {
  32. if (container === window) {
  33. return rect.top - element.ownerDocument.documentElement.clientTop;
  34. }
  35. return rect.top - container.getBoundingClientRect().top;
  36. }
  37. return rect.top;
  38. }
  39. const sharpMatcherRegex = /#([\S ]+)$/;
  40. const Anchor = props => {
  41. var _a;
  42. const {
  43. rootClassName,
  44. prefixCls: customPrefixCls,
  45. className,
  46. style,
  47. offsetTop,
  48. affix = true,
  49. showInkInFixed = false,
  50. children,
  51. items,
  52. direction: anchorDirection = 'vertical',
  53. bounds,
  54. targetOffset,
  55. onClick,
  56. onChange,
  57. getContainer,
  58. getCurrentAnchor,
  59. replace
  60. } = props;
  61. // =================== Warning =====================
  62. if (process.env.NODE_ENV !== 'production') {
  63. const warning = (0, _warning.devUseWarning)('Anchor');
  64. warning.deprecated(!children, 'Anchor children', 'items');
  65. process.env.NODE_ENV !== "production" ? warning(!(anchorDirection === 'horizontal' && (items === null || items === void 0 ? void 0 : items.some(n => 'children' in n))), 'usage', '`Anchor items#children` is not supported when `Anchor` direction is horizontal.') : void 0;
  66. }
  67. const [links, setLinks] = React.useState([]);
  68. const [activeLink, setActiveLink] = React.useState(null);
  69. const activeLinkRef = React.useRef(activeLink);
  70. const wrapperRef = React.useRef(null);
  71. const spanLinkNode = React.useRef(null);
  72. const animating = React.useRef(false);
  73. const {
  74. direction,
  75. getPrefixCls,
  76. className: anchorClassName,
  77. style: anchorStyle
  78. } = (0, _context.useComponentConfig)('anchor');
  79. const {
  80. getTargetContainer
  81. } = React.useContext(_context.ConfigContext);
  82. const prefixCls = getPrefixCls('anchor', customPrefixCls);
  83. const rootCls = (0, _useCSSVarCls.default)(prefixCls);
  84. const [wrapCSSVar, hashId, cssVarCls] = (0, _style.default)(prefixCls, rootCls);
  85. const getCurrentContainer = (_a = getContainer !== null && getContainer !== void 0 ? getContainer : getTargetContainer) !== null && _a !== void 0 ? _a : getDefaultContainer;
  86. const dependencyListItem = JSON.stringify(links);
  87. const registerLink = (0, _useEvent.default)(link => {
  88. if (!links.includes(link)) {
  89. setLinks(prev => [].concat((0, _toConsumableArray2.default)(prev), [link]));
  90. }
  91. });
  92. const unregisterLink = (0, _useEvent.default)(link => {
  93. if (links.includes(link)) {
  94. setLinks(prev => prev.filter(i => i !== link));
  95. }
  96. });
  97. const updateInk = () => {
  98. var _a;
  99. const linkNode = (_a = wrapperRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${prefixCls}-link-title-active`);
  100. if (linkNode && spanLinkNode.current) {
  101. const {
  102. style: inkStyle
  103. } = spanLinkNode.current;
  104. const horizontalAnchor = anchorDirection === 'horizontal';
  105. inkStyle.top = horizontalAnchor ? '' : `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
  106. inkStyle.height = horizontalAnchor ? '' : `${linkNode.clientHeight}px`;
  107. inkStyle.left = horizontalAnchor ? `${linkNode.offsetLeft}px` : '';
  108. inkStyle.width = horizontalAnchor ? `${linkNode.clientWidth}px` : '';
  109. if (horizontalAnchor) {
  110. (0, _scrollIntoViewIfNeeded.default)(linkNode, {
  111. scrollMode: 'if-needed',
  112. block: 'nearest'
  113. });
  114. }
  115. }
  116. };
  117. const getInternalCurrentAnchor = (_links, _offsetTop = 0, _bounds = 5) => {
  118. const linkSections = [];
  119. const container = getCurrentContainer();
  120. _links.forEach(link => {
  121. const sharpLinkMatch = sharpMatcherRegex.exec(link === null || link === void 0 ? void 0 : link.toString());
  122. if (!sharpLinkMatch) {
  123. return;
  124. }
  125. const target = document.getElementById(sharpLinkMatch[1]);
  126. if (target) {
  127. const top = getOffsetTop(target, container);
  128. if (top <= _offsetTop + _bounds) {
  129. linkSections.push({
  130. link,
  131. top
  132. });
  133. }
  134. }
  135. });
  136. if (linkSections.length) {
  137. const maxSection = linkSections.reduce((prev, curr) => curr.top > prev.top ? curr : prev);
  138. return maxSection.link;
  139. }
  140. return '';
  141. };
  142. const setCurrentActiveLink = (0, _useEvent.default)(link => {
  143. // FIXME: Seems a bug since this compare is not equals
  144. // `activeLinkRef` is parsed value which will always trigger `onChange` event.
  145. if (activeLinkRef.current === link) {
  146. return;
  147. }
  148. // https://github.com/ant-design/ant-design/issues/30584
  149. const newLink = typeof getCurrentAnchor === 'function' ? getCurrentAnchor(link) : link;
  150. setActiveLink(newLink);
  151. activeLinkRef.current = newLink;
  152. // onChange should respect the original link (which may caused by
  153. // window scroll or user click), not the new link
  154. onChange === null || onChange === void 0 ? void 0 : onChange(link);
  155. });
  156. const handleScroll = React.useCallback(() => {
  157. if (animating.current) {
  158. return;
  159. }
  160. const currentActiveLink = getInternalCurrentAnchor(links, targetOffset !== undefined ? targetOffset : offsetTop || 0, bounds);
  161. setCurrentActiveLink(currentActiveLink);
  162. }, [dependencyListItem, targetOffset, offsetTop]);
  163. const handleScrollTo = React.useCallback(link => {
  164. setCurrentActiveLink(link);
  165. const sharpLinkMatch = sharpMatcherRegex.exec(link);
  166. if (!sharpLinkMatch) {
  167. return;
  168. }
  169. const targetElement = document.getElementById(sharpLinkMatch[1]);
  170. if (!targetElement) {
  171. return;
  172. }
  173. const container = getCurrentContainer();
  174. const scrollTop = (0, _getScroll.default)(container);
  175. const eleOffsetTop = getOffsetTop(targetElement, container);
  176. let y = scrollTop + eleOffsetTop;
  177. y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
  178. animating.current = true;
  179. (0, _scrollTo.default)(y, {
  180. getContainer: getCurrentContainer,
  181. callback() {
  182. animating.current = false;
  183. }
  184. });
  185. }, [targetOffset, offsetTop]);
  186. const wrapperClass = (0, _classnames.default)(hashId, cssVarCls, rootCls, rootClassName, `${prefixCls}-wrapper`, {
  187. [`${prefixCls}-wrapper-horizontal`]: anchorDirection === 'horizontal',
  188. [`${prefixCls}-rtl`]: direction === 'rtl'
  189. }, className, anchorClassName);
  190. const anchorClass = (0, _classnames.default)(prefixCls, {
  191. [`${prefixCls}-fixed`]: !affix && !showInkInFixed
  192. });
  193. const inkClass = (0, _classnames.default)(`${prefixCls}-ink`, {
  194. [`${prefixCls}-ink-visible`]: activeLink
  195. });
  196. const wrapperStyle = Object.assign(Object.assign({
  197. maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh'
  198. }, anchorStyle), style);
  199. const createNestedLink = options => Array.isArray(options) ? options.map(item => (/*#__PURE__*/React.createElement(_AnchorLink.default, Object.assign({
  200. replace: replace
  201. }, item, {
  202. key: item.key
  203. }), anchorDirection === 'vertical' && createNestedLink(item.children)))) : null;
  204. const anchorContent = /*#__PURE__*/React.createElement("div", {
  205. ref: wrapperRef,
  206. className: wrapperClass,
  207. style: wrapperStyle
  208. }, /*#__PURE__*/React.createElement("div", {
  209. className: anchorClass
  210. }, /*#__PURE__*/React.createElement("span", {
  211. className: inkClass,
  212. ref: spanLinkNode
  213. }), 'items' in props ? createNestedLink(items) : children));
  214. React.useEffect(() => {
  215. const scrollContainer = getCurrentContainer();
  216. handleScroll();
  217. scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.addEventListener('scroll', handleScroll);
  218. return () => {
  219. scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.removeEventListener('scroll', handleScroll);
  220. };
  221. }, [dependencyListItem]);
  222. React.useEffect(() => {
  223. if (typeof getCurrentAnchor === 'function') {
  224. setCurrentActiveLink(getCurrentAnchor(activeLinkRef.current || ''));
  225. }
  226. }, [getCurrentAnchor]);
  227. React.useEffect(() => {
  228. updateInk();
  229. }, [anchorDirection, getCurrentAnchor, dependencyListItem, activeLink]);
  230. const memoizedContextValue = React.useMemo(() => ({
  231. registerLink,
  232. unregisterLink,
  233. scrollTo: handleScrollTo,
  234. activeLink,
  235. onClick,
  236. direction: anchorDirection
  237. }), [activeLink, onClick, handleScrollTo, anchorDirection]);
  238. const affixProps = affix && typeof affix === 'object' ? affix : undefined;
  239. return wrapCSSVar(/*#__PURE__*/React.createElement(_context2.default.Provider, {
  240. value: memoizedContextValue
  241. }, affix ? (/*#__PURE__*/React.createElement(_affix.default, Object.assign({
  242. offsetTop: offsetTop,
  243. target: getCurrentContainer
  244. }, affixProps), anchorContent)) : anchorContent));
  245. };
  246. if (process.env.NODE_ENV !== 'production') {
  247. Anchor.displayName = 'Anchor';
  248. }
  249. var _default = exports.default = Anchor;