diff --git a/examples/index.html b/examples/index.html index 3ed6fe2..2cd56e0 100644 --- a/examples/index.html +++ b/examples/index.html @@ -4,8 +4,15 @@ text-expander demo @@ -30,6 +37,7 @@

Multiword text-expander element

const {key, provide, text} = event.detail if (key === '#') { const menu = document.createElement('ul') + menu.classList.add('menu') menu.role = 'listbox' for (const issue of [ '#1 Implement a text-expander element', diff --git a/src/text-expander-element.ts b/src/text-expander-element.ts index a03be12..48a413a 100644 --- a/src/text-expander-element.ts +++ b/src/text-expander-element.ts @@ -20,25 +20,6 @@ type Key = { const states = new WeakMap() -function isTopLayer(el: Element) { - try { - if (el.matches(':popover-open')) return true - } catch { - /* fall through */ - } - try { - if (el.matches('dialog:modal')) return true - } catch { - /* fall through */ - } - try { - if (el.matches(':fullscreen')) return true - } catch { - /* fall through */ - } - return false -} - class TextExpander { expander: TextExpanderElement input: HTMLInputElement | HTMLTextAreaElement @@ -103,18 +84,7 @@ class TextExpander { this.expander.dispatchEvent(new Event('text-expander-activate')) - let {top, left} = new InputRange(this.input, match.position).getBoundingClientRect() - if (isTopLayer(menu)) { - const rect = this.input.getBoundingClientRect() - top += rect.top - left += rect.left - if (getComputedStyle(menu).position === 'absolute') { - top += window.scrollY - left += window.scrollX - } - } - menu.style.top = `${top}px` - menu.style.left = `${left}px` + this.positionMenu(menu, match.position) this.combobox.start() menu.addEventListener('combobox-commit', this.oncommit) @@ -124,6 +94,27 @@ class TextExpander { this.combobox.navigate(1) } + private positionMenu(menu: HTMLElement, position: number) { + const caretRect = new InputRange(this.input, position).getBoundingClientRect() + const targetPosition = {left: caretRect.left, top: caretRect.top + caretRect.height} + + const currentPosition = menu.getBoundingClientRect() + + const delta = { + left: targetPosition.left - currentPosition.left, + top: targetPosition.top - currentPosition.top + } + + if (delta.left !== 0 || delta.top !== 0) { + // Use computedStyle to avoid nesting calc() deeper and deeper + const currentStyle = getComputedStyle(menu) + + // Using `calc` avoids having to parse the current pixel value + menu.style.left = currentStyle.left ? `calc(${currentStyle.left} + ${delta.left}px)` : `${delta.left}px` + menu.style.top = currentStyle.top ? `calc(${currentStyle.top} + ${delta.top}px)` : `${delta.top}px` + } + } + private deactivate() { const menu = this.menu if (!menu || !this.combobox) return false