<div id="root"></div>
// copy this into your project
@mixin colorcycle($color-list: #222 #111, $duration: 5s, $name: "colorcycle") {
  & { 
    // target only the element with these params.
    background-color: nth($color-list, 1);
    animation-name: #{$name}; 
    //animation is named after the element it resides in. 
    animation-duration: $duration;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
  }
  
  @keyframes #{$name} {
    // go through each color in the list
    @each $color in $color-list {
      // get the current index of the color
      $i: index($color-list, $color);
      // Set the percent of the animation frames based on how many colors are in the list
      $percent: ($i - 1)/length($color-list)*100; 
      #{$percent}% {
          background-color: $color; 
      }
    }
    100% {
      background-color: nth($color-list,1);
    }
  }
}

input, select {
    border:0;
    display:inline;
    background-color: rgba(0,0,20,0.4);
    color: #DEDEDE;
    padding: 3% 4%;
    &.invalid {
      border-left: 0.2rem solid #FA3433;
    }
    &:focus {
      background-color: #222244;
        outline-width: 0;
    }
}

.matrix {
  width:100%;
  input {
  cursor: pointer;
    border-radius: 0;
    background-color: rgba(0,0,20,0.2);
    border:0;
    display:inline;
    font-size:1.2rem;
    width: 50%;
    text-align:center;
    padding: 4% 0%;
    transition: background-color 0.2s;
    &:hover {
      transition: background-color 0.2s;
      background-color: rgba(10,10,20,0.5);
    }
    &:focus {
      transition: background-color 0.2s;
      @include colorcycle(rgba(0,0,20,0.2) rgba(50,50,200,0.5), 0.5s, "bluecycle");
      color: transparent;
        outline-width: 0;
      text-shadow: 0px 0px 0px #FFFFFF;
    }
    &::placeholder { 
      text-shadow: 0px 0px 0.1px rgba(255,255,255,0.3);
    }

  }

  &.matrix-4 input{
    width: 50%;
  }
    &.matrix-9 input {
    width: 33%;
  }
    &.matrix-16 input{
    width: 25%;
  }
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  color: #DEDEDE;
  background-color: #090915;
}

hr {
  height:0.2rem;
  border:0;
  background-color: #292931;
}

.editor {
  margin-top: -1.2rem;
  border-radius: 0.4rem;
  padding: 1.2rem 1.2rem;
  background-color: #333338;
  border: 1px solid black;
  width: 28rem;
}

.functions {
  margin-bottom: 0.5rem;
  display:flex;
  justify-content: center;
    input {
      width:80%;
    }
    select {
      border: 0;
      margin-left: 0.5rem;
      appearance: none
    }
}

.code {
    display:flex;
  justify-content: center;
}

pre {
  width:50%;
  padding:1rem;
  background-color: rgba(0,0,20,0.4);
  font-size: 11px;
  margin:0;
  margin-top: 0.5rem;
  &:last-child {
    margin-left: 0.5rem;
  }
}
View Compiled
const MAT4SIZE = 16;

const MATSIZES = {
  MAT4: 16,
  MAT3: 9,
  MAT2: 4
};

const MATRICES = {
  MAT4: "MAT4",
  MAT3: "MAT3",
  MAT2: "MAT2"
};

const MATLENGTH = {
  16: MATRICES.MAT4,
  9: MATRICES.MAT3,
  4: MATRICES.MAT2
};

const ROWLENGTH = {
  16: 4,
  9: 3,
  4: 2
};

const STRIP_BRACKETS = /[\[\]\s]/g;

let reduceMat = (matrix, size) => {
  return matrix.reduce((a, v, i) => {
    return (
      a +
      ` ${v}${i < matrix.length - 1 ? "," : ""}` +
      `${i % size == size - 1 ? "\n" : ""}`
    );
  }, "\n");
};

let renderGLSLMatrix = matrix => {
  if(MATLENGTH[matrix.length]) {
    return `${MATLENGTH[matrix.length].toLowerCase()}(${reduceMat(matrix, ROWLENGTH[matrix.length])})`;
  }
};

let renderJSMatrix = matrix => {
  if(ROWLENGTH[matrix.length]) {
  return `[${reduceMat(matrix, ROWLENGTH[matrix.length])}]`;
  }
};

let shrinkMat = (matrix, newSize) => {
  let currentRowSize = ROWLENGTH[matrix.length];
  let rowSize = ROWLENGTH[newSize];
  let sliceSize = matrix.length - newSize;
  return matrix
    .map((v, i) => {
      let remove = i % currentRowSize + 1 > rowSize || i > newSize + 1;
      if (remove) {
        sliceSize--;
      }
      return remove ? null : v;
    })
    .filter(v => v != null);
};

let expandMat = (matrix, newSize) => {
  return matrix.concat(new Array(newSize - matrix.length).fill(0));
};

let validMatrix = matrix => {
  return ROWLENGTH[matrix.length] != undefined;
};

class OptionSelect extends React.Component {
  static defaultProps = {
    selected: null,
    options: ["none"],
    onChange: e => console.log(e)
  };

  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onChange(e.target.value);
  }

  render() {
    return (
      <select onChange={this.handleChange}>
        {this.props.options.map(o => (
          <option selected={this.props.selected == o}>{o}</option>
        ))}
      </select>
    );
  }
}

class MatrixInput extends React.Component {
  static defaultProps = {
    matrix: new Array(16).fill(0),
    onSubmit: e => console.log(e)
  };
  state = {
    focus: false,
    valid: null
  };
  constructor(props) {
    super(props);
    this.handleFocus = this.handleFocus.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.submit = this.submit.bind(this);
  }
  handleBlur(e) {
    this.setState({ focus: false });
  }
  handleFocus(e) {
    this.setState({ focus: true });
  }
  submit(e) {
    if(e.target.value != "") {
      let matrix = this.parseInput(e.target.value);

      if (this.state.focus && validMatrix(matrix)) {
        this.props.onSubmit(matrix);
      }
      this.setState({ valid: validMatrix(matrix) });
      return;
    }
    this.setState({valid: null});
  }

  parseInput(input) {
    let parsedInput = input.replace(STRIP_BRACKETS, "");
    return parsedInput.split(",").map(v=>v=="" ? 0 : v);
  }

  render() {
    return (
      <input
        placeholder="Type / Paste Matrix Here. Comma Delimited."
        className={this.state.valid || this.state.valid == null ? "" : "invalid"}
        type="text"
        onKeyUp={this.submit}
        onFocus={this.handleFocus}
        onBlur={this.handleBlur}
      />
    );
  }
}

class MatrixEdit extends React.Component {
  static defaultProps = {
    matrix: new Array(16).fill(0),
    onChange: e => console.log(e)
  };

  state = {
    matrix: [],
  };

  selected = null;

  constructor(props) {
    super(props);

    this.handleFocus = this.handleFocus.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleMouseWheel = this.handleMouseWheel.bind(this);
    addEventListener("wheel", this.handleMouseWheel);
  }

  componentDidMount() {
    this.setState({ matrix: this.props.matrix });
  }

  componentWillReceiveProps(newProps, props) {
    if (props.matrix != newProps.matrix) {
      this.setState({ matrix: newProps.matrix });
    }
  }

  handleBlur(e) {
    if (this.selected.value === "") {
      let index = parseInt(e.target.name, 10);
      let value = this.state.matrix[index];
      this.updateValue(index, value)
    }
    this.selected = null;
  }

handleMouseWheel(e) {
  if(this.selected && !isNaN(this.state.matrix[parseInt(this.selected.name, 10)])) {
      let index = parseInt(this.selected.name, 10);
      let value = parseFloat((this.state.matrix[index]-(e.deltaY/10000)).toFixed(4),10);
      this.updateValue(index, value);
  }
}

  updateValue(index, value) {
    let newMatrix = this.setIndex(index, value == "" ? 0 : value);
    this.props.onChange(newMatrix);
  }

  handleFocus(e) {
    this.selected = e.target;
    this.selected.placeholder = this.selected.value;
    this.selected.value = "";
  }

  handleChange(e) {
    let index = parseInt(e.target.name, 10);
    this.updateValue(index, e.target.value)
  }

  setIndex(index, value) {
    let matrix = this.state.matrix.slice();
    matrix[index] = value;
    return matrix;
  }

  render() {
    let components = [];
    this.state.matrix.map((v, i) => {
      components.push(
        <input
          value={v}
          type="text"
          name={i}
          onChange={this.handleChange}
          onFocus={this.handleFocus}
          onBlur={this.handleBlur}
        />
      );
    });
    return (
      <div className={`matrix matrix-${this.state.matrix.length}`}>
        {components}
      </div>
    );
  }
}

class MatrixDisplay extends React.Component {
  static defaultProps = {
    matrix: new Array(16).fill(0)
  };

  state = {
    matrix: []
  };

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    this.setState({ matrix: this.props.matrix });
  }

  componentWillReceiveProps(newProps, props) {
    if (props.matrix != newProps.matrix) {
      this.setState({ matrix: newProps.matrix });
    }
  }

  render() {
    return (
      <div className="code">
        <pre>{renderGLSLMatrix(this.state.matrix)}</pre>
        <pre>{renderJSMatrix(this.state.matrix)}</pre>
      </div>
    );
  }
}

class App extends React.Component {
  state = {
    size: MATRICES.MAT4,
    matrix: new Array(MATSIZES[MATRICES.MAT4]).fill(0)
  };
  constructor() {
    super();
    this.changeMatrix = this.changeMatrix.bind(this);
    this.pasteMatrix = this.pasteMatrix.bind(this);
  }

  changeMatrix(newMatrix) {
    this.setState({ matrix: newMatrix });
  }

  pasteMatrix(newMatrix) {
    let size = MATLENGTH[newMatrix.length];
    this.setState({ matrix: newMatrix, size: size });
  }

  changeSize(option) {
    let size = MATSIZES[option];
    let matrix = [];
    if (size < this.state.matrix.length) {
      matrix = shrinkMat(this.state.matrix, size);
    } else {
      matrix = expandMat(this.state.matrix, size);
    }
    this.setState({ matrix: matrix, size: MATRICES[option] });
  }

  render() {
    return (
      <div className="editor">
        <div className="functions">
          <MatrixInput onSubmit={this.pasteMatrix} />
          <OptionSelect
            onChange={this.changeSize.bind(this)}
            options={Object.keys(MATRICES)}
            selected={this.state.size}
          />
        </div>
        <MatrixEdit matrix={this.state.matrix} onChange={this.changeMatrix} />

        <MatrixDisplay matrix={this.state.matrix} />
      </div>
    );
  }
}
ReactDOM.render(<App />, window.root);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/react@16/umd/react.production.min.js
  2. https://unpkg.com/react-dom@16/umd/react-dom.production.min.js