zsarnett/Lit-Grid-Layout

View on GitHub
src/lit-resizable.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import {
  css,
  CSSResult,
  customElement,
  html,
  LitElement,
  property,
  svg,
  TemplateResult,
} from "lit-element";
import "./lit-draggable";
import type { DraggingEvent, LGLDomEvent } from "./types";
import { fireEvent } from "./util/fire-event";

@customElement("lit-resizable")
export class LitResizable extends LitElement {
  @property({ attribute: false }) public handle?: HTMLElement;

  @property({ type: Boolean }) public disabled = false;

  private startWidth?: number;

  private startHeight?: number;

  protected render(): TemplateResult {
    return html`
      <slot></slot>

      ${this.disabled
        ? ""
        : html`
            <lit-draggable
              @dragging=${this._resize}
              @dragStart=${this._resizeStart}
              @dragEnd=${this._resizeEnd}
            >
              ${!this.handle
                ? svg`
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      class="icon-tabler-arrows-diagonal-2"
                      viewBox="0 0 24 24"
                    >
                      <path stroke="none" d="M0 0h24v24H0z" />
                      <polyline points="16 20 20 20 20 16" />
                      <line x1="14" y1="14" x2="20" y2="20" />
                      <polyline points="8 4 4 4 4 8" />
                      <line x1="4" y1="4" x2="10" y2="10" />
                    </svg>
                  `
                : html`${this.handle}`}
            </lit-draggable>
          `}
    `;
  }

  private _resizeStart(ev: Event): void {
    ev.preventDefault();
    ev.stopPropagation();

    this.startWidth = this.clientWidth;
    this.startHeight = this.clientHeight;

    fireEvent(this, "resizeStart");
  }

  private _resize(ev: LGLDomEvent<DraggingEvent>): void {
    ev.preventDefault();
    ev.stopPropagation();

    if (this.startWidth === undefined || this.startHeight === undefined) {
      return;
    }

    const { deltaX, deltaY } = ev.detail;

    if (deltaY === 0 && deltaX === 0) {
      return;
    }

    const width = this.startWidth + deltaX;
    const height = this.startHeight + deltaY;

    fireEvent(this, "resize", {
      width,
      height,
      deltaX,
      deltaY,
    });
  }

  private _resizeEnd(ev: Event): void {
    ev.preventDefault();
    ev.stopPropagation();

    this.startWidth = undefined;
    this.startHeight = undefined;

    fireEvent(this, "resizeEnd");
  }

  static get styles(): CSSResult {
    return css`
      :host {
        position: relative;
        display: block;
      }

      lit-draggable {
        position: absolute;
        left: var(--resize-handle-position-left, unset);
        top: var(--resize-handle-postion-top, unset);
        bottom: var(--resize-handle-position-bottom, 0);
        right: var(--resize-handle-postion-right, 0);
        width: var(--resize-handle-size, 18px);
        height: var(--resize-handle-size, 18px);
        z-index: var(--resize-handle-z-index, 5);
        opacity: var(--resize-handle-opacity, 1);
        user-select: none;
      }

      .icon-tabler-arrows-diagonal-2 {
        width: 100%;
        height: 100%;
        stroke-width: 1.5;
        stroke: #607d8b;
        fill: none;
        stroke-linecap: round;
        stroke-linejoin: round;
        cursor: se-resize;
      }
    `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "lit-resizable": LitResizable;
  }
}