From 554c144d3fec9ae52b9e596ff7ccb682f5baf9ed Mon Sep 17 00:00:00 2001 From: Anton Fisher Date: Sun, 10 Sep 2017 01:19:15 -0700 Subject: [PATCH] feat: add custom colon property (closes #1) --- README.md | 12 ++++-------- demo/index.js | 30 +++++++++++++++++++++++++----- demo/package.json | 2 +- docs/bundle.js | 4 ++-- docs/index.html | 2 +- package.json | 4 ++-- src/index.js | 45 ++++++++++++++++++++++++++++++--------------- tests/unit.test.js | 11 +++++++++-- 8 files changed, 74 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 85de07f..02f353f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ import TimeField from 'react-simple-timefield'; value={time} // {String} required, format '00:00' or '00:00:00' onChange={(value) => {...}} // {Function} required input={} // {Element} default: + colon=":" // {String} default: ":" showSeconds // {Boolean} default: false /> ``` @@ -48,20 +49,14 @@ class App extends React.Component { const {time} = this.state; return ( -
- - -
+ ); } } ``` ## Changelog +* 1.3.0 Added custom colon property * 1.2.0 Added custom input field property * 1.1.0 Added `showSeconds` property * 1.0.0 Initial release @@ -87,6 +82,7 @@ npm run lint - [x] Support full time format with seconds - [x] Tests - [x] Custom input field (like Material UI TextField) +- [x] Custom colon - [ ] Support for Date object as value ## License diff --git a/demo/index.js b/demo/index.js index a9efe19..38f5f44 100644 --- a/demo/index.js +++ b/demo/index.js @@ -16,21 +16,24 @@ class App extends React.Component { this.state = { time: '12:34', - timeSeconds: '12:34:56' + timeSeconds: '12:34:56', + timeSecondsCustomColon: '12-34-56' }; this.onTimeChange = this.onTimeChange.bind(this); } - onTimeChange(newTime) { + onTimeChange(value) { + const newTime = value.replace(/-/g, ':'); const time = newTime.substr(0, 5); const timeSeconds = newTime.padEnd(8, this.state.timeSeconds.substr(5, 3)); + const timeSecondsCustomColon = timeSeconds.replace(/:/g, '-'); - this.setState({time, timeSeconds}); + this.setState({time, timeSeconds, timeSecondsCustomColon}); } render() { - const {time, timeSeconds} = this.state; + const {time, timeSeconds, timeSecondsCustomColon} = this.state; const muiTheme = getMuiTheme({ fontFamily: 'Arial', @@ -75,10 +78,27 @@ class App extends React.Component { }} /> +

Custom colon:

+
+ +

React Material-UI:

-
+
0&&r.length<20?n+" (keys: "+r.join(", ")+")":n}function i(e,t){var n=u.get(e);return n||null}var a=n(7),u=(n(41),n(88)),s=(n(30),n(37)),c=(n(1),n(6),{isMounted:function(e){var t=u.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){c.validateCallback(t,n);var o=i(e);if(!o)return null;o._pendingCallbacks?o._pendingCallbacks.push(t):o._pendingCallbacks=[t],r(o)},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],r(e)},enqueueForceUpdate:function(e){var t=i(e,"forceUpdate");t&&(t._pendingForceUpdate=!0,r(t))},enqueueReplaceState:function(e,t,n){var o=i(e,"replaceState");o&&(o._pendingStateQueue=[t],o._pendingReplaceState=!0,void 0!==n&&null!==n&&(c.validateCallback(n,"replaceState"),o._pendingCallbacks?o._pendingCallbacks.push(n):o._pendingCallbacks=[n]),r(o))},enqueueSetState:function(e,t){var n=i(e,"setState");n&&((n._pendingStateQueue||(n._pendingStateQueue=[])).push(t),r(n))},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,r(e)},validateCallback:function(e,t){e&&"function"!=typeof e&&a("122",t,o(e))}});e.exports=c},function(e,t,n){"use strict";var r=(n(9),n(25)),o=(n(6),r);e.exports=o},function(e,t,n){"use strict";function r(e){var t,n=e.keyCode;return"charCode"in e?0===(t=e.charCode)&&13===n&&(t=13):t=n,t>=32||13===t?t:0}e.exports=r},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t){e.exports=!0},function(e,t,n){var r=n(585);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){var r=n(90);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){var r=n(74),o=n(587),i=n(181),a=n(179)("IE_PROTO"),u=function(){},s=function(){var e,t=n(261)("iframe"),r=i.length;for(t.style.display="none",n(590).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(" \ No newline at end of file +react-simple-timefield - Demos

react-simple-timefield demos

Demo source code
\ No newline at end of file diff --git a/package.json b/package.json index dbcde8d..fb34f5d 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "react-simple-timefield", - "version": "1.2.0", + "version": "1.3.0", "description": "Simple React time input field", "author": "Anton Fisher (http://antonfisher.com)", "license": "MIT", "main": "index.js", "scripts": { - "build": "babel -d dist src", + "build": "babel -d dist src && cd ./demo && npm run build", "test": "jest", "lint": "eslint --ignore-path .gitignore --ignore-pattern node_modules --ignore-pattern docs -- .", "cover": "jest --coverage", diff --git a/src/index.js b/src/index.js index 4246b73..fea7e7d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -const DEFAULT_VALUE_SHORT = '00:00'; -const DEFAULT_VALUE_FULL = '00:00:00'; +const DEFAULT_COLON = ':'; +const DEFAULT_VALUE_SHORT = `00${DEFAULT_COLON}00`; +const DEFAULT_VALUE_FULL = `00${DEFAULT_COLON}00${DEFAULT_COLON}00`; export function isNumber(value) { const number = Number(value); @@ -13,11 +14,11 @@ export function formatTimeItem(value) { return (`${value || ''}00`).substr(0, 2); } -export function validateTimeAndCursor(showSeconds, value, defaultValue, cursorPosition) { - const [oldH, oldM, oldS] = defaultValue.split(':'); +export function validateTimeAndCursor(showSeconds, value, defaultValue, colon = DEFAULT_COLON, cursorPosition = 0) { + const [oldH, oldM, oldS] = defaultValue.split(colon); let newCursorPosition = Number(cursorPosition); - let [newH, newM, newS] = value.split(':'); + let [newH, newM, newS] = value.split(colon); newH = formatTimeItem(newH); if (Number(newH[0]) > 2) { @@ -46,7 +47,7 @@ export function validateTimeAndCursor(showSeconds, value, defaultValue, cursorPo } } - const validatedValue = (showSeconds ? `${newH}:${newM}:${newS}` : `${newH}:${newM}`); + const validatedValue = (showSeconds ? `${newH}${colon}${newM}${colon}${newS}` : `${newH}${colon}${newM}`); return [validatedValue, newCursorPosition]; } @@ -57,13 +58,15 @@ export default class TimeField extends React.Component { onChange: PropTypes.func.isRequired, showSeconds: PropTypes.bool, input: PropTypes.element, + colon: PropTypes.string, style: PropTypes.object }; static defaultProps = { showSeconds: false, input: null, - style: {} + style: {}, + colon: DEFAULT_COLON }; constructor(props, ...args) { @@ -71,7 +74,12 @@ export default class TimeField extends React.Component { this.configure(props); - const [validatedTime] = validateTimeAndCursor(this._showSeconds, this.props.value, this._defaultValue); + const [validatedTime] = validateTimeAndCursor( + this._showSeconds, + this.props.value, + this._defaultValue, + this._colon + ); this.state = { value: validatedTime @@ -86,7 +94,12 @@ export default class TimeField extends React.Component { this.configure(nextProps); if (value !== nextProps.value) { - const [validatedTime] = validateTimeAndCursor(this._showSeconds, nextProps.value, this._defaultValue); + const [validatedTime] = validateTimeAndCursor( + this._showSeconds, + nextProps.value, + this._defaultValue, + this._colon + ); this.setState({ value: validatedTime }); @@ -94,6 +107,7 @@ export default class TimeField extends React.Component { } configure(props) { + this._colon = (props.colon && props.colon.length === 1 ? props.colon : DEFAULT_COLON); this._showSeconds = Boolean(props.showSeconds); this._defaultValue = (this._showSeconds ? DEFAULT_VALUE_FULL : DEFAULT_VALUE_SHORT); this._maxLength = this._defaultValue.length; @@ -108,6 +122,7 @@ export default class TimeField extends React.Component { const isType = (inputValue.length > oldValue.length); const addedCharacter = (isType ? inputValue[position - 1] : null); const removedCharacter = (isType ? null : oldValue[position]); + const colon = this._colon; let newValue = oldValue; let newPosition = position; @@ -115,10 +130,10 @@ export default class TimeField extends React.Component { if (addedCharacter !== null) { if (position > this._maxLength) { newPosition = this._maxLength; - } else if ((position === 3 || position === 6) && addedCharacter === ':') { - newValue = `${inputValue.substr(0, position - 1)}:${inputValue.substr(position + 1)}`; + } else if ((position === 3 || position === 6) && addedCharacter === colon) { + newValue = `${inputValue.substr(0, position - 1)}${colon}${inputValue.substr(position + 1)}`; } else if ((position === 3 || position === 6) && isNumber(addedCharacter)) { - newValue = `${inputValue.substr(0, position - 1)}:${addedCharacter}${inputValue.substr(position + 2)}`; + newValue = `${inputValue.substr(0, position - 1)}${colon}${addedCharacter}${inputValue.substr(position + 2)}`; newPosition = (position + 1); } else if (isNumber(addedCharacter)) { // user typed a number newValue = (inputValue.substr(0, position - 1) + addedCharacter + inputValue.substr(position + 1)); @@ -129,8 +144,8 @@ export default class TimeField extends React.Component { newPosition = (position - 1); } } else if (removedCharacter !== null) { - if ((position === 2 || position === 5) && removedCharacter === ':') { - newValue = `${inputValue.substr(0, position - 1)}0:${inputValue.substr(position)}`; + if ((position === 2 || position === 5) && removedCharacter === colon) { + newValue = `${inputValue.substr(0, position - 1)}0${colon}${inputValue.substr(position)}`; newPosition = position - 1; } else { // user removed a number newValue = `${inputValue.substr(0, position)}0${inputValue.substr(position)}`; @@ -140,7 +155,7 @@ export default class TimeField extends React.Component { const [ validatedTime, validatedCursorPosition - ] = validateTimeAndCursor(this._showSeconds, newValue, oldValue, newPosition); + ] = validateTimeAndCursor(this._showSeconds, newValue, oldValue, this._colon, newPosition); this.setState({value: validatedTime}, () => { inputEl.selectionStart = validatedCursorPosition; diff --git a/tests/unit.test.js b/tests/unit.test.js index 372f6ff..29fc4de 100644 --- a/tests/unit.test.js +++ b/tests/unit.test.js @@ -32,19 +32,26 @@ describe('#validateTimeAndCursor()', () => { const DF = '00:00:00'; test('should return an array', () => { - const res = validateTimeAndCursor(true, '', DF, 0); + const res = validateTimeAndCursor(true, '', DF, ':', 0); expect(res).toBeInstanceOf(Array); expect(res).toHaveLength(2); expect(res).toEqual([DF, 0]); }); - test('should handle showSeconds option', () => { + test('should handle "showSeconds" option', () => { expect(validateTimeAndCursor(true, '12:34:56', DF)[0]).toEqual('12:34:56'); expect(validateTimeAndCursor(true, '12:34', DF)[0]).toEqual('12:34:00'); expect(validateTimeAndCursor(false, '12:34:56', DF)[0]).toEqual('12:34'); expect(validateTimeAndCursor(false, '12:34', DF)[0]).toEqual('12:34'); }); + test('should handle "colon" option', () => { + expect(validateTimeAndCursor(true, '12-34-56', DF, '-')[0]).toEqual('12-34-56'); + expect(validateTimeAndCursor(true, '12-34', DF, '-')[0]).toEqual('12-34-00'); + expect(validateTimeAndCursor(false, '12-34-56', DF, '-')[0]).toEqual('12-34'); + expect(validateTimeAndCursor(false, '12-34', DF, '-')[0]).toEqual('12-34'); + }); + test('should return default value if bad format of hours', () => { expect(validateTimeAndCursor(false, '30:00', DF)[0]).toEqual('00:00'); expect(validateTimeAndCursor(false, ':', DF)[0]).toEqual('00:00');