<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
  <div class="app" id="app">
    <svg class="app__svg" :viewBox="viewBox">
      <rect
        class="app__rect"
        v-for="cell in cells"
        :class="{ 'app__rect--active': cell.isActive }"
        :x="getCellX(cell)"
        :y="getCellY(cell)"
        :width="width"
        :height="height"
        @mousedown="onMouseDown(cell)"
        @mouseenter="onMouseEnter(cell)"
        :key="cell.idx"
      />
    </svg>

    <form class="app__form">
      <label class="app__label">
        startX:
        <input
          type="number"
          min="0"
          :max="colls - 1"
          v-model.number="dragStart.col"
        />
      </label>
      <label class="app__label">
        startY:
        <input
          type="number"
          min="0"
          :max="rows - 1"
          v-model.number="dragStart.row"
        />
      </label>

      <br />

      <label class="app__label">
        endX:
        <input
          type="number"
          min="0"
          :max="colls - 1"
          v-model.number="dragEnd.col"
        />
      </label>
      <label class="app__label">
        endY:
        <input
          type="number"
          min="0"
          :max="rows - 1"
          v-model.number="dragEnd.row"
        />
      </label>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      rows: 10,
      colls: 10,
      width: 20,
      height: 20,
      gap: 4,
      cells: [],
      isMouseDown: false,
      dragStart: { col: 0, row: 0 },
      dragEnd: { col: 0, row: 0 }
    };
  },

  created() {
    this.cells = Array.from(
      { length: this.rows * this.colls },
      (_, idx) => ({
        idx,
        isActive: false,
        col: idx % this.colls,
        row: Math.floor(idx / this.colls)
      })
    );
    
    window.addEventListener('mouseup', this.onMouseUp.bind(this));
  },

  watch: {
    "dragStart.col": "markActiveCells",
    "dragStart.row": "markActiveCells",

    "dragEnd.col": "markActiveCells",
    "dragEnd.row": "markActiveCells"
  },

  methods: {
    getCellX(cell) {
      return cell.col * (this.width + this.gap);
    },

    getCellY(cell) {
      return cell.row * (this.height + this.gap);
    },

    markActiveCells() {
      const colStart = Math.min(this.dragStart.col, this.dragEnd.col);
      const rowStart = Math.min(this.dragStart.row, this.dragEnd.row);
      const colEnd = Math.max(this.dragStart.col, this.dragEnd.col);
      const rowEnd = Math.max(this.dragStart.row, this.dragEnd.row);

      for (let c = 0; c < this.colls; c++) {
        for (let r = 0; r < this.rows; r++) {
          const idx = r * this.colls + c;
          const isActive = 
            c >= colStart && c <= colEnd &&
            r >= rowStart && r <= rowEnd;
          this.cells[idx].isActive = isActive;
        }
      }
    },

    onMouseDown(cell) {
      this.isMouseDown = true;
      cell.isActive = true;
      this.dragStart = { ...cell };
      this.dragEnd = { ...cell };
    },

    onMouseUp(e) {
      this.isMouseDown = false;
    },

    onMouseEnter(cell) {
      if (!this.isMouseDown) return;
      this.dragEnd = { ...cell };
    }
  },

  computed: {
    viewBox() {
      const w = this.width * this.colls + this.gap * (this.colls - 1);
      const h = this.height * this.rows + this.gap * (this.rows - 1);
      return `0 0 ${w} ${h}`;
    }
  }
};
</script>

<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
.app {
  display: flex;
  align-items: center;
  gap: 25px;
}
.app__svg {
  display: block;
  overflow: visible;
  width: 50%;
  transform: scale(0.75) rotateY(-45deg) rotateX(60deg);
}
.app__rect {
  fill: none;
  stroke-width: 1px;
  stroke: #aaa;
  pointer-events: all;
}

.app__rect--active {
  fill: rgba(255, 99, 71, 0.25);
  stroke: rgba(255, 99, 71, 1);
}

.app__form {
  width: 50%;
}

.app__label {
  white-space: nowrap;
}
</style>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.