Skip to content

Commit

Permalink
refactor: Extract a ItemContainer class
Browse files Browse the repository at this point in the history
  • Loading branch information
ledsun committed Oct 29, 2024
1 parent d5c7ecb commit 2b0c9c3
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 76 deletions.
69 changes: 69 additions & 0 deletions src/lib/component/autocomplete/ItemContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import createResultElement from './createResultElement'

export default class ItemContainer {
#inputElement
#element

constructor(inputElement) {
this.#inputElement = inputElement
this.#element = document.createElement('ul')
this.#element.setAttribute('popover', 'auto')
this.#element.classList.add('textae-editor__dialog__autocomplete')
inputElement.parentElement.appendChild(this.#element)
}

get element() {
return this.#element
}

set items(items) {
this.#element.innerHTML = ''

if (items.length === 0) {
this.#element.hidePopover()
return
}

const elements = items.map(createResultElement)
this.#element.append(...elements)
this.#showPopoverUnderInputElement()
}

highlight(index) {
this.#unhighlight() // Clear previous highlight.

const currentItem = this.#element.querySelector(
`li:nth-child(${index + 1})`
)

if (currentItem) {
currentItem.classList.add(
'textae-editor__dialog__autocomplete__item--highlighted'
)
}
}

#showPopoverUnderInputElement() {
const rect = this.#inputElement.getBoundingClientRect()

Object.assign(this.#element.style, {
position: 'absolute',
top: `${rect.bottom + window.scrollY}px`,
left: `${rect.left + window.scrollX}px`
})

this.#element.showPopover()
}

#unhighlight() {
const target = this.#element.querySelector(
'.textae-editor__dialog__autocomplete__item--highlighted'
)

if (target) {
target.classList.remove(
'textae-editor__dialog__autocomplete__item--highlighted'
)
}
}
}
93 changes: 17 additions & 76 deletions src/lib/component/autocomplete/index.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,58 @@
import AutocompleteModel from './autocompleteModel'
import createResultElement from './createResultElement'
import debounce300 from '../debounce300'
import delegate from 'delegate'
import ItemContainer from './ItemContainer'

export default class Autocomplete {
#inputElement
#onSelect
#model
#resultsElement
#itemsContainer

constructor(inputElement, onSearch, onSelect) {
this.#inputElement = inputElement
this.#onSelect = onSelect
this.#itemsContainer = new ItemContainer(inputElement)

this.#model = new AutocompleteModel(
(term) => onSearch(term, (results) => (this.#model.items = results)),
(items) => this.#renderItem(items),
(index) => this.#highlight(index)
(items) => (this.#itemsContainer.items = items),
(index) => this.#itemsContainer.highlight(index)
)

this.#resultsElement = document.createElement('ul')
this.#resultsElement.setAttribute('popover', 'auto')
this.#resultsElement.classList.add('textae-editor__dialog__autocomplete')
inputElement.parentElement.appendChild(this.#resultsElement)
this.#setEventHandlersToInput(this.#inputElement)
this.#setEventHandlersToItemsContainer(this.#itemsContainer.element)
}

#setEventHandlersToInput(inputElement) {
const handleInput = debounce300((term) => {
this.#model.term = term
})

this.#inputElement.addEventListener('input', ({ target }) =>
inputElement.addEventListener('input', ({ target }) =>
handleInput(target.value)
)
this.#inputElement.addEventListener('keydown', (event) =>
inputElement.addEventListener('keydown', (event) =>
this.#handleKeydown(event)
)
this.#inputElement.addEventListener('keyup', (event) =>
this.#handleKeyup(event)
)

// Hide popover when input is out of focus.
this.#inputElement.addEventListener('blur', () =>
this.#resultsElement.hidePopover()
)

this.#setEventHandlersToResults()
inputElement.addEventListener('keyup', (event) => this.#handleKeyup(event))
}

#setEventHandlersToResults() {
delegate(this.#resultsElement, 'li', 'mousedown', ({ delegateTarget }) => {
#setEventHandlersToItemsContainer(element) {
delegate(element, 'li', 'mousedown', ({ delegateTarget }) => {
this.#onSelect(delegateTarget.dataset.id, delegateTarget.dataset.label)
this.#resultsElement.hidePopover()
element.hidePopover()
})

delegate(this.#resultsElement, 'li', 'mouseover', ({ delegateTarget }) => {
delegate(element, 'li', 'mouseover', ({ delegateTarget }) => {
this.#model.highlightedIndex = Number(delegateTarget.dataset.index)
})

delegate(this.#resultsElement, 'li', 'mouseout', () => {
delegate(element, 'li', 'mouseout', () => {
this.#model.highlightedIndex = -1
})
}

#renderItem(items) {
this.#resultsElement.innerHTML = ''

if (items.length === 0) {
this.#resultsElement.hidePopover()
return
}

const elements = items.map(createResultElement)
this.#resultsElement.append(...elements)
this.#showPopoverUnderInputElement()
}

#showPopoverUnderInputElement() {
const rect = this.#inputElement.getBoundingClientRect()

Object.assign(this.#resultsElement.style, {
position: 'absolute',
top: `${rect.bottom + window.scrollY}px`,
left: `${rect.left + window.scrollX}px`
})

this.#resultsElement.showPopover()
}

#handleKeydown(event) {
if (this.#model.itemsCount === 0) return

Expand Down Expand Up @@ -118,30 +85,4 @@ export default class Autocomplete {
this.#model.clearItems()
}
}

#highlight(index) {
this.#unhighlight() // Clear previous highlight.

const currentItem = this.#resultsElement.querySelector(
`li:nth-child(${index + 1})`
)

if (currentItem) {
currentItem.classList.add(
'textae-editor__dialog__autocomplete__item--highlighted'
)
}
}

#unhighlight() {
const target = this.#resultsElement.querySelector(
'.textae-editor__dialog__autocomplete__item--highlighted'
)

if (target) {
target.classList.remove(
'textae-editor__dialog__autocomplete__item--highlighted'
)
}
}
}

0 comments on commit 2b0c9c3

Please sign in to comment.