traceRowWidthMeasurer.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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: Array<[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. (handler as any)(...args);
  54. }
  55. }
  56. enqueueMeasure(node: T, element: HTMLElement) {
  57. if (this.cache.has(node)) {
  58. return;
  59. }
  60. this.queue.push([node, element]);
  61. if (this.drainRaf !== null) {
  62. window.cancelAnimationFrame(this.drainRaf);
  63. }
  64. this.drainRaf = window.requestAnimationFrame(this.drain);
  65. }
  66. drain() {
  67. const startMax = this.max;
  68. while (this.queue.length > 0) {
  69. const next = this.queue.pop()!;
  70. const width = this.measure(next[0], next[1]);
  71. if (width > this.max) {
  72. this.max = width;
  73. }
  74. }
  75. if (this.max !== startMax) {
  76. this.dispatch('max', this.max);
  77. }
  78. this.dispatch('row measure end');
  79. }
  80. measure(node: T, element: HTMLElement): number {
  81. const cache = this.cache.get(node);
  82. if (cache !== undefined) {
  83. return cache;
  84. }
  85. const rect = element.getBoundingClientRect();
  86. this.cache.set(node, rect.width);
  87. return rect.width;
  88. }
  89. }