traceRowWidthMeasurer.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. type ArgumentTypes<F> = F extends (...args: infer A) => any ? A : never;
  2. type EventStore<T> = {
  3. [K in keyof TraceRowWidthMeasurerEvents<T>]: Set<TraceRowWidthMeasurerEvents<T>[K]>;
  4. };
  5. interface TraceRowWidthMeasurerEvents<T> {
  6. ['max']: (max: number) => void;
  7. ['row measure']: (row: T) => void;
  8. ['row measure end']: () => void;
  9. }
  10. export class TraceRowWidthMeasurer<T> {
  11. cache: Map<T, number> = new Map();
  12. queue: [T, HTMLElement][] = [];
  13. drainRaf: number | null = null;
  14. max: number = 0;
  15. constructor() {
  16. this.drain = this.drain.bind(this);
  17. }
  18. listeners: EventStore<T> = {
  19. max: new Set(),
  20. 'row measure': new Set(),
  21. 'row measure end': new Set(),
  22. };
  23. once<K extends keyof TraceRowWidthMeasurerEvents<T>>(
  24. event: K,
  25. cb: (max: number) => void
  26. ) {
  27. const listener = (...args: any[]) => {
  28. cb(...(args as ArgumentTypes<typeof cb>));
  29. this.off(event, listener);
  30. };
  31. this.on(event, listener);
  32. }
  33. on<K extends keyof TraceRowWidthMeasurerEvents<T>>(
  34. eventName: K,
  35. cb: TraceRowWidthMeasurerEvents<T>[K]
  36. ): void {
  37. this.listeners?.[eventName]?.add?.(cb);
  38. }
  39. off<K extends keyof TraceRowWidthMeasurerEvents<T>>(
  40. eventName: K,
  41. cb: TraceRowWidthMeasurerEvents<T>[K]
  42. ): void {
  43. this.listeners?.[eventName]?.delete?.(cb);
  44. }
  45. dispatch<K extends keyof TraceRowWidthMeasurerEvents<T>>(
  46. event: K,
  47. ...args: ArgumentTypes<TraceRowWidthMeasurerEvents<T>[K]>
  48. ): void {
  49. if (!this.listeners[event] || this.listeners[event].size === 0) {
  50. return;
  51. }
  52. for (const handler of this.listeners[event]) {
  53. // @ts-expect-error
  54. handler(...args);
  55. }
  56. }
  57. enqueueMeasure(node: T, element: HTMLElement) {
  58. if (this.cache.has(node)) {
  59. return;
  60. }
  61. this.queue.push([node, element]);
  62. if (this.drainRaf !== null) {
  63. window.cancelAnimationFrame(this.drainRaf);
  64. }
  65. this.drainRaf = window.requestAnimationFrame(this.drain);
  66. }
  67. drain() {
  68. const startMax = this.max;
  69. while (this.queue.length > 0) {
  70. const next = this.queue.pop()!;
  71. const width = this.measure(next[0], next[1]);
  72. if (width > this.max) {
  73. this.max = width;
  74. }
  75. }
  76. if (this.max !== startMax) {
  77. this.dispatch('max', this.max);
  78. }
  79. this.dispatch('row measure end');
  80. }
  81. measure(node: T, element: HTMLElement): number {
  82. const cache = this.cache.get(node);
  83. if (cache !== undefined) {
  84. return cache;
  85. }
  86. const rect = element.getBoundingClientRect();
  87. this.cache.set(node, rect.width);
  88. return rect.width;
  89. }
  90. }