All files / packages/tools/src/widgets Widget.ts

0% Statements 0/36
0% Branches 0/12
0% Functions 0/9
0% Lines 0/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170                                                                                                                                                                                                                                                                                                                                                   
import type { WidgetProps, WidgetSize } from './types';
 
/**
 * Base class for any widget that can be added to cornerstone. Currently it is
 * responsible only for holding the `rootElement`, contains a method that allows
 * adding it to the DOM and it also listens to container's size changes when the
 * widget is already added to the DOM. `dispose` must be called to destroy the
 * widget because it removes the widget from the DOM and stop listening to
 * container changes.
 *
 * You can apply some styles to widgets using the widget id or the `widget` class.
 *
 * Example:
 *   type ColorPickerProps = WidgetProps & {
 *     selectedColor: string;
 *   }
 *
 *   class ColorPicker extends Widget {
 *     constructor(props: ColorPickerProps) {
 *       super(props);
 *       // [code]
 *     }
 *
 *     public show() {
 *       console.log('Show color picker');
 *     }
 *
 *     protected containerResized() {
 *       console.log('New container size: ', this.containerSize);
 *     }
 *   }
 *
 *   const colorPicker = new ColorPicker({
 *     container: document.body,
 *     selectedColor: '#000';
 *   });
 *
 *   // another way to add the color picker to the DOM
 *   colorPicker.appendTo(document.body)
 *
 *   // Show color picker
 *   colorPicker.show();
 */
abstract class Widget {
  private _id: string;
  private _rootElement: HTMLElement;
  private _containerSize: WidgetSize;
  private _containerResizeObserver: ResizeObserver;
 
  constructor({ id, container }: WidgetProps) {
    this._id = id;
    this._containerSize = { width: 0, height: 0 };
    this._rootElement = this.createRootElement(id);
    this._containerResizeObserver = new ResizeObserver(
      this._containerResizeCallback
    );
 
    if (container) {
      this.appendTo(container);
    }
  }
 
  /**
   * Widget id
   */
  public get id() {
    return this._id;
  }
 
  /**
   * Widget's root element
   */
  public get rootElement(): HTMLElement {
    return this._rootElement;
  }
 
  /**
   * Append the widget to a parent element
   * @param container - HTML element where the widget should be added to
   */
  public appendTo(container: HTMLElement) {
    const {
      _rootElement: rootElement,
      _containerResizeObserver: resizeObserver,
    } = this;
    const { parentElement: currentContainer } = rootElement;
 
    if (!container || container === currentContainer) {
      return;
    }
 
    if (currentContainer) {
      resizeObserver.unobserve(currentContainer);
    }
 
    container.appendChild(rootElement);
    resizeObserver.observe(container);
  }
 
  /**
   * Removes the widget from the DOM and stop listening to DOM events
   */
  public destroy() {
    const {
      _rootElement: rootElement,
      _containerResizeObserver: resizeObserver,
    } = this;
    const { parentElement } = rootElement;
 
    parentElement?.removeChild(rootElement);
    resizeObserver.disconnect();
  }
 
  protected get containerSize(): WidgetSize {
    // Returns a copy to prevent any external change
    return { ...this._containerSize };
  }
 
  /**
   * Creates the root element which is a div by default
   * @param id - Root element id
   * @returns A new HTML element where all other elements should be added to
   */
  protected createRootElement(id: string): HTMLElement {
    const rootElement = document.createElement('div');
 
    rootElement.id = id;
    rootElement.classList.add('widget');
 
    Object.assign(rootElement.style, {
      width: '100%',
      height: '100%',
    });
 
    return rootElement;
  }
 
  /**
   * Method called every time widget's container is resize giving the
   * opportunity to children classes to act when that happens.
   */
  protected onContainerResize() {
    // no-op
  }
 
  private _containerResizeCallback = (entries: ResizeObserverEntry[]): void => {
    let width;
    let height;
 
    const { contentRect, contentBoxSize } = entries[0];
 
    // `contentRect` is better supported than `borderBoxSize` or `contentBoxSize`,
    // but it is left over from an earlier implementation of the Resize Observer API
    // and may be deprecated in future versions.
    // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentRect
    if (contentRect) {
      width = contentRect.width;
      height = contentRect.height;
    } else if (contentBoxSize?.length) {
      width = contentBoxSize[0].inlineSize;
      height = contentBoxSize[0].blockSize;
    }
 
    this._containerSize = { width, height };
    this.onContainerResize();
  };
}
 
export { Widget as default, Widget };