Загрузка данных


import { PriceScaleMode } from 'lightweight-charts';

import { PriceScaleControls as PriceScaleControlsView } from '@components/PriceScaleControls';
import { ReactRenderer } from '@core/ReactRenderer';
import { Direction } from '@src/types';

import { PriceScale } from './PriceScale';

interface PriceScaleControlsParams {
  leftPriceScale: PriceScale;
  rightPriceScale: PriceScale;
  onPriceScaleChange: () => void;
}

export class PriceScaleControls {
  private readonly leftPriceScale: PriceScale;
  private readonly rightPriceScale: PriceScale;
  private readonly onPriceScaleChange: () => void;
  private readonly container: HTMLElement;
  private readonly renderer: ReactRenderer;

  private paneElement: HTMLElement | null = null;
  private hoveredPriceScale: PriceScale | null = null;

  constructor({
    leftPriceScale,
    rightPriceScale,
    onPriceScaleChange,
  }: PriceScaleControlsParams) {
    this.leftPriceScale = leftPriceScale;
    this.rightPriceScale = rightPriceScale;
    this.onPriceScaleChange = onPriceScaleChange;

    this.container = document.createElement('div');
    this.container.className = 'moex-price-scale-controls';
    this.container.style.position = 'absolute';
    this.container.style.inset = '0';
    this.container.style.display = 'flex';
    this.container.style.alignItems = 'center';
    this.container.style.justifyContent = 'center';
    this.container.style.zIndex = '10';
    this.container.style.pointerEvents = 'none';

    this.renderer = new ReactRenderer(this.container);
  }

  public mount(paneElement: HTMLElement): void {
    if (this.paneElement === paneElement) {
      this.refresh();
      return;
    }

    this.unmount();

    this.paneElement = paneElement;
    this.paneElement.addEventListener('pointermove', this.handlePointerMove);
    this.paneElement.addEventListener('pointerleave', this.handlePointerLeave);

    this.refresh();
  }

  public refresh(): void {
    if (!this.hoveredPriceScale) {
      this.hide();
      return;
    }

    this.render(this.hoveredPriceScale);
  }

  public destroy(): void {
    this.unmount();
    this.renderer.destroy();
  }

  private handlePointerMove = (event: PointerEvent): void => {
    const nextPriceScale = this.getHoveredPriceScale(event.clientX);

    if (this.hoveredPriceScale === nextPriceScale) {
      return;
    }

    this.hoveredPriceScale = nextPriceScale;
    this.refresh();
  };

  private handlePointerLeave = (): void => {
    this.hoveredPriceScale = null;
    this.refresh();
  };

  private render(priceScale: PriceScale): void {
    const priceScaleElement = this.getPriceScaleElement(priceScale);

    if (!priceScaleElement || !this.canShowControls(priceScale, priceScaleElement)) {
      this.hide();
      return;
    }

    priceScaleElement.style.position = 'relative';

    if (this.container.parentElement !== priceScaleElement) {
      priceScaleElement.appendChild(this.container);
    }

    this.renderer.renderComponent(
      <PriceScaleControlsView
        isAutoScale={priceScale.isAutoScaleEnabled()}
        isLogarithmic={priceScale.getMode() === PriceScaleMode.Logarithmic}
        onToggleAutoScale={() => {
          priceScale.toggleAutoScale();
          this.refresh();
          this.onPriceScaleChange();
        }}
        onToggleLogarithmic={() => {
          priceScale.toggleLogarithmic();
          this.refresh();
          this.onPriceScaleChange();
        }}
      />,
    );

    this.container.classList.add('moex-price-scale-controls_visible');
  }

  private hide(): void {
    this.container.classList.remove('moex-price-scale-controls_visible');
  }

  private getHoveredPriceScale(clientX: number): PriceScale | null {
    if (this.isPointerInsidePriceScale(this.leftPriceScale, clientX)) {
      return this.leftPriceScale;
    }

    if (this.isPointerInsidePriceScale(this.rightPriceScale, clientX)) {
      return this.rightPriceScale;
    }

    return null;
  }

  private isPointerInsidePriceScale(priceScale: PriceScale, clientX: number): boolean {
    const priceScaleElement = this.getPriceScaleElement(priceScale);

    if (!priceScaleElement || !this.canShowControls(priceScale, priceScaleElement)) {
      return false;
    }

    const rect = priceScaleElement.getBoundingClientRect();

    return clientX >= rect.left && clientX <= rect.right;
  }

  private canShowControls(priceScale: PriceScale, priceScaleElement: HTMLElement): boolean {
    return (
      priceScale.isVisible() &&
      priceScale.hasVisibleSeriesData() &&
      priceScaleElement.getBoundingClientRect().width > 0
    );
  }

  private getPriceScaleElement(priceScale: PriceScale): HTMLTableCellElement | null {
    if (!this.paneElement) {
      return null;
    }

    const cells = this.paneElement.querySelectorAll<HTMLTableCellElement>(':scope > td');

    if (cells.length < 3) {
      return null;
    }

    return priceScale.side === Direction.Left ? cells.item(0) : cells.item(cells.length - 1);
  }

  private unmount(): void {
    if (this.paneElement) {
      this.paneElement.removeEventListener('pointermove', this.handlePointerMove);
      this.paneElement.removeEventListener('pointerleave', this.handlePointerLeave);
    }

    this.paneElement = null;
    this.hoveredPriceScale = null;
    this.container.remove();
    this.hide();
  }
}


private syncPaneContainers(): void {
  const lwcPaneElement = this.lwcPane.getHTMLElement();

  if (!lwcPaneElement) {
    this.schedulePaneContainerSync();
    return;
  }

  const cells = lwcPaneElement.querySelectorAll<HTMLTableCellElement>(':scope > td');
  const chartCell = cells.item(1);

  if (!chartCell) {
    this.schedulePaneContainerSync();
    return;
  }

  chartCell.style.position = 'relative';
  chartCell.appendChild(this.legendContainer);
  chartCell.appendChild(this.paneOverlayContainer);

  this.priceScaleControls.mount(lwcPaneElement);
}