Skip to content

Commit c97f615

Browse files
Scroll details menu into view when it opens off-screen
If the details menu button is at the bottom of the page. we don't automatically scroll the page to show you the dropdown options. While there is an `autofocus` attribute we can use, there are two problems with it: 1. it only works with inputs 2. it doesn't scroll smoothly so the experience not as smooth when you open the menu. Let's use scrollIntoView and center on the details menu.
1 parent c3cf7bb commit c97f615

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

examples/index.html

+17
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ <h1>Autofocus example</h1>
8585
</details-menu>
8686
</details>
8787

88+
<details>
89+
<summary>Scroll into view radio group</summary>
90+
<details-menu scrollIntoView>
91+
<ul role="radiogroup">
92+
<li role="radio">
93+
<label><input type="radio" name="robot" value="Hubot">Hubot</label>
94+
</li>
95+
<li role="radio">
96+
<label><input type="radio" name="robot" value="Bender">Bender</label>
97+
</li>
98+
<li role="radio">
99+
<label><input type="radio" name="robot" value="BB-8">BB-8</label>
100+
</li>
101+
</ul>
102+
</details-menu>
103+
</details>
104+
88105
<script type="text/javascript">
89106
document.addEventListener('details-menu-selected', e => console.log(e))
90107
</script>

src/index.ts

+18
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class DetailsMenuElement extends HTMLElement {
3939
fromEvent(details, 'keydown', e => keydown(details, this, e)),
4040
fromEvent(details, 'toggle', () => loadFragment(details, this), {once: true}),
4141
fromEvent(details, 'toggle', () => closeCurrentMenu(details)),
42+
fromEvent(details, 'toggle', () => scrollIntoView(details)),
4243
this.preload
4344
? fromEvent(details, 'mouseover', () => loadFragment(details, this), {once: true})
4445
: NullSubscription,
@@ -137,6 +138,23 @@ function autofocus(details: Element): boolean {
137138
}
138139
}
139140

141+
// Scroll entire details menu into view, center on it.
142+
function scrollIntoView(details: Element): boolean {
143+
if (!details.hasAttribute('open')) return false
144+
const detailsMenu = details.querySelector<HTMLElement>('details-menu [scrollIntoView]')
145+
if (detailsMenu) {
146+
const innerMenu = detailsMenu.closest('[role="menu"], [role="radiogroup"]')
147+
if (innerMenu) {
148+
innerMenu.scrollIntoView({behavior: 'smooth', block: 'center'})
149+
} else {
150+
return false
151+
}
152+
return true
153+
} else {
154+
return false
155+
}
156+
}
157+
140158
// Focus first item unless an item is already focused.
141159
function focusFirstItem(details: Element) {
142160
const selected = document.activeElement

test/test.js

+39
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,45 @@ describe('details-menu element', function () {
629629
})
630630
})
631631

632+
describe('with input[scrollIntoView]', function () {
633+
beforeEach(function () {
634+
const container = document.createElement('div')
635+
container.innerHTML = `
636+
<details>
637+
<summary>Scroll into view radio group</summary>
638+
<details-menu scrollIntoView>
639+
<ul role="radiogroup">
640+
<li role="radio">
641+
<label><input type="radio" name="robot" value="Hubot">Hubot</label>
642+
</li>
643+
<li role="radio">
644+
<label><input type="radio" name="robot" value="Bender">Bender</label>
645+
</li>
646+
<li role="radio">
647+
<label><input type="radio" name="robot" value="BB-8">BB-8</label>
648+
</li>
649+
</ul>
650+
</details-menu>
651+
</details>
652+
`
653+
document.body.append(container)
654+
})
655+
656+
afterEach(function () {
657+
document.body.innerHTML = ''
658+
})
659+
660+
it('scrolls the inner menu into view on mouse click', function () {
661+
const details = document.querySelector('details')
662+
const detailsMenu = details.querySelector('details-menu')
663+
const innerMenu = detailsMenu.closest('[role="menu"]')
664+
665+
details.open = true
666+
details.dispatchEvent(new CustomEvent('toggle'))
667+
assert.equal(0, innerMenu.scrollTop)
668+
})
669+
})
670+
632671
describe('closing the menu', function () {
633672
beforeEach(function () {
634673
const container = document.createElement('div')

0 commit comments

Comments
 (0)