useDataTable.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { isString, debounce, isElement } from 'lodash-es'
  2. import { isRef, nextTick, onMounted, reactive, ref, onBeforeUnmount } from 'vue'
  3. import type { ComponentPublicInstance, Ref } from 'vue'
  4. type MaybeHTMLElement = HTMLElement | Ref<HTMLElement | null> | string | null
  5. interface DataTableOptions {
  6. parentContainer?: MaybeHTMLElement
  7. parentWrap?: MaybeHTMLElement
  8. }
  9. type ElementKey = 'container' | 'dataTable' | 'table' | 'tableThead' | 'wrap'
  10. export function useDataTable<T extends ComponentPublicInstance>(
  11. target: Ref<T | null>,
  12. options: DataTableOptions = {},
  13. ) {
  14. const { parentContainer = '.main-container', parentWrap = '.main-wrap' } = options
  15. let resizeObserver: ResizeObserver | null = null
  16. const maxHeight = ref<number | undefined>(0)
  17. const elementMap = reactive<Record<ElementKey, HTMLElement | null>>({
  18. dataTable: null,
  19. table: null,
  20. tableThead: null,
  21. container: null,
  22. wrap: null,
  23. })
  24. function updateMaxHeight() {
  25. const { container, wrap, tableThead, table } = elementMap
  26. if (!container || !wrap || !tableThead || !table) {
  27. return
  28. }
  29. const containerHeight = container.offsetHeight
  30. const wrapHeight = wrap.offsetHeight
  31. const theadHeight = tableThead.offsetHeight
  32. const tableHeight = table.offsetHeight
  33. maxHeight.value =
  34. (containerHeight < wrapHeight
  35. ? tableHeight - theadHeight - (wrapHeight - containerHeight)
  36. : containerHeight - wrapHeight - theadHeight + tableHeight) - 1
  37. }
  38. const debounceUpdateMaxHeight = debounce(() => {
  39. updateMaxHeight()
  40. }, 300)
  41. function getParentElement(element: MaybeHTMLElement) {
  42. if (isString(element)) return elementMap.dataTable?.closest<HTMLElement>(element) ?? null
  43. if (isRef(element)) return element.value
  44. if (isElement(element)) return element
  45. return null
  46. }
  47. function initTableResizeObserver() {
  48. if (!target.value) {
  49. maxHeight.value = undefined
  50. return
  51. }
  52. elementMap.dataTable = (target.value?.$el as HTMLElement) ?? null
  53. elementMap.table = elementMap.dataTable?.querySelector('.n-data-table-base-table') ?? null
  54. elementMap.tableThead = elementMap.table?.querySelector('.n-data-table-thead') ?? null
  55. nextTick(() => {
  56. elementMap.container = getParentElement(parentContainer)
  57. elementMap.wrap = getParentElement(parentWrap)
  58. const missingKeys = Object.entries(elementMap)
  59. .filter((value) => value[1] == null)
  60. .map(([key]) => key)
  61. if (missingKeys.length) {
  62. throw new Error(`Missing or null elements: ${missingKeys.join(', ')}`)
  63. }
  64. resizeObserver = new ResizeObserver((entries) => {
  65. for (const entry of entries) {
  66. const { height } = entry.contentRect
  67. if (height) debounceUpdateMaxHeight()
  68. }
  69. })
  70. if (elementMap.container) {
  71. resizeObserver.observe(elementMap.container)
  72. }
  73. })
  74. }
  75. onMounted(() => {
  76. initTableResizeObserver()
  77. })
  78. onBeforeUnmount(() => {
  79. resizeObserver?.disconnect()
  80. })
  81. return {
  82. maxHeight,
  83. updateMaxHeight,
  84. }
  85. }