1. use requestAnimationFrame
2. add a minimum size for rect so that it cannot shrink from that point
```
const ACTION_FLAGS = {
  RESIZE_NW: 1 << 0, // 0000 0001
  RESIZE_NE: 1 << 1, // 0000 0010
  RESIZE_SW: 1 << 2, // 0000 0100
  RESIZE_SE: 1 << 3, // 0000 1000
  RESIZE_N: 1 << 4,  // 0001 0000
  RESIZE_S: 1 << 5,  // 0010 0000
  RESIZE_W: 1 << 6,  // 0100 0000
  RESIZE_E: 1 << 7,  // 1000 0000
  MOVE: 1 << 8        // 0001 0000 0000
};

class DndPlugin extends RectLifecycleInterface {
  constructor() {
    super();
    this.currentRect = null;
    this.action = null;
    this.host = null;
    this.startX = 0;
    this.startY = 0;
    this.startRect = {};
    this.boundOnMouseMove = this.onMouseMove.bind(this);
    this.boundOnMouseUp = this.onMouseUp.bind(this);
    this.listeners = new WeakMap();
  }

  attach(host) {
    if (host) {
      this.host = host;
    }
    return null;
  }

  onRectCreate(rect, element) {
    const callback = this.onMouseDown.bind(this, rect);
    this.listeners.set(element, callback);
    element.addEventListener('mousedown', callback);
  }

  onRectRemove(rect, element) {
    element.removeEventListener('mousedown', this.listeners.get(element));
    this.listeners.delete(element);
  }

  onMouseDown(rect, e) {
    const rectElement = e.target;
    if (!rectElement) return;
    const rectStyle = rectElement.style;

    const x = parseInt(rectStyle.left, 10);
    const y = parseInt(rectStyle.top, 10);
    const width = parseInt(rectStyle.getPropertyValue('--width'), 10);
    const height = parseInt(rectStyle.getPropertyValue('--height'), 10);
    const borderSize = parseInt(rectStyle.getPropertyValue('--border-size') || getComputedStyle(this.host).getPropertyValue('--border-size'), 10) || 6;
    const vertexSize = parseInt(rectStyle.getPropertyValue('--vertex-size') || getComputedStyle(this.host).getPropertyValue('--vertex-size'), 10) || 0;

    const bbox = rectElement.getBoundingClientRect();
    const cursorOffsetX = e.clientX - bbox.left;
    const cursorOffsetY = e.clientY - bbox.top;

    const xOffset = width - borderSize;
    const yOffset = height - borderSize;
    const x1 = -(vertexSize-borderSize)/2;
    const x2 = (vertexSize+borderSize)/2;
    const x3 = x1 + xOffset;
    const x4 = x2 + xOffset;
    const y1 = x1;
    const y2 = x2;
    const y3 = y1 + yOffset;
    const y4 = y2 + yOffset;
    const heightSubBorder = height - borderSize;
    const widthSubBorder = width - borderSize;

    // action    x-range  y-range
    // resize-nw: (x1, x2) (y1, y2)
    // resize-ne: (x3, x4) (y1, y2)
    // resize-sw: (x1, x2) (y3, y4)
    // resize-se: (x3, x4) (y3, y4)
    // resize-n: (0, width), (0, borderSize);
    // resize-s: (0, width), (heightSubBorder, height)
    // resize-w: (0, borderSize), (0, height)
    // resize-e: (widthSubBorder, width), (0, height)
    // move: (borderSize, widthSubBorder), (borderSize, heightSubBorder)

    if (cursorOffsetX >= x1 && cursorOffsetX <= x2 && cursorOffsetY >= y1 && cursorOffsetY <= y2) {
      this.action = ACTION_FLAGS.RESIZE_NW;
    } else if (cursorOffsetX >= x3 && cursorOffsetX <= x4 && cursorOffsetY >= y1 && cursorOffsetY <= y2) {
      this.action = ACTION_FLAGS.RESIZE_NE;
    } else if (cursorOffsetX >= x1 && cursorOffsetX <= x2 && cursorOffsetY >= y3 && cursorOffsetY <= y4) {
      this.action = ACTION_FLAGS.RESIZE_SW;
    } else if (cursorOffsetX >= x3 && cursorOffsetX <= x4 && cursorOffsetY >= y3 && cursorOffsetY <= y4) {
      this.action = ACTION_FLAGS.RESIZE_SE;
    } else if (cursorOffsetX >= 0 && cursorOffsetX <= width && cursorOffsetY >= 0 && cursorOffsetY <= borderSize) {
      this.action = ACTION_FLAGS.RESIZE_N;
    } else if (cursorOffsetX >= 0 && cursorOffsetX <= width && cursorOffsetY >= heightSubBorder && cursorOffsetY <= height) {
      this.action = ACTION_FLAGS.RESIZE_S;
    } else if (cursorOffsetX >= 0 && cursorOffsetX <= borderSize && cursorOffsetY >= 0 && cursorOffsetY <= height) {
      this.action = ACTION_FLAGS.RESIZE_W;
    } else if (cursorOffsetX >= widthSubBorder && cursorOffsetX <= width && cursorOffsetY >= 0 && cursorOffsetY <= height) {
      this.action = ACTION_FLAGS.RESIZE_E;
    } else if (
      cursorOffsetX > borderSize && cursorOffsetX < widthSubBorder &&
      cursorOffsetY > borderSize && cursorOffsetY < heightSubBorder
    ) {
      this.action = ACTION_FLAGS.MOVE;
    } else {
      return;
    }

    this.startX = e.clientX;
    this.startY = e.clientY;

    this.currentRect = rect;
    this.startRect = {x, y, width, height};

    e.preventDefault();

    document.addEventListener('mousemove', this.boundOnMouseMove);
    document.addEventListener('mouseup', this.boundOnMouseUp);
  }

  onMouseMove(e) {
    if (!this.currentRect) return;

    const dx = e.clientX - this.startX;
    const dy = e.clientY - this.startY;
    let {x: newX, y: newY, width: newWidth, height: newHeight} = this.startRect;

    switch (this.action) {
      case ACTION_FLAGS.MOVE:
        newX += dx;
        newY += dy;
        break;
      case ACTION_FLAGS.RESIZE_NW:
        newX += dx;
        newY += dy;
        newWidth -= dx;
        newHeight -= dy;
        break;
      case ACTION_FLAGS.RESIZE_NE:
        newY += dy;
        newWidth += dx;
        newHeight -= dy;
        break;
      case ACTION_FLAGS.RESIZE_SW:
        newX += dx;
        newWidth -= dx;
        newHeight += dy;
        break;
      case ACTION_FLAGS.RESIZE_SE:
        newWidth += dx;
        newHeight += dy;
        break;
      case ACTION_FLAGS.RESIZE_N:
        newY += dy;
        newHeight -= dy;
        break;
      case ACTION_FLAGS.RESIZE_S:
        newHeight += dy;
        break;
      case ACTION_FLAGS.RESIZE_W:
        newX += dx;
        newWidth -= dx;
        break;
      case ACTION_FLAGS.RESIZE_E:
        newWidth += dx;
        break;
    }

    this.updateRect({x: newX, y: newY, width: newWidth, height: newHeight});
  }

  updateRect(newRect) {
    const updates = {};
    for (const key in newRect) {
      if (newRect[key] !== this.startRect[key]) {
        updates[key] = newRect[key];
      }
    }
    if (Object.keys(updates).length > 0) {
      this.currentRect.update(updates);
    }
  }

  onMouseUp() {
    document.removeEventListener('mousemove', this.boundOnMouseMove);
    document.removeEventListener('mouseup', this.boundOnMouseUp);
    this.currentRect = null;
    this.action = null;
  }
}
```