From fbf1b337eb278d9b7d93eb2ff67602943424b43c Mon Sep 17 00:00:00 2001 From: Tom Begley Date: Mon, 10 Feb 2025 08:21:55 +0000 Subject: [PATCH 1/2] Wrap Link in forwardRef --- src/private/Link.js | 108 +++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/src/private/Link.js b/src/private/Link.js index 46204781..d0aa94d8 100644 --- a/src/private/Link.js +++ b/src/private/Link.js @@ -29,57 +29,63 @@ function isExternalLink(external_link, href) { return external_link; } -function Link({ - children, - preOnClick, - target, - linkTarget, - href, - download, - external_link, - disabled, - ...otherProps -}) { - const updateLocation = e => { - const hasModifiers = e.metaKey || e.shiftKey || e.altKey || e.ctrlKey; - if (hasModifiers) { - return; - } - if (disabled) { - e.preventDefault(); - return; - } - if (preOnClick) { - preOnClick(); - } - if (href && !isExternalLink(external_link, href)) { - // prevent anchor from updating location - e.preventDefault(); - window.history.pushState({}, '', href); - window.dispatchEvent(new CustomEvent('_dashprivate_pushstate')); - // scroll back to top - window.scrollTo(0, 0); - } - }; - - const linkIsExternal = href && isExternalLink(external_link, href); - /** - * ideally, we would use cloneElement however - * that doesn't work with dash's recursive - * renderTree implementation for some reason - */ - return ( - updateLocation(e)} - > - {children} - - ); -} +const Link = React.forwardRef( + ( + { + children, + preOnClick, + target, + linkTarget, + href, + download, + external_link, + disabled, + ...otherProps + }, + ref + ) => { + const updateLocation = e => { + const hasModifiers = e.metaKey || e.shiftKey || e.altKey || e.ctrlKey; + if (hasModifiers) { + return; + } + if (disabled) { + e.preventDefault(); + return; + } + if (preOnClick) { + preOnClick(); + } + if (href && !isExternalLink(external_link, href)) { + // prevent anchor from updating location + e.preventDefault(); + window.history.pushState({}, '', href); + window.dispatchEvent(new CustomEvent('_dashprivate_pushstate')); + // scroll back to top + window.scrollTo(0, 0); + } + }; + + const linkIsExternal = href && isExternalLink(external_link, href); + /** + * ideally, we would use cloneElement however + * that doesn't work with dash's recursive + * renderTree implementation for some reason + */ + return ( + updateLocation(e)} + ref={ref} + > + {children} + + ); + } +); Link.propTypes = { /** From 28b77d484893211e12f8544af74745f9f06717d9 Mon Sep 17 00:00:00 2001 From: Tom Begley Date: Mon, 10 Feb 2025 11:37:53 +0000 Subject: [PATCH 2/2] Intercept navigation clicks --- src/private/__tests__/Link.test.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/private/__tests__/Link.test.js b/src/private/__tests__/Link.test.js index 8ec4793a..a9243531 100644 --- a/src/private/__tests__/Link.test.js +++ b/src/private/__tests__/Link.test.js @@ -70,7 +70,12 @@ describe('Link', () => { const user = userEvent.setup(); const outerOnClick = jest.fn(); const link = render( -
outerOnClick(e.defaultPrevented)}> +
{ + outerOnClick(e.defaultPrevented); + e.preventDefault(); + }} + > Clickable
); @@ -86,7 +91,12 @@ describe('Link', () => { const user = userEvent.setup(); const outerOnClick = jest.fn(); const link = render( -
outerOnClick(e.defaultPrevented)}> +
{ + outerOnClick(e.defaultPrevented); + e.preventDefault(); + }} + > Clickable