position-items.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { debounce } from 'lodash'
  2. const COLLAPSED_HEADER_HEIGHT = 75
  3. const OFFSET_FOR_ENTRIES_ABOVE = 70
  4. export const positionItems = debounce(
  5. (element: HTMLDivElement, previousFocusedItemIndex: number) => {
  6. const items = Array.from(
  7. element.querySelectorAll<HTMLDivElement>('.review-panel-entry')
  8. )
  9. items.sort((a, b) => Number(a.dataset.pos) - Number(b.dataset.pos))
  10. if (!items.length) {
  11. return
  12. }
  13. let activeItemIndex = items.findIndex(item =>
  14. item.classList.contains('review-panel-entry-action')
  15. )
  16. if (activeItemIndex === -1) {
  17. // if there is no action available
  18. // check if there is a focused entry
  19. activeItemIndex = items.findIndex(item =>
  20. item.classList.contains('review-panel-entry-focused')
  21. )
  22. }
  23. if (activeItemIndex === -1) {
  24. // if entry was not focused manually
  25. // check if there is an entry in selection and use that as the focused item
  26. activeItemIndex = items.findIndex(item =>
  27. item.classList.contains('review-panel-entry-highlighted')
  28. )
  29. }
  30. if (activeItemIndex === -1) {
  31. activeItemIndex = previousFocusedItemIndex
  32. }
  33. const activeItem = items[activeItemIndex]
  34. if (!activeItem) {
  35. return
  36. }
  37. const activeItemTop = getTopPosition(activeItem, activeItemIndex === 0)
  38. activeItem.style.top = `${activeItemTop}px`
  39. activeItem.style.visibility = 'visible'
  40. const focusedItemRect = activeItem.getBoundingClientRect()
  41. // above the active item
  42. let topLimit = activeItemTop
  43. for (let i = activeItemIndex - 1; i >= 0; i--) {
  44. const item = items[i]
  45. const rect = item.getBoundingClientRect()
  46. let top = getTopPosition(item, i === 0)
  47. const bottom = top + rect.height
  48. if (bottom > topLimit) {
  49. top = topLimit - rect.height - 10
  50. }
  51. item.style.top = `${top}px`
  52. item.style.visibility = 'visible'
  53. topLimit = top
  54. }
  55. // below the active item
  56. let bottomLimit = activeItemTop + focusedItemRect.height
  57. for (let i = activeItemIndex + 1; i < items.length; i++) {
  58. const item = items[i]
  59. const rect = item.getBoundingClientRect()
  60. let top = getTopPosition(item, false)
  61. if (top < bottomLimit) {
  62. top = bottomLimit + 10
  63. }
  64. item.style.top = `${top}px`
  65. item.style.visibility = 'visible'
  66. bottomLimit = top + rect.height
  67. }
  68. return {
  69. activeItemIndex,
  70. min: topLimit,
  71. max: bottomLimit,
  72. }
  73. },
  74. 100,
  75. { leading: false, trailing: true, maxWait: 1000 }
  76. )
  77. function getTopPosition(item: HTMLDivElement, isFirstEntry: boolean) {
  78. const offset = isFirstEntry ? 0 : OFFSET_FOR_ENTRIES_ABOVE
  79. return Math.max(COLLAPSED_HEADER_HEIGHT + offset, Number(item.dataset.top))
  80. }