position-items.ts 2.5 KB

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