position-items.ts 2.7 KB

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