position-items.ts 2.5 KB

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