From f380561f2ccebe8f992b538bb5500afc35e9afc9 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 19:16:57 +0300 Subject: [PATCH 1/9] Generalisation of lowercase etc enforcements --- .size-snapshot.json | 30 +++++++++++----------- pages/case-enforcement/index.js | 26 ++++++++++++++++--- src/Rifm.js | 45 ++++++++++++++++++++++++++------- tests/RifmFormat.test.js | 12 +++++++++ tests/utils/exec.js | 2 ++ 5 files changed, 88 insertions(+), 27 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index b6d14a3..2713abf 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,23 +1,23 @@ { "dist/rifm.umd.js": { - "bundled": 7480, - "minified": 1724, - "gzipped": 858 + "bundled": 8089, + "minified": 2023, + "gzipped": 987 }, "dist/rifm.min.js": { - "bundled": 7266, - "minified": 1583, - "gzipped": 780 + "bundled": 7216, + "minified": 1570, + "gzipped": 779 }, "dist/rifm.cjs.js": { - "bundled": 7008, - "minified": 1650, - "gzipped": 831 + "bundled": 7794, + "minified": 2077, + "gzipped": 975 }, "dist/rifm.esm.js": { - "bundled": 6937, - "minified": 1587, - "gzipped": 787, + "bundled": 7723, + "minified": 2014, + "gzipped": 933, "treeshaked": { "rollup": { "code": 14, @@ -29,9 +29,9 @@ } }, "dist/rifm.esm.production.js": { - "bundled": 6651, - "minified": 1372, - "gzipped": 664, + "bundled": 6597, + "minified": 1359, + "gzipped": 663, "treeshaked": { "rollup": { "code": 14, diff --git a/pages/case-enforcement/index.js b/pages/case-enforcement/index.js index 04bbae3..2605187 100644 --- a/pages/case-enforcement/index.js +++ b/pages/case-enforcement/index.js @@ -16,7 +16,8 @@ const Example = () /*:React.Node*/ => {
Lower case
v.toLowerCase()} + format={v => v} + replace={v => v.toLowerCase()} value={lowercase} onChange={setLowercase} > @@ -28,7 +29,8 @@ const Example = () /*:React.Node*/ => {
Upper case
v.toUpperCase()} + format={v => v} + replace={v => v.toUpperCase()} value={uppercase} onChange={setUppercase} > @@ -40,7 +42,8 @@ const Example = () /*:React.Node*/ => {
Capital first letter
v.slice(0, 1).toUpperCase() + v.slice(1).toLowerCase()} + format={v => v} + replace={v => v.slice(0, 1).toUpperCase() + v.slice(1).toLowerCase()} value={capitalized} onChange={setCapitalized} > @@ -59,6 +62,23 @@ const Example = () /*:React.Node*/ => { {renderInput} + +
+
Leave a comment about Rifm
+ v} + replace={v => + 'Rifm is the best mask and formatting library. I love it! ' + .repeat(20) + .slice(0, v.length) + } + value={capitalized} + onChange={setCapitalized} + > + {renderInput} + +
); }; diff --git a/src/Rifm.js b/src/Rifm.js index 37ebccc..2495544 100644 --- a/src/Rifm.js +++ b/src/Rifm.js @@ -7,6 +7,7 @@ type Props = {| onChange: string => void, format: (str: string) => string, mask?: boolean, + replace?: string => string, accept?: RegExp, children: ({ value: string, @@ -46,6 +47,19 @@ export const Rifm = (props: Props) => { userValue === props.format(eventValue), // isNoOperation ]; + if (process.env.NODE_ENV !== 'production') { + const formattedEventValue = props.format(eventValue); + if ( + eventValue !== formattedEventValue && + eventValue.toLowerCase() === formattedEventValue.toLowerCase() + ) { + console.warn(` + Case enforcement does not work with format. + Please use replace={value => value.toLowerCase()} instead + `); + } + } + // The main trick is to update underlying input with non formatted value (= eventValue) // that allows us to calculate right cursor position after formatting (see getCursorPosition) // then we format new value and call props.onChange with masked/formatted value @@ -78,7 +92,7 @@ export const Rifm = (props: Props) => { const valueBeforeSelectionStart = clean( eventValue.substr(0, input.selectionStart) - ).toLowerCase(); + ); // trying to find cursor position in formatted value having knowledge about valueBeforeSelectionStart // This works because we assume that format doesn't change the order of accepted symbols. @@ -87,20 +101,16 @@ export const Rifm = (props: Props) => { // inputValue = 1'23'|4 so valueBeforeSelectionStart = 123 and formatted value = 1'2'3'4 // calling getCursorPosition("1'2'3'4") will give us position after 3, 1'2'3|'4 // so for formatting just this function to determine cursor position after formatting is enough - // with masking we need to do some additional checks see `replace` below + // with masking we need to do some additional checks see `mask` below const getCursorPosition = val => { let start = 0; let cleanPos = 0; for (let i = 0; i !== valueBeforeSelectionStart.length; ++i) { - let newPos = - val.toLowerCase().indexOf(valueBeforeSelectionStart[i], start) + 1; + let newPos = val.indexOf(valueBeforeSelectionStart[i], start) + 1; let newCleanPos = - clean(val.toLowerCase()).indexOf( - valueBeforeSelectionStart[i], - cleanPos - ) + 1; + clean(val).indexOf(valueBeforeSelectionStart[i], cleanPos) + 1; // this skips position change if accepted symbols order was broken // For example fixes edge case with fixed point numbers: @@ -137,7 +147,24 @@ export const Rifm = (props: Props) => { // if nothing changed for formatted value, just refresh so userValue will be used at render refresh(); } else { - props.onChange(formattedValue); + if (process.env.NODE_ENV !== 'production') { + const replaceValue = + props.replace != null + ? props.replace(formattedValue) + : formattedValue; + + if (replaceValue.length !== formattedValue.length) { + console.warn('replace operation to work, must preserve length'); + } + + props.onChange(replaceValue); + } else { + props.onChange( + props.replace != null + ? props.replace(formattedValue) + : formattedValue + ); + } } return () => { diff --git a/tests/RifmFormat.test.js b/tests/RifmFormat.test.js index bce9cf9..716f406 100644 --- a/tests/RifmFormat.test.js +++ b/tests/RifmFormat.test.js @@ -132,3 +132,15 @@ test('format works even if state is not updated on equal vals', async () => { exec({ type: 'PUT_SYMBOL', payload: 'x' }).toMatchInlineSnapshot(`"123|’456"`); exec({ type: 'PUT_SYMBOL', payload: 'x' }).toMatchInlineSnapshot(`"123|’456"`); }); + +it('format can work with case changes', () => { + const exec = createExec({ + format: v => v, + replace: v => v.toLowerCase(), + accept: /.+/g, + }); + + exec({ type: 'PUT_SYMBOL', payload: 'HELLO WORLD' }).toMatchInlineSnapshot(`"hello world|"`); + exec({ type: 'MOVE_CARET', payload: -5 }).toMatchInlineSnapshot(`"hello |world"`); + exec({ type: 'PUT_SYMBOL', payload: 'BeAuTiFuL ' }).toMatchInlineSnapshot(`"hello beautiful |world"`); +}); diff --git a/tests/utils/exec.js b/tests/utils/exec.js index 4a18ee9..b1ac73b 100644 --- a/tests/utils/exec.js +++ b/tests/utils/exec.js @@ -14,6 +14,7 @@ type Props = {| // replace?: string => boolean, mask?: boolean, format: (str: string) => string, + replace?: (str: string) => string, maskFn?: string => boolean, |}; @@ -33,6 +34,7 @@ export const createExec = (props: Props) => { onChange={input.set} accept={props.accept} format={props.format} + replace={props.replace} mask={ props.mask != null ? props.mask From 635ca7bba617afc2ed5a3613f9ebb10a180b6ca8 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 19:35:23 +0300 Subject: [PATCH 2/9] Add own state 2 comment example --- pages/case-enforcement/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pages/case-enforcement/index.js b/pages/case-enforcement/index.js index 2605187..2381036 100644 --- a/pages/case-enforcement/index.js +++ b/pages/case-enforcement/index.js @@ -9,6 +9,7 @@ const Example = () /*:React.Node*/ => { const [uppercase, setUppercase] = React.useState(''); const [capitalized, setCapitalized] = React.useState(''); const [latinLetters, setLatinLetters] = React.useState(''); + const [comment, setComment] = React.useState(''); return ( @@ -73,8 +74,8 @@ const Example = () /*:React.Node*/ => { .repeat(20) .slice(0, v.length) } - value={capitalized} - onChange={setCapitalized} + value={comment} + onChange={setComment} > {renderInput}
From 97f3df552039c0e73877a72e057cd421e5066cd6 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 19:36:15 +0300 Subject: [PATCH 3/9] Fix comments --- src/Rifm.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Rifm.js b/src/Rifm.js index 2495544..93b218e 100644 --- a/src/Rifm.js +++ b/src/Rifm.js @@ -53,10 +53,9 @@ export const Rifm = (props: Props) => { eventValue !== formattedEventValue && eventValue.toLowerCase() === formattedEventValue.toLowerCase() ) { - console.warn(` - Case enforcement does not work with format. - Please use replace={value => value.toLowerCase()} instead - `); + console.warn( + 'Case enforcement does not work with format. Please use replace={value => value.toLowerCase()} instead' + ); } } @@ -154,7 +153,7 @@ export const Rifm = (props: Props) => { : formattedValue; if (replaceValue.length !== formattedValue.length) { - console.warn('replace operation to work, must preserve length'); + console.warn('replace must preserve length'); } props.onChange(replaceValue); From dcebeadad84a8a6ae4d32886bcfdadac96ab9a86 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 19:49:39 +0300 Subject: [PATCH 4/9] update snapshot --- .size-snapshot.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index 2713abf..f0dea3c 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,8 +1,8 @@ { "dist/rifm.umd.js": { - "bundled": 8089, - "minified": 2023, - "gzipped": 987 + "bundled": 8037, + "minified": 1971, + "gzipped": 967 }, "dist/rifm.min.js": { "bundled": 7216, @@ -10,14 +10,14 @@ "gzipped": 779 }, "dist/rifm.cjs.js": { - "bundled": 7794, - "minified": 2077, - "gzipped": 975 + "bundled": 7742, + "minified": 2025, + "gzipped": 957 }, "dist/rifm.esm.js": { - "bundled": 7723, - "minified": 2014, - "gzipped": 933, + "bundled": 7671, + "minified": 1962, + "gzipped": 914, "treeshaked": { "rollup": { "code": 14, From d7b823a8d5fca7b4bf709a3608723de420a404fd Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 19:53:22 +0300 Subject: [PATCH 5/9] -1 byte --- .size-snapshot.json | 30 +++++++++++++++--------------- src/Rifm.js | 11 ++++------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index f0dea3c..4d7265d 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,23 +1,23 @@ { "dist/rifm.umd.js": { - "bundled": 8037, - "minified": 1971, - "gzipped": 967 + "bundled": 8029, + "minified": 1965, + "gzipped": 965 }, "dist/rifm.min.js": { - "bundled": 7216, - "minified": 1570, - "gzipped": 779 + "bundled": 7208, + "minified": 1564, + "gzipped": 777 }, "dist/rifm.cjs.js": { - "bundled": 7742, - "minified": 2025, - "gzipped": 957 + "bundled": 7726, + "minified": 2013, + "gzipped": 955 }, "dist/rifm.esm.js": { - "bundled": 7671, - "minified": 1962, - "gzipped": 914, + "bundled": 7655, + "minified": 1950, + "gzipped": 912, "treeshaked": { "rollup": { "code": 14, @@ -29,9 +29,9 @@ } }, "dist/rifm.esm.production.js": { - "bundled": 6597, - "minified": 1359, - "gzipped": 663, + "bundled": 6589, + "minified": 1353, + "gzipped": 662, "treeshaked": { "rollup": { "code": 14, diff --git a/src/Rifm.js b/src/Rifm.js index 93b218e..e7c0bec 100644 --- a/src/Rifm.js +++ b/src/Rifm.js @@ -147,10 +147,9 @@ export const Rifm = (props: Props) => { refresh(); } else { if (process.env.NODE_ENV !== 'production') { - const replaceValue = - props.replace != null - ? props.replace(formattedValue) - : formattedValue; + const replaceValue = props.replace + ? props.replace(formattedValue) + : formattedValue; if (replaceValue.length !== formattedValue.length) { console.warn('replace must preserve length'); @@ -159,9 +158,7 @@ export const Rifm = (props: Props) => { props.onChange(replaceValue); } else { props.onChange( - props.replace != null - ? props.replace(formattedValue) - : formattedValue + props.replace ? props.replace(formattedValue) : formattedValue ); } } From ff2c86367a840726dd89bcf54cf33be3213b3f67 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 20:09:29 +0300 Subject: [PATCH 6/9] replace initial val --- .size-snapshot.json | 30 +++++++++++++++--------------- src/Rifm.js | 13 +++++++------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index 4d7265d..a3c2170 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,23 +1,23 @@ { "dist/rifm.umd.js": { - "bundled": 8029, - "minified": 1965, - "gzipped": 965 + "bundled": 8097, + "minified": 1984, + "gzipped": 973 }, "dist/rifm.min.js": { - "bundled": 7208, - "minified": 1564, - "gzipped": 777 + "bundled": 7276, + "minified": 1583, + "gzipped": 783 }, "dist/rifm.cjs.js": { - "bundled": 7726, - "minified": 2013, - "gzipped": 955 + "bundled": 7780, + "minified": 2016, + "gzipped": 961 }, "dist/rifm.esm.js": { - "bundled": 7655, - "minified": 1950, - "gzipped": 912, + "bundled": 7709, + "minified": 1953, + "gzipped": 917, "treeshaked": { "rollup": { "code": 14, @@ -29,9 +29,9 @@ } }, "dist/rifm.esm.production.js": { - "bundled": 6589, - "minified": 1353, - "gzipped": 662, + "bundled": 6655, + "minified": 1372, + "gzipped": 667, "treeshaked": { "rollup": { "code": 14, diff --git a/src/Rifm.js b/src/Rifm.js index e7c0bec..b4ebd13 100644 --- a/src/Rifm.js +++ b/src/Rifm.js @@ -20,7 +20,10 @@ type Props = {| export const Rifm = (props: Props) => { const [, refresh] = React.useReducer(c => c + 1, 0); const valueRef = React.useRef(null); - const userValue = props.format(props.value); + const { replace } = props; + const userValue = replace + ? replace(props.format(props.value)) + : props.format(props.value); // state of delete button see comments below about inputType support const isDeleleteButtonDownRef = React.useRef(false); @@ -147,8 +150,8 @@ export const Rifm = (props: Props) => { refresh(); } else { if (process.env.NODE_ENV !== 'production') { - const replaceValue = props.replace - ? props.replace(formattedValue) + const replaceValue = replace + ? replace(formattedValue) : formattedValue; if (replaceValue.length !== formattedValue.length) { @@ -157,9 +160,7 @@ export const Rifm = (props: Props) => { props.onChange(replaceValue); } else { - props.onChange( - props.replace ? props.replace(formattedValue) : formattedValue - ); + props.onChange(replace ? replace(formattedValue) : formattedValue); } } From 55d66be1b7a9e4e3eed89183884c7b25b0159c3b Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 20:19:56 +0300 Subject: [PATCH 7/9] Add test for input value replace --- tests/RifmFormat.test.js | 11 +++++++++++ tests/utils/exec.js | 28 +++++++++++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/tests/RifmFormat.test.js b/tests/RifmFormat.test.js index 716f406..45bf111 100644 --- a/tests/RifmFormat.test.js +++ b/tests/RifmFormat.test.js @@ -144,3 +144,14 @@ it('format can work with case changes', () => { exec({ type: 'MOVE_CARET', payload: -5 }).toMatchInlineSnapshot(`"hello |world"`); exec({ type: 'PUT_SYMBOL', payload: 'BeAuTiFuL ' }).toMatchInlineSnapshot(`"hello beautiful |world"`); }); + +it('replace is applied to input value', () => { + const exec = createExec({ + format: v => v, + replace: v => v.toLowerCase(), + accept: /.+/g, + initialValue: 'HeLLo', + }); + + exec({ type: 'MOVE_CARET', payload: -5 }).toMatchInlineSnapshot(`"|hello"`); +}); diff --git a/tests/utils/exec.js b/tests/utils/exec.js index b1ac73b..9affdb4 100644 --- a/tests/utils/exec.js +++ b/tests/utils/exec.js @@ -16,6 +16,7 @@ type Props = {| format: (str: string) => string, replace?: (str: string) => string, maskFn?: string => boolean, + initialValue?: string, |}; export const createExec = (props: Props) => { @@ -24,7 +25,7 @@ export const createExec = (props: Props) => { let stateValue_ = null; TestRenderer.create( - + {input => { stateValue_ = input.value; @@ -43,15 +44,17 @@ export const createExec = (props: Props) => { : undefined } > - {({ value, onChange }) => ( - - {(exec, val) => { - execCommand = exec; - getVal = val; - return null; - }} - - )} + {({ value, onChange }) => + console.log('1', value) || ( + + {(exec, val) => { + execCommand = exec; + getVal = val; + return null; + }} + + ) + }
); }} @@ -70,8 +73,11 @@ export const createExec = (props: Props) => { if (getVal == null || stateValue_ == null) { throw Error('rifm is not initialized'); } + const { replace } = props; - expect(props.format(stateValue_)).toEqual(getVal().value); + expect( + replace ? replace(props.format(stateValue_)) : props.format(stateValue_) + ).toEqual(getVal().value); return expect(renderInputState(getVal())); }; From 4ebdd62f2593d9cb6387d27da183954466c54bdb Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 20:23:13 +0300 Subject: [PATCH 8/9] remove log --- tests/utils/exec.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/utils/exec.js b/tests/utils/exec.js index 9affdb4..1325682 100644 --- a/tests/utils/exec.js +++ b/tests/utils/exec.js @@ -44,17 +44,15 @@ export const createExec = (props: Props) => { : undefined } > - {({ value, onChange }) => - console.log('1', value) || ( - - {(exec, val) => { - execCommand = exec; - getVal = val; - return null; - }} - - ) - } + {({ value, onChange }) => ( + + {(exec, val) => { + execCommand = exec; + getVal = val; + return null; + }} + + )} ); }} From e657af149b23ffa1bddf3ff0e654829a43860592 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Sun, 2 Jun 2019 20:33:14 +0300 Subject: [PATCH 9/9] Improve readability --- src/Rifm.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Rifm.js b/src/Rifm.js index b4ebd13..e7bc475 100644 --- a/src/Rifm.js +++ b/src/Rifm.js @@ -157,11 +157,9 @@ export const Rifm = (props: Props) => { if (replaceValue.length !== formattedValue.length) { console.warn('replace must preserve length'); } - - props.onChange(replaceValue); - } else { - props.onChange(replace ? replace(formattedValue) : formattedValue); } + + props.onChange(replace ? replace(formattedValue) : formattedValue); } return () => {