Skip to content

Commit

Permalink
improve scrolling to in-page location
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinMalfait committed Dec 16, 2022
1 parent 79818fc commit 5994283
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 4 deletions.
38 changes: 36 additions & 2 deletions packages/@headlessui-react/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ function useScrollLock(
if (!ownerDocument) return

let d = disposables()
let scrollPosition = window.pageYOffset

function style(node: HTMLElement, property: string, value: string) {
let previous = node.style.getPropertyValue(property)
Expand All @@ -123,10 +124,36 @@ function useScrollLock(
}

if (isIOS()) {
let scrollPosition = window.pageYOffset
style(document.body, 'marginTop', `-${scrollPosition}px`)
style(ownerDocument.body, 'marginTop', `-${scrollPosition}px`)
window.scrollTo(0, 0)

// Relatively hacky, but if you click a link like `<a href="#foo">` in the Dialog, and there
// exists an element on the page (outside of the Dialog) with that id, then the browser will
// scroll to that position. However, this is not the case if the element we want to scroll to
// is higher and the browser needs to scroll up, but it doesn't do that.
//
// Let's try and capture that element and store it, so that we can later scroll to it once the
// Dialog closes.
let scrollToElement: HTMLElement | null = null
d.addEventListener(
ownerDocument,
'click',
(e) => {
if (e.target instanceof HTMLElement) {
try {
let anchor = e.target.closest('a')
if (!anchor) return
let { hash } = new URL(anchor.href)
let el = ownerDocument.querySelector(hash)
if (el && !resolveAllowedContainers().some((container) => container.contains(el))) {
scrollToElement = el as HTMLElement
}
} catch (err) {}
}
},
true
)

d.addEventListener(
ownerDocument,
'touchmove',
Expand Down Expand Up @@ -161,6 +188,13 @@ function useScrollLock(
// (Since the value of window.pageYOffset is 0 in the first case, we should be able to
// always sum these values)
window.scrollTo(0, window.pageYOffset + scrollPosition)

// If we captured an element that should be scrolled to, then we can try to do that if the
// element is still connected (aka, still in the DOM).
if (scrollToElement && scrollToElement.isConnected) {
scrollToElement.scrollIntoView({ block: 'nearest' })
scrollToElement = null
}
})
}

Expand Down
38 changes: 36 additions & 2 deletions packages/@headlessui-vue/src/components/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export let Dialog = defineComponent({
if (!owner) return

let d = disposables()
let scrollPosition = window.pageYOffset

function style(node: HTMLElement, property: string, value: string) {
let previous = node.style.getPropertyValue(property)
Expand All @@ -249,10 +250,36 @@ export let Dialog = defineComponent({
}

if (isIOS()) {
let scrollPosition = window.pageYOffset
style(document.body, 'marginTop', `-${scrollPosition}px`)
style(owner.body, 'marginTop', `-${scrollPosition}px`)
window.scrollTo(0, 0)

// Relatively hacky, but if you click a link like `<a href="#foo">` in the Dialog, and there
// exists an element on the page (outside of the Dialog) with that id, then the browser will
// scroll to that position. However, this is not the case if the element we want to scroll to
// is higher and the browser needs to scroll up, but it doesn't do that.
//
// Let's try and capture that element and store it, so that we can later scroll to it once the
// Dialog closes.
let scrollToElement: HTMLElement | null = null
d.addEventListener(
owner,
'click',
(e) => {
if (e.target instanceof HTMLElement) {
try {
let anchor = e.target.closest('a')
if (!anchor) return
let { hash } = new URL(anchor.href)
let el = owner!.querySelector(hash)
if (el && !resolveAllowedContainers().some((container) => container.contains(el))) {
scrollToElement = el as HTMLElement
}
} catch (err) {}
}
},
true
)

d.addEventListener(
owner,
'touchmove',
Expand Down Expand Up @@ -288,6 +315,13 @@ export let Dialog = defineComponent({
// (Since the value of window.pageYOffset is 0 in the first case, we should be able to
// always sum these values)
window.scrollTo(0, window.pageYOffset + scrollPosition)

// If we captured an element that should be scrolled to, then we can try to do that if the
// element is still connected (aka, still in the DOM).
if (scrollToElement && scrollToElement.isConnected) {
scrollToElement.scrollIntoView({ block: 'nearest' })
scrollToElement = null
}
})
}

Expand Down

0 comments on commit 5994283

Please sign in to comment.