2 * Copyright 2003-present Greg Hurrell. All rights reserved.
3 * Licensed under the terms of the MIT license.
8 import DIGITS from './DIGITS';
9 import PropTypes from 'prop-types';
10 import React from 'react';
11 import convert from './convert';
12 import cx from 'classnames';
18 export const ValuePropType = PropTypes.shape({
19 base: PropTypes.number,
20 value: PropTypes.string,
24 * Convert from `value` to `base`.
26 function fromValue(value: ?Value, base: number): string {
29 } else if (value.base === base) {
32 return convert(value.value, value.base, base);
35 function getValidator(base) {
36 const prefix = base === 16 ? '(?:0x)?' : '';
37 const regexp = new RegExp(
38 `^\\s*${prefix}[${DIGITS.slice(0, base)}]*\\s*$`,
41 return value => regexp.test(value);
44 export default class Field extends React.Component {
46 base: PropTypes.number,
47 onValueChange: PropTypes.func.isRequired,
50 static defaultProps = {
56 if (props.base < 2 || props.base > DIGITS.length) {
58 `base prop must be between 2..${DIGITS.length}`
61 this._validate = getValidator(props.base);
62 const value = fromValue(props.value, props.base);
65 selectionEnd: value.length,
66 selectionStart: value.length,
71 componentDidUpdate(prevProps, prevState) {
72 const {selectionStart, selectionEnd} = this.state;
73 this._input.setSelectionRange(selectionStart, selectionEnd);
76 componentWillReceiveProps(nextProps) {
77 if (nextProps.base !== this.props.base) {
78 this._validate = getValidator(nextProps.base);
80 this.setState({value: fromValue(nextProps.value, nextProps.base)});
83 _onChange = event => {
84 const value = event.currentTarget.value;
85 if (this._validate(value)) {
86 const {selectionEnd, selectionStart} = event.currentTarget;
87 this.setState({selectionEnd, selectionStart});
88 this.props.onValueChange({
89 base: this.props.base,
94 selectionEnd: this._selectionEnd,
95 selectionStart: this._selectionStart,
100 _onSelect = event => {
101 // Remember selection to stop React moving cursor to end:
102 // https://github.com/facebook/react/issues/955
103 const {selectionEnd, selectionStart} = event.currentTarget;
104 this._selectionStart = selectionStart;
105 this._selectionEnd = selectionEnd;
109 this._input.select();
111 // May throw a SecurityError.
114 copySucceeded: document.execCommand('copy'),
116 setTimeout(() => this.setState({copySucceeded: false}), 750);
118 this.setState({copySucceeded: false});
123 // Would check `document.queryCommandSupported('copy')` here, but that
124 // doesn't work; see:
125 // - https://code.google.com/p/chromium/issues/detail?id=476508
126 // - https://github.com/w3c/clipboard-apis/issues/4
129 className="hextrapolate-copy"
130 onClick={this._onCopy}
131 title="Copy to Clipboard">
138 const classNames = cx({
139 'hextrapolate-copy-status': true,
140 'hextrapolate-copy-success': this.state.copySucceeded,
142 return <span className={classNames}>✓</span>;
151 <span className="hextrapolate-field">
153 onChange={this._onChange}
154 onSelect={this._onSelect}
155 ref={ref => this._input = ref}
158 value={this.state.value}