position-items.ts 2.9 KB

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