<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;
	}
}
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://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js