2024-08-12 05:50:54 -04:00
|
|
|
import { debounce } from 'lodash'
|
|
|
|
|
2024-08-15 06:07:40 -04:00
|
|
|
const COLLAPSED_HEADER_HEIGHT = 75
|
|
|
|
const OFFSET_FOR_ENTRIES_ABOVE = 70
|
2024-08-12 05:50:54 -04:00
|
|
|
|
2024-08-15 06:07:40 -04:00
|
|
|
export const positionItems = debounce(
|
2024-09-19 06:08:13 -04:00
|
|
|
(
|
|
|
|
element: HTMLDivElement,
|
|
|
|
previousFocusedItemIndex: number,
|
|
|
|
docId: string
|
|
|
|
) => {
|
2024-08-12 05:50:54 -04:00
|
|
|
const items = Array.from(
|
|
|
|
element.querySelectorAll<HTMLDivElement>('.review-panel-entry')
|
|
|
|
)
|
|
|
|
|
|
|
|
items.sort((a, b) => Number(a.dataset.pos) - Number(b.dataset.pos))
|
|
|
|
|
|
|
|
if (!items.length) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-22 05:44:22 -04:00
|
|
|
let activeItemIndex = items.findIndex(item =>
|
|
|
|
item.classList.contains('review-panel-entry-action')
|
2024-08-12 05:50:54 -04:00
|
|
|
)
|
2024-08-22 05:44:22 -04:00
|
|
|
|
|
|
|
if (activeItemIndex === -1) {
|
|
|
|
// if there is no action available
|
|
|
|
// check if there is a focused entry
|
|
|
|
activeItemIndex = items.findIndex(item =>
|
|
|
|
item.classList.contains('review-panel-entry-focused')
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (activeItemIndex === -1) {
|
2024-08-15 06:07:40 -04:00
|
|
|
// if entry was not focused manually
|
|
|
|
// check if there is an entry in selection and use that as the focused item
|
2024-08-22 05:44:22 -04:00
|
|
|
activeItemIndex = items.findIndex(item =>
|
2024-08-15 06:07:40 -04:00
|
|
|
item.classList.contains('review-panel-entry-highlighted')
|
|
|
|
)
|
|
|
|
}
|
2024-08-22 05:44:22 -04:00
|
|
|
|
|
|
|
if (activeItemIndex === -1) {
|
|
|
|
activeItemIndex = previousFocusedItemIndex
|
2024-08-12 05:50:54 -04:00
|
|
|
}
|
|
|
|
|
2024-08-22 05:44:22 -04:00
|
|
|
const activeItem = items[activeItemIndex]
|
|
|
|
if (!activeItem) {
|
2024-08-12 05:50:54 -04:00
|
|
|
return
|
|
|
|
}
|
2024-08-15 06:07:40 -04:00
|
|
|
|
2024-08-22 05:44:22 -04:00
|
|
|
const activeItemTop = getTopPosition(activeItem, activeItemIndex === 0)
|
2024-08-15 06:07:40 -04:00
|
|
|
|
2024-08-22 05:44:22 -04:00
|
|
|
activeItem.style.top = `${activeItemTop}px`
|
|
|
|
activeItem.style.visibility = 'visible'
|
|
|
|
const focusedItemRect = activeItem.getBoundingClientRect()
|
2024-08-12 05:50:54 -04:00
|
|
|
|
2024-08-22 05:44:22 -04:00
|
|
|
// above the active item
|
|
|
|
let topLimit = activeItemTop
|
|
|
|
for (let i = activeItemIndex - 1; i >= 0; i--) {
|
2024-08-12 05:50:54 -04:00
|
|
|
const item = items[i]
|
|
|
|
const rect = item.getBoundingClientRect()
|
2024-08-15 06:07:40 -04:00
|
|
|
let top = getTopPosition(item, i === 0)
|
2024-08-12 05:50:54 -04:00
|
|
|
const bottom = top + rect.height
|
|
|
|
if (bottom > topLimit) {
|
|
|
|
top = topLimit - rect.height - 10
|
|
|
|
}
|
2024-08-15 06:07:40 -04:00
|
|
|
item.style.top = `${top}px`
|
2024-08-12 05:50:54 -04:00
|
|
|
item.style.visibility = 'visible'
|
|
|
|
topLimit = top
|
|
|
|
}
|
|
|
|
|
2024-08-22 05:44:22 -04:00
|
|
|
// below the active item
|
|
|
|
let bottomLimit = activeItemTop + focusedItemRect.height
|
|
|
|
for (let i = activeItemIndex + 1; i < items.length; i++) {
|
2024-08-12 05:50:54 -04:00
|
|
|
const item = items[i]
|
|
|
|
const rect = item.getBoundingClientRect()
|
2024-08-15 06:07:40 -04:00
|
|
|
let top = getTopPosition(item, false)
|
2024-08-12 05:50:54 -04:00
|
|
|
if (top < bottomLimit) {
|
|
|
|
top = bottomLimit + 10
|
|
|
|
}
|
2024-08-15 06:07:40 -04:00
|
|
|
item.style.top = `${top}px`
|
2024-08-12 05:50:54 -04:00
|
|
|
item.style.visibility = 'visible'
|
|
|
|
bottomLimit = top + rect.height
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2024-09-19 06:08:13 -04:00
|
|
|
docId,
|
2024-08-22 05:44:22 -04:00
|
|
|
activeItemIndex,
|
2024-08-12 05:50:54 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
100,
|
|
|
|
{ leading: false, trailing: true, maxWait: 1000 }
|
|
|
|
)
|
2024-08-15 06:07:40 -04:00
|
|
|
|
|
|
|
function getTopPosition(item: HTMLDivElement, isFirstEntry: boolean) {
|
|
|
|
const offset = isFirstEntry ? 0 : OFFSET_FOR_ENTRIES_ABOVE
|
|
|
|
return Math.max(COLLAPSED_HEADER_HEIGHT + offset, Number(item.dataset.top))
|
|
|
|
}
|