From 4d0ef71dabccef7a7253bb4258aa5872e021eb1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:24:05 +0000 Subject: [PATCH 1/4] Initial plan From 045d76d69e328ad665775f9ea94dbaa0784d28c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:27:45 +0000 Subject: [PATCH 2/4] fix: use History API instead of window.location.hash to prevent Safari scroll-to-top Co-authored-by: marlonmarcello <1956448+marlonmarcello@users.noreply.github.com> --- package-lock.json | 4 ++-- src/lib/hooks/use-modal.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index baa2a9f..d011b85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@wethegit/react-modal", - "version": "3.1.0", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@wethegit/react-modal", - "version": "3.1.0", + "version": "3.2.0", "license": "MIT", "dependencies": { "@changesets/changelog-github": "~0.5.0", diff --git a/src/lib/hooks/use-modal.ts b/src/lib/hooks/use-modal.ts index e25ef76..bf5b01c 100644 --- a/src/lib/hooks/use-modal.ts +++ b/src/lib/hooks/use-modal.ts @@ -28,7 +28,7 @@ export function useModal(props: UseModalOptions = {}) { // an extra dependecy and stay within the render loop current = cur - if (cur !== ModalStates.CLOSED) return ModalStates.CLOSED + if (cur !== ModalStates.CLOSED) return ModalStates.CLOSED return cur }) @@ -37,7 +37,6 @@ export function useModal(props: UseModalOptions = {}) { if (current === ModalStates.CLOSED || !current) return if (hash && window && window.location.hash === `#${hash}`) { - window.location.hash = "" window.history.replaceState({}, "", window.location.pathname) } @@ -59,7 +58,7 @@ export function useModal(props: UseModalOptions = {}) { if (current === ModalStates.OPEN) return if (hash && window && window.location.hash !== `#${hash}`) { - window.location.hash = `#${hash}` + window.history.pushState({}, "", `#${hash}`) } }, [hash]) @@ -81,10 +80,15 @@ export function useModal(props: UseModalOptions = {}) { // Check for a hash on mount handleHashChange() + // hashchange fires when navigating to same-page anchors (e.g. ) window.addEventListener("hashchange", handleHashChange) + // popstate fires when navigating via browser history (back/forward), + // which is what history.pushState entries use + window.addEventListener("popstate", handleHashChange) return () => { window.removeEventListener("hashchange", handleHashChange) + window.removeEventListener("popstate", handleHashChange) } }, [handleClose, handleOpen, hash]) From b2da150ded628128b43f8d37221373f289f6d2fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:30:46 +0000 Subject: [PATCH 3/4] chore: add minor changeset for history API hash fix Co-authored-by: marlonmarcello <1956448+marlonmarcello@users.noreply.github.com> --- .changeset/fix-use-history-api-for-hash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-use-history-api-for-hash.md diff --git a/.changeset/fix-use-history-api-for-hash.md b/.changeset/fix-use-history-api-for-hash.md new file mode 100644 index 0000000..d59ac02 --- /dev/null +++ b/.changeset/fix-use-history-api-for-hash.md @@ -0,0 +1,5 @@ +--- +"@wethegit/react-modal": minor +--- + +Use the History API (`pushState`/`replaceState`) instead of directly assigning `window.location.hash` when opening and closing hash-linked modals. This prevents Safari from scrolling the user to the top of the page. Also adds a `popstate` listener so browser back/forward navigation correctly opens and closes the modal. From 007b43b8d11f7466d46f132d5f065ab5125cd785 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:34:26 +0000 Subject: [PATCH 4/4] fix: safe window guard and preserve query string in replaceState Co-authored-by: marlonmarcello <1956448+marlonmarcello@users.noreply.github.com> --- src/lib/hooks/use-modal.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/hooks/use-modal.ts b/src/lib/hooks/use-modal.ts index bf5b01c..647e9dc 100644 --- a/src/lib/hooks/use-modal.ts +++ b/src/lib/hooks/use-modal.ts @@ -36,8 +36,12 @@ export function useModal(props: UseModalOptions = {}) { // we don't want focus back on the trigger if (current === ModalStates.CLOSED || !current) return - if (hash && window && window.location.hash === `#${hash}`) { - window.history.replaceState({}, "", window.location.pathname) + if (hash && typeof window !== "undefined" && window.location.hash === `#${hash}`) { + window.history.replaceState( + {}, + "", + window.location.pathname + window.location.search + ) } if (triggerRef && triggerRef.current) triggerRef.current.focus() @@ -57,7 +61,7 @@ export function useModal(props: UseModalOptions = {}) { if (current === ModalStates.OPEN) return - if (hash && window && window.location.hash !== `#${hash}`) { + if (hash && typeof window !== "undefined" && window.location.hash !== `#${hash}`) { window.history.pushState({}, "", `#${hash}`) } }, [hash])