Skip to content

Commit fc7f9b6

Browse files
authored
Merge pull request #57 from github/fix-positioning-error
Fix positioning error
2 parents c0fc9fc + 1be094c commit fc7f9b6

File tree

2 files changed

+32
-33
lines changed

2 files changed

+32
-33
lines changed

examples/index.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
<meta charset="utf-8" />
55
<title>text-expander demo</title>
66
<style>
7-
[aria-selected='true'] {
8-
background: #eee;
7+
.menu {
8+
position: absolute;
9+
list-style-type: none;
10+
padding: 0;
11+
background: lightgray;
12+
13+
[aria-selected='true'] {
14+
background: #eee;
15+
}
916
}
1017
</style>
1118
</head>
@@ -30,6 +37,7 @@ <h2>Multiword text-expander element</h2>
3037
const {key, provide, text} = event.detail
3138
if (key === '#') {
3239
const menu = document.createElement('ul')
40+
menu.classList.add('menu')
3341
menu.role = 'listbox'
3442
for (const issue of [
3543
'#1 Implement a text-expander element',

src/text-expander-element.ts

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,6 @@ type Key = {
2020

2121
const states = new WeakMap()
2222

23-
function isTopLayer(el: Element) {
24-
try {
25-
if (el.matches(':popover-open')) return true
26-
} catch {
27-
/* fall through */
28-
}
29-
try {
30-
if (el.matches('dialog:modal')) return true
31-
} catch {
32-
/* fall through */
33-
}
34-
try {
35-
if (el.matches(':fullscreen')) return true
36-
} catch {
37-
/* fall through */
38-
}
39-
return false
40-
}
41-
4223
class TextExpander {
4324
expander: TextExpanderElement
4425
input: HTMLInputElement | HTMLTextAreaElement
@@ -103,18 +84,7 @@ class TextExpander {
10384

10485
this.expander.dispatchEvent(new Event('text-expander-activate'))
10586

106-
let {top, left} = new InputRange(this.input, match.position).getBoundingClientRect()
107-
if (isTopLayer(menu)) {
108-
const rect = this.input.getBoundingClientRect()
109-
top += rect.top
110-
left += rect.left
111-
if (getComputedStyle(menu).position === 'absolute') {
112-
top += window.scrollY
113-
left += window.scrollX
114-
}
115-
}
116-
menu.style.top = `${top}px`
117-
menu.style.left = `${left}px`
87+
this.positionMenu(menu, match.position)
11888

11989
this.combobox.start()
12090
menu.addEventListener('combobox-commit', this.oncommit)
@@ -124,6 +94,27 @@ class TextExpander {
12494
this.combobox.navigate(1)
12595
}
12696

97+
private positionMenu(menu: HTMLElement, position: number) {
98+
const caretRect = new InputRange(this.input, position).getBoundingClientRect()
99+
const targetPosition = {left: caretRect.left, top: caretRect.top + caretRect.height}
100+
101+
const currentPosition = menu.getBoundingClientRect()
102+
103+
const delta = {
104+
left: targetPosition.left - currentPosition.left,
105+
top: targetPosition.top - currentPosition.top
106+
}
107+
108+
if (delta.left !== 0 || delta.top !== 0) {
109+
// Use computedStyle to avoid nesting calc() deeper and deeper
110+
const currentStyle = getComputedStyle(menu)
111+
112+
// Using `calc` avoids having to parse the current pixel value
113+
menu.style.left = currentStyle.left ? `calc(${currentStyle.left} + ${delta.left}px)` : `${delta.left}px`
114+
menu.style.top = currentStyle.top ? `calc(${currentStyle.top} + ${delta.top}px)` : `${delta.top}px`
115+
}
116+
}
117+
127118
private deactivate() {
128119
const menu = this.menu
129120
if (!menu || !this.combobox) return false

0 commit comments

Comments
 (0)