position-items.ts 2.7 KB

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