Edit in JSFiddle

const editor = grapesjs.init({
  container: '#gjs',
  fromElement: true,
  height: '100%',
  storageManager: false,
  noticeOnUnload: false,
  layerManager: { custom: true },
});

const { Components, Layers } = editor;
const cmpElMap = new WeakMap();

// Layer item component
Vue.component('layer-item', {
  template: '#layer-item-template',
  props: { component: Object, level: Number },
  data() {
    return {
      name: '',
      components: [],
      visible: true,
      open: false,
      selected: false,
      hovered: false,
      editing: false,
    }
  },
  mounted() {
    this.updateLayer(Layers.getLayerData(this.component));
    cmpElMap.set(this.$refs.layerRef, this.component);
    editor.on('layer:component', this.onLayerComponentUpdate);
  },
  destroyed() {
    editor.off('layer:component', this.onLayerComponentUpdate);
  },
  methods: {
    onLayerComponentUpdate(cmp) {
      if (cmp === this.component) {
        this.updateLayer(Layers.getLayerData(cmp));
      }
    },
    updateLayer(data) {
      this.name = data.name;
      this.components = data.components;
      this.visible = data.visible;
      this.open = data.open;
      this.selected = data.selected;
      this.hovered = data.hovered;
    },
    toggleVisibility() {
      const { component } = this;
      Layers.setVisible(this.component, !this.visible);
    },
    toggleOpen() {
      const { component } = this;
      Layers.setOpen(this.component, !this.open);
    },
    setHover(hovered) {
      Layers.setLayerData(this.component, { hovered })
    },
    setSelected(event) {
      Layers.setLayerData(this.component, { selected: true }, { event })
    },
    setEditing(value) {
      this.editing = value;
      const el = this.$refs.nameInput;
      if (!value) {
        Layers.setName(this.component, el.innerText)
      } else {
        setTimeout(() => el.focus())
      }
    },
  }
});

// Load the Vue app
const app = new Vue({
  el: '.layer-manager',
  data: {
    root: null,
    isDragging: false,
    draggingCmp: null,
    draggingOverCmp: null,
    dragIndicator: {},
    canMoveRes: {},
  },
  mounted() {
    editor.on('layer:custom', this.handleCustom);
    editor.on('layer:root', this.handleRootChange);
  },
  destroyed() {
    editor.off('layer:custom', this.handleCustom);
    editor.off('layer:root', this.handleRootChange);
  },
  methods: {
    handleCustom(props = {}) {
      const { container, root } = props;
      container && container.appendChild(this.$el);
      this.handleRootChange(root);
    },
    handleRootChange(root) {
      console.log('root update', root);
      this.root = root;
    },
    getDragTarget(ev) {
      const el = document.elementFromPoint(ev.clientX, ev.clientY);
      const dragEl = el.closest('[data-layer-move]');
      const elLayer = el.closest('[data-layer-item]');

      return {
          dragEl,
          elLayer,
          cmp: cmpElMap.get(elLayer),
      }
    },
    onDragStart(ev) {
      if (this.getDragTarget(ev).dragEl) {
        this.isDragging = true;
      }
    },
    onDragMove(ev) {
      if (!this.isDragging) return;
      const { cmp, elLayer } = this.getDragTarget(ev);
      if (!cmp || !elLayer) return;
      const { draggingCmp } = this;
      const layerRect = elLayer.getBoundingClientRect();
      const layerH = elLayer.offsetHeight;
      const layerY = elLayer.offsetTop;
      const pointerY = ev.clientY;
      const isBefore = pointerY < (layerRect.y + layerH / 2);
      const cmpSource = !draggingCmp ? cmp : draggingCmp;
      const cmpTarget = cmp.parent();
      const cmpIndex = cmp.index() + (isBefore ? 0 : 1);
      this.draggingCmp = !draggingCmp ? cmp : draggingCmp;
      this.draggingOverCmp = cmp;
      const canMove = Components.canMove(cmpTarget, cmpSource, cmpIndex);
      const canMoveRes = { ...canMove, index: cmpIndex };
      this.canMoveRes = canMoveRes;
      const dragLevel = (cmp ? cmp.parents() : []).length;
      this.dragIndicator = {
          y: layerY + (isBefore ? 0 : layerH),
          h: layerH,
          offset: dragLevel * 10 + 20,
          show: !!(this.draggingCmp && canMoveRes.result),
      };
    },
    onDragEnd(ev) {
      const { canMoveRes } = this;
      canMoveRes.result && canMoveRes.source.move(canMoveRes.target, { at: canMoveRes.index });
      this.isDragging = false;
      this.draggingCmp = null;
      this.draggingOverCmp = null;
      this.dragIndicator = {};
      this.canMoveRes = {};
    },
  }
});
<div id="gjs">
  <div style="padding: 25px">Custom Layer Manager</div>
  <div>Element A</div>
  <div style="padding: 20px">
    <div>Element B1</div>
    <div>Element B2</div>
    <div>Element B3</div>
  </div>
  <div>Element C</div>
</div>

<div style="display: none">
  <div
    class="layer-manager"
    @pointerdown="onDragStart"
    @pointermove="onDragMove"
    @pointerup="onDragEnd"
  >
    <layer-item v-if="root" :component="root" :level="0"></layer-item>
    <div
      v-if="dragIndicator.show"
      class="layer-drag-indicator"
      :style="{ top: `${dragIndicator.y}px`, marginLeft: `${dragIndicator.offset}px`, width: `calc(100% - ${dragIndicator.offset}px)` }"></div>
  </div>

  <div id="layer-item-template" style="display: none;">
    <div :class="['layer-item', !visible && 'hidden']">
      <div
        :class="['layer-item-row', selected && 'selected', hovered && 'hovered']"
        @click="setSelected"
        @mouseenter="setHover(true)"
        @mouseleave="setHover(false)"
        ref="layerRef"
        data-layer-item
      >
        <div class="layer-item-icon layer-item-eye" @click.stop="toggleVisibility()">
          <svg v-if="visible" viewBox="0 0 24 24"><path fill="currentColor" d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" /></svg>
          <svg v-else viewBox="0 0 24 24"><path fill="currentColor" d="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z" /></svg>
        </div>
        <div class="layer-item-name-cnt" :style="{ marginLeft: `${level*10}px` }">
          <div :class="['layer-item-icon layer-item-chevron', open && 'open', !components.length && 'hidden']" @click.stop="toggleOpen()">
            <svg viewBox="0 0 24 24"><path fill="currentColor" d="M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z" /></svg>
          </div>
          <div ref="nameInput"
            :class="['layer-item-name', editing && 'editing']"
            :contenteditable="editing"
            @dblclick.stop="setEditing(true)"
            @blur.stop="setEditing(false)"
            @keydown.enter="setEditing(false)"
          >
            {{ name }}
          </div>
        </div>
        <div v-if="component.get('draggable')" class="layer-item-icon layer-item-move" data-layer-move>
          <svg viewBox="0 0 24 24"><path fill="currentColor" d="M13,6V11H18V7.75L22.25,12L18,16.25V13H13V18H16.25L12,22.25L7.75,18H11V13H6V16.25L1.75,12L6,7.75V11H11V6H7.75L12,1.75L16.25,6H13Z"/></svg>
        </div>
      </div>
      <div v-if="open" class="layer-items">
        <layer-item v-for="cmp in components" :key="cmp.getId()" :component="cmp" :level="level + 1"/>
      </div>
    </div>
  </div>
</div>
body, html {
  margin: 0;
  height: 100%;
}
.layer-manager {
  position: relative;
  text-align: left;
}
.layer-item.hidden {
  opacity: 0.5;
}
.layer-item-icon {
  width: 15px;
  cursor: pointer;
}
.layer-item-eye {
}
.layer-item-chevron {
  transform: rotate(90deg);
}
.layer-item-chevron.open {
  transform: rotate(180deg);
}
.layer-item-chevron.hidden {
  opacity: 0;
  pointer-events: none;
}
.layer-item-row {
  display: flex;
  align-items: center;
  user-select: none;
  gap: 8px;
  padding: 5px 8px;
  border-bottom: 1px solid rgba(0,0,0,0.35);
}
.layer-item-row.selected {
  background-color: rgba(255,255,255,0.15);
}
.layer-item-row.hovered {
  background-color: rgba(255,255,255,0.05);
}
.layer-item-name {
  margin-left: 3px;
}
.layer-item-name.editing {
  background-color: white;
  color: #555;
  padding: 0 3px;
}
.layer-item-name-cnt {
  display: flex;
  align-items: center;
  flex-grow: 1;
}
.layer-drag-indicator {
  position: absolute;
  width: 100%;
  height: 1px;
  left: 0;
  background-color: #3b97e3;
}