Resizable Experiment

by Nicholas Berlette

HTML

<div style="--width:300px;--height:300px;margin:100px auto;--bg:#eee;--stroke:#ff33aa;--stroke-width:2px;width:var(--width);height:var(--height);" id="box-1">
  <v-handles data-label-id="box-1" backdrop="#789"></v-handles>
</div>

<script>
  const $ = document.querySelectorAll.bind(document);
  const VARIABLES_CSS = getVariablesCss();
  const isFixed = elem => {
    do {
      if (window.getComputedStyle(elem).position == 'fixed') return true;
    } while (elem = elem.offsetParent);
    return false;
  };

  // <v-handle />
  window.Handle = class Handle extends HTMLElement {
    constructor() {
      super();
      this.$shadow = this.attachShadow({
        mode: 'closed'
      });
      this.styles = [Handle.styles];
      this.hooks = {
        button: ['pointerdown', this.on_element_resize_start.bind(this)]
      };
    }

    get styles() {
      return this._styles || (this.styles = Handle.styles);
    }

    set styles(value) {
      (this._styles ??= new CSSStyleSheet()).replaceSync([value].flat(2));
      if (this.$shadow) {
        this.$shadow.adoptedStyleSheets = [this._styles];
      }
    }

    connectedCallback() {
      this.$connected = true;
      this.render();
      this.button = this.$shadow.querySelector('button');

      for (const key in this.hooks) {
        if (!(key in this)) continue;
        const [event, listener] = this.hooks[key];
        this[key].addEventListener(event, listener);
      }

      this.placement = this.getAttribute('placement');
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (oldValue !== newValue) {
        if (name === 'placement') this.placement = newValue;
        if (name === 'styles') this.styles = newValue;

        this.render();
      }
    }

    disconnectedCallback() {
      this.$connected = false;
      for (const key in this.hooks) {
        if (!(key in this)) continue;
        const [event, listener] = this.hooks[key];
        this[key].removeEventListener(event,...