'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var React = _interopRequireWildcard(_react); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* global global */ var KEYCODE_ENTER = 13; var KEYCODE_TAB = 9; var KEYCODE_BACKSPACE = 8; var KEYCODE_Y = 89; var KEYCODE_Z = 90; var KEYCODE_M = 77; var KEYCODE_PARENS = 57; var KEYCODE_BRACKETS = 219; var KEYCODE_QUOTE = 222; var KEYCODE_BACK_QUOTE = 192; var KEYCODE_ESCAPE = 27; var HISTORY_LIMIT = 100; var HISTORY_TIME_GAP = 3000; var isWindows = 'navigator' in global && /Win/i.test(navigator.platform); var isMacLike = 'navigator' in global && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform); var className = 'npm__react-simple-code-editor__textarea'; var cssText = /* CSS */'\n/**\n * Reset the text fill color so that placeholder is visible\n */\n.' + className + ':empty {\n -webkit-text-fill-color: inherit !important;\n}\n\n/**\n * Hack to apply on some CSS on IE10 and IE11\n */\n@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {\n /**\n * IE doesn\'t support \'-webkit-text-fill-color\'\n * So we use \'color: transparent\' to make the text transparent on IE\n * Unlike other browsers, it doesn\'t affect caret color in IE\n */\n .' + className + ' {\n color: transparent !important;\n }\n\n .' + className + '::selection {\n background-color: #accef7 !important;\n color: transparent !important;\n }\n}\n'; var Editor = function (_React$Component) { _inherits(Editor, _React$Component); function Editor() { var _ref; var _temp, _this, _ret; _classCallCheck(this, Editor); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Editor.__proto__ || Object.getPrototypeOf(Editor)).call.apply(_ref, [this].concat(args))), _this), _this.state = { capture: true }, _this._recordCurrentState = function () { var input = _this._input; if (!input) return; // Save current state of the input var value = input.value, selectionStart = input.selectionStart, selectionEnd = input.selectionEnd; _this._recordChange({ value: value, selectionStart: selectionStart, selectionEnd: selectionEnd }); }, _this._getLines = function (text, position) { return text.substring(0, position).split('\n'); }, _this._recordChange = function (record) { var overwrite = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var _this$_history = _this._history, stack = _this$_history.stack, offset = _this$_history.offset; if (stack.length && offset > -1) { // When something updates, drop the redo operations _this._history.stack = stack.slice(0, offset + 1); // Limit the number of operations to 100 var count = _this._history.stack.length; if (count > HISTORY_LIMIT) { var extras = count - HISTORY_LIMIT; _this._history.stack = stack.slice(extras, count); _this._history.offset = Math.max(_this._history.offset - extras, 0); } } var timestamp = Date.now(); if (overwrite) { var last = _this._history.stack[_this._history.offset]; if (last && timestamp - last.timestamp < HISTORY_TIME_GAP) { // A previous entry exists and was in short interval // Match the last word in the line var re = /[^a-z0-9]([a-z0-9]+)$/i; // Get the previous line var previous = _this._getLines(last.value, last.selectionStart).pop().match(re); // Get the current line var current = _this._getLines(record.value, record.selectionStart).pop().match(re); if (previous && current && current[1].startsWith(previous[1])) { // The last word of the previous line and current line match // Overwrite previous entry so that undo will remove whole word _this._history.stack[_this._history.offset] = _extends({}, record, { timestamp: timestamp }); return; } } } // Add the new operation to the stack _this._history.stack.push(_extends({}, record, { timestamp: timestamp })); _this._history.offset++; }, _this._updateInput = function (record) { var input = _this._input; if (!input) return; // Update values and selection state input.value = record.value; input.selectionStart = record.selectionStart; input.selectionEnd = record.selectionEnd; _this.props.onValueChange(record.value); }, _this._applyEdits = function (record) { // Save last selection state var input = _this._input; var last = _this._history.stack[_this._history.offset]; if (last && input) { _this._history.stack[_this._history.offset] = _extends({}, last, { selectionStart: input.selectionStart, selectionEnd: input.selectionEnd }); } // Save the changes _this._recordChange(record); _this._updateInput(record); }, _this._undoEdit = function () { var _this$_history2 = _this._history, stack = _this$_history2.stack, offset = _this$_history2.offset; // Get the previous edit var record = stack[offset - 1]; if (record) { // Apply the changes and update the offset _this._updateInput(record); _this._history.offset = Math.max(offset - 1, 0); } }, _this._redoEdit = function () { var _this$_history3 = _this._history, stack = _this$_history3.stack, offset = _this$_history3.offset; // Get the next edit var record = stack[offset + 1]; if (record) { // Apply the changes and update the offset _this._updateInput(record); _this._history.offset = Math.min(offset + 1, stack.length - 1); } }, _this._handleKeyDown = function (e) { var _this$props = _this.props, tabSize = _this$props.tabSize, insertSpaces = _this$props.insertSpaces, ignoreTabKey = _this$props.ignoreTabKey, onKeyDown = _this$props.onKeyDown; if (onKeyDown) { onKeyDown(e); if (e.defaultPrevented) { return; } } if (e.keyCode === KEYCODE_ESCAPE) { e.target.blur(); } var _e$target = e.target, value = _e$target.value, selectionStart = _e$target.selectionStart, selectionEnd = _e$target.selectionEnd; var tabCharacter = (insertSpaces ? ' ' : '\t').repeat(tabSize); if (e.keyCode === KEYCODE_TAB && !ignoreTabKey && _this.state.capture) { // Prevent focus change e.preventDefault(); if (e.shiftKey) { // Unindent selected lines var linesBeforeCaret = _this._getLines(value, selectionStart); var startLine = linesBeforeCaret.length - 1; var endLine = _this._getLines(value, selectionEnd).length - 1; var nextValue = value.split('\n').map(function (line, i) { if (i >= startLine && i <= endLine && line.startsWith(tabCharacter)) { return line.substring(tabCharacter.length); } return line; }).join('\n'); if (value !== nextValue) { var startLineText = linesBeforeCaret[startLine]; _this._applyEdits({ value: nextValue, // Move the start cursor if first line in selection was modified // It was modified only if it started with a tab selectionStart: startLineText.startsWith(tabCharacter) ? selectionStart - tabCharacter.length : selectionStart, // Move the end cursor by total number of characters removed selectionEnd: selectionEnd - (value.length - nextValue.length) }); } } else if (selectionStart !== selectionEnd) { // Indent selected lines var _linesBeforeCaret = _this._getLines(value, selectionStart); var _startLine = _linesBeforeCaret.length - 1; var _endLine = _this._getLines(value, selectionEnd).length - 1; var _startLineText = _linesBeforeCaret[_startLine]; _this._applyEdits({ value: value.split('\n').map(function (line, i) { if (i >= _startLine && i <= _endLine) { return tabCharacter + line; } return line; }).join('\n'), // Move the start cursor by number of characters added in first line of selection // Don't move it if it there was no text before cursor selectionStart: /\S/.test(_startLineText) ? selectionStart + tabCharacter.length : selectionStart, // Move the end cursor by total number of characters added selectionEnd: selectionEnd + tabCharacter.length * (_endLine - _startLine + 1) }); } else { var updatedSelection = selectionStart + tabCharacter.length; _this._applyEdits({ // Insert tab character at caret value: value.substring(0, selectionStart) + tabCharacter + value.substring(selectionEnd), // Update caret position selectionStart: updatedSelection, selectionEnd: updatedSelection }); } } else if (e.keyCode === KEYCODE_BACKSPACE) { var hasSelection = selectionStart !== selectionEnd; var textBeforeCaret = value.substring(0, selectionStart); if (textBeforeCaret.endsWith(tabCharacter) && !hasSelection) { // Prevent default delete behaviour e.preventDefault(); var _updatedSelection = selectionStart - tabCharacter.length; _this._applyEdits({ // Remove tab character at caret value: value.substring(0, selectionStart - tabCharacter.length) + value.substring(selectionEnd), // Update caret position selectionStart: _updatedSelection, selectionEnd: _updatedSelection }); } } else if (e.keyCode === KEYCODE_ENTER) { // Ignore selections if (selectionStart === selectionEnd) { // Get the current line var line = _this._getLines(value, selectionStart).pop(); var matches = line.match(/^\s+/); if (matches && matches[0]) { e.preventDefault(); // Preserve indentation on inserting a new line var indent = '\n' + matches[0]; var _updatedSelection2 = selectionStart + indent.length; _this._applyEdits({ // Insert indentation character at caret value: value.substring(0, selectionStart) + indent + value.substring(selectionEnd), // Update caret position selectionStart: _updatedSelection2, selectionEnd: _updatedSelection2 }); } } } else if (e.keyCode === KEYCODE_PARENS || e.keyCode === KEYCODE_BRACKETS || e.keyCode === KEYCODE_QUOTE || e.keyCode === KEYCODE_BACK_QUOTE) { var chars = void 0; if (e.keyCode === KEYCODE_PARENS && e.shiftKey) { chars = ['(', ')']; } else if (e.keyCode === KEYCODE_BRACKETS) { if (e.shiftKey) { chars = ['{', '}']; } else { chars = ['[', ']']; } } else if (e.keyCode === KEYCODE_QUOTE) { if (e.shiftKey) { chars = ['"', '"']; } else { chars = ["'", "'"]; } } else if (e.keyCode === KEYCODE_BACK_QUOTE && !e.shiftKey) { chars = ['`', '`']; } // If text is selected, wrap them in the characters if (selectionStart !== selectionEnd && chars) { e.preventDefault(); _this._applyEdits({ value: value.substring(0, selectionStart) + chars[0] + value.substring(selectionStart, selectionEnd) + chars[1] + value.substring(selectionEnd), // Update caret position selectionStart: selectionStart, selectionEnd: selectionEnd + 2 }); } } else if ((isMacLike ? // Trigger undo with ⌘+Z on Mac e.metaKey && e.keyCode === KEYCODE_Z : // Trigger undo with Ctrl+Z on other platforms e.ctrlKey && e.keyCode === KEYCODE_Z) && !e.shiftKey && !e.altKey) { e.preventDefault(); _this._undoEdit(); } else if ((isMacLike ? // Trigger redo with ⌘+Shift+Z on Mac e.metaKey && e.keyCode === KEYCODE_Z && e.shiftKey : isWindows ? // Trigger redo with Ctrl+Y on Windows e.ctrlKey && e.keyCode === KEYCODE_Y : // Trigger redo with Ctrl+Shift+Z on other platforms e.ctrlKey && e.keyCode === KEYCODE_Z && e.shiftKey) && !e.altKey) { e.preventDefault(); _this._redoEdit(); } else if (e.keyCode === KEYCODE_M && e.ctrlKey && (isMacLike ? e.shiftKey : true)) { e.preventDefault(); // Toggle capturing tab key so users can focus away _this.setState(function (state) { return { capture: !state.capture }; }); } }, _this._handleChange = function (e) { var _e$target2 = e.target, value = _e$target2.value, selectionStart = _e$target2.selectionStart, selectionEnd = _e$target2.selectionEnd; _this._recordChange({ value: value, selectionStart: selectionStart, selectionEnd: selectionEnd }, true); _this.props.onValueChange(value); }, _this._history = { stack: [], offset: -1 }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(Editor, [{ key: 'componentDidMount', value: function componentDidMount() { this._recordCurrentState(); } }, { key: 'render', value: function render() { var _this2 = this; var _props = this.props, value = _props.value, style = _props.style, padding = _props.padding, highlight = _props.highlight, textareaId = _props.textareaId, autoFocus = _props.autoFocus, disabled = _props.disabled, form = _props.form, maxLength = _props.maxLength, minLength = _props.minLength, name = _props.name, placeholder = _props.placeholder, readOnly = _props.readOnly, required = _props.required, onClick = _props.onClick, onFocus = _props.onFocus, onBlur = _props.onBlur, onKeyUp = _props.onKeyUp, onKeyDown = _props.onKeyDown, onValueChange = _props.onValueChange, tabSize = _props.tabSize, insertSpaces = _props.insertSpaces, ignoreTabKey = _props.ignoreTabKey, rest = _objectWithoutProperties(_props, ['value', 'style', 'padding', 'highlight', 'textareaId', 'autoFocus', 'disabled', 'form', 'maxLength', 'minLength', 'name', 'placeholder', 'readOnly', 'required', 'onClick', 'onFocus', 'onBlur', 'onKeyUp', 'onKeyDown', 'onValueChange', 'tabSize', 'insertSpaces', 'ignoreTabKey']); var contentStyle = { paddingTop: padding, paddingRight: padding, paddingBottom: padding, paddingLeft: padding }; var highlighted = highlight(value); return React.createElement( 'div', _extends({}, rest, { style: _extends({}, styles.container, style) }), React.createElement('textarea', { ref: function ref(c) { return _this2._input = c; }, style: _extends({}, styles.editor, styles.textarea, contentStyle), className: className, id: textareaId, value: value, onChange: this._handleChange, onKeyDown: this._handleKeyDown, onClick: onClick, onKeyUp: onKeyUp, onFocus: onFocus, onBlur: onBlur, disabled: disabled, form: form, maxLength: maxLength, minLength: minLength, name: name, placeholder: placeholder, readOnly: readOnly, required: required, autoFocus: autoFocus, autoCapitalize: 'off', autoComplete: 'off', autoCorrect: 'off', spellCheck: false, 'data-gramm': false }), React.createElement('pre', _extends({ 'aria-hidden': 'true', style: _extends({}, styles.editor, styles.highlight, contentStyle) }, typeof highlighted === 'string' ? { dangerouslySetInnerHTML: { __html: highlighted + '
' } } : { children: highlighted })), React.createElement('style', { type: 'text/css', dangerouslySetInnerHTML: { __html: cssText } }) ); } }, { key: 'session', get: function get() { return { history: this._history }; }, set: function set(session) { this._history = session.history; } }]); return Editor; }(React.Component); Editor.defaultProps = { tabSize: 2, insertSpaces: true, ignoreTabKey: false, padding: 0 }; exports.default = Editor; var styles = { container: { position: 'relative', textAlign: 'left', boxSizing: 'border-box', padding: 0, overflow: 'hidden' }, textarea: { position: 'absolute', top: 0, left: 0, height: '100%', width: '100%', resize: 'none', color: 'inherit', overflow: 'hidden', MozOsxFontSmoothing: 'grayscale', WebkitFontSmoothing: 'antialiased', WebkitTextFillColor: 'transparent' }, highlight: { position: 'relative', pointerEvents: 'none' }, editor: { margin: 0, border: 0, background: 'none', boxSizing: 'inherit', display: 'inherit', fontFamily: 'inherit', fontSize: 'inherit', fontStyle: 'inherit', fontVariantLigatures: 'inherit', fontWeight: 'inherit', letterSpacing: 'inherit', lineHeight: 'inherit', tabSize: 'inherit', textIndent: 'inherit', textRendering: 'inherit', textTransform: 'inherit', whiteSpace: 'pre-wrap', wordBreak: 'keep-all', overflowWrap: 'break-word' } }; //# sourceMappingURL=index.js.map