From b0ee9fd35c3101a82bbb9e896365a1f55d2794c2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 28 Jun 2019 11:59:38 +0800 Subject: [PATCH 01/13] add move operation for List --- examples/StateForm-list.tsx | 43 ++++++++++----- examples/components/useDraggable.ts | 81 +++++++++++++++++++++++++++++ package.json | 3 ++ src/List.tsx | 32 ++++++++++-- tests/list.test.js | 15 ++++++ 5 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 examples/components/useDraggable.ts diff --git a/examples/StateForm-list.tsx b/examples/StateForm-list.tsx index 0763acff..b4e86476 100644 --- a/examples/StateForm-list.tsx +++ b/examples/StateForm-list.tsx @@ -1,16 +1,34 @@ /* eslint-disable react/prop-types */ -import React from 'react'; +import * as React from 'react'; import Form from '../src/'; import Input from './components/Input'; import LabelField from './components/LabelField'; +import useDraggable from "./components/useDraggable"; +import HTML5Backend from 'react-dnd-html5-backend' +import { DndProvider} from 'react-dnd' const { List, useForm } = Form; +type LabelFieldProps = Parameters[0]; +interface DraggableFieldProps extends LabelFieldProps{ + id : string|number, + index : number, + move : (from:number,to :number)=>void, +} +const DraggableField :React.FunctionComponent= ({id,index,move,...others})=>{ + const {ref,isDragging} = useDraggable("demo-list",id,index,move); + return
+ +
+}; const Demo = () => { const [form] = useForm(); return ( +

List of Form

You can set Field as List

@@ -23,24 +41,24 @@ const Demo = () => { style={{ border: '1px solid red', padding: 15 }} > - {(fields, { add, remove }) => { + {(fields, { add, remove,move }) => { console.log('Demo Fields:', fields); return (

List of `users`

{fields.map((field, index) => ( - + {control => ( - + )} - + ))}
+
); }; diff --git a/examples/components/useDraggable.ts b/examples/components/useDraggable.ts new file mode 100644 index 00000000..7d66fbbe --- /dev/null +++ b/examples/components/useDraggable.ts @@ -0,0 +1,81 @@ +import {useRef} from "react"; +import {DragObjectWithType, useDrag, useDrop} from "react-dnd"; + +type DragWithIndex = DragObjectWithType & { + index : number, +}; +export default function useDraggable(type : string,id : string|number,index : number,move : (from : number,to : number)=>void){ + const ref = useRef(null); + const [, drop] = useDrop({ + accept: type, + hover(item : DragWithIndex,monitor){ + console.log(ref,item); + if(!ref.current){ + return; + } + const dragIndex = item.index; + if(dragIndex === undefined || dragIndex === null) return; + const hoverIndex = index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return + } + + // Determine rectangle on screen + const hoverBoundingRect = ref.current.getBoundingClientRect(); + + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; + + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + + // Get pixels to the top + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + const hoverClientX = clientOffset.x - hoverBoundingRect.left; + + // console.log(hoverBoundingRect,hoverMiddleY,clientOffset,hoverClientY, + // dragIndex,hoverIndex + // ); + // Only perform the move when the mouse has crossed half of the items height + // When dragging downwards, only move when the cursor is below 50% + // When dragging upwards, only move when the cursor is above 50% + + // Dragging downwards + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY + && hoverClientX < hoverMiddleX + ) { + return; + } + + // Dragging upwards + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY + && hoverClientX > hoverMiddleX + ) { + return; + } + + // Time to actually perform the action + move(dragIndex, hoverIndex); + + // Note: we're mutating the monitor item here! + // Generally it's better to avoid mutations, + // but it's good here for the sake of performance + // to avoid expensive index searches. + item.index = hoverIndex; + } , + }); + const [{ isDragging }, drag] = useDrag({ + item: { type: type, id, index }, + collect: monitor => ({ + isDragging: monitor.isDragging(), + }), + }); + drag(drop(ref)); + return { + ref,isDragging, + } + +} diff --git a/package.json b/package.json index 6461f47a..7179d16f 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ "father": "^2.13.2", "np": "^5.0.3", "react": "^v16.9.0-alpha.0", + "react-dnd": "^8.0.3", + "react-dnd-html5-backend": "^8.0.3", "react-dom": "^v16.9.0-alpha.0", "react-redux": "^4.4.10", "react-router": "^3.0.0", @@ -62,6 +64,7 @@ "dependencies": { "async-validator": "^1.11.2", "lodash": "^4.17.4", + "lodash-move": "^1.1.1", "rc-util": "^4.6.0", "warning": "^4.0.3" } diff --git a/src/List.tsx b/src/List.tsx index b50b4215..98fc2f26 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -4,6 +4,7 @@ import { InternalNamePath, NamePath, StoreValue } from './interface'; import FieldContext from './FieldContext'; import Field from './Field'; import { getNamePath } from './utils/valueUtil'; +import move from "lodash-move"; interface ListField { name: number; @@ -13,6 +14,7 @@ interface ListField { interface ListOperations { add: () => void; remove: (index: number) => void; + move:(from:number,to:number)=>void; } interface ListProps { @@ -49,7 +51,10 @@ const List: React.FunctionComponent = ({ name, children }) => { {({ value = [], onChange }) => { const { getFieldValue } = context; - + const getNewValue = ()=>{ + const values = getFieldValue(prefixName || []) as StoreValue[]; + return values || []; + }; /** * Always get latest value in case user update fields by `form` api. */ @@ -59,11 +64,11 @@ const List: React.FunctionComponent = ({ name, children }) => { keyManager.keys = [...keyManager.keys, keyManager.id]; keyManager.id += 1; - const newValue = (getFieldValue(prefixName) || []) as StoreValue[]; + const newValue = getNewValue(); onChange([...newValue, undefined]); }, remove: (index: number) => { - const newValue = (getFieldValue(prefixName) || []) as StoreValue[]; + const newValue = getNewValue(); // Do not handle out of range if (index < 0 || index >= newValue.length) { @@ -82,6 +87,27 @@ const List: React.FunctionComponent = ({ name, children }) => { // Trigger store change onChange(newValue.filter((_, id) => id !== index)); }, + move(from:number,to:number){ + if(from === to){ + return; + } + const newValue = getNewValue(); + + // Do not handle out of range + if (from < 0 || from >= newValue.length) { + return; + } + + // Do not handle out of range + if(to < 0 || to >= newValue.length){ + return; + } + + keyManager.keys = move(keyManager.keys,from,to); + + // Trigger store change + onChange(move(newValue,from,to)); + } }; return children( diff --git a/tests/list.test.js b/tests/list.test.js index 8bf286fa..3d478203 100644 --- a/tests/list.test.js +++ b/tests/list.test.js @@ -108,6 +108,21 @@ describe('Form.List', () => { matchKey(0, '0'); matchKey(1, '1'); matchKey(2, '2'); + + //Move + act(()=>{ + operation.move(2,0); + }); + wrapper.update(); + matchKey(0,'2'); + matchKey(1, '0'); + matchKey(2, '1'); + + //Revert Move + act(()=>{ + operation.move(0,2); + }); + wrapper.update(); // Modify await changeValue(getField(getList(), 1), '222'); From 522ee30f456db7b6cea2d26d6bed846a6d9af1bc Mon Sep 17 00:00:00 2001 From: hvsy Date: Mon, 1 Jul 2019 15:57:29 +0800 Subject: [PATCH 02/13] Fixed format error --- examples/StateForm-list.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/StateForm-list.tsx b/examples/StateForm-list.tsx index b4e86476..9f4adf28 100644 --- a/examples/StateForm-list.tsx +++ b/examples/StateForm-list.tsx @@ -49,14 +49,14 @@ const Demo = () => { {fields.map((field, index) => ( {control => ( - + )} ))} From 70288fbf9231c5069240db3d0c681ece3d562edb Mon Sep 17 00:00:00 2001 From: David Date: Mon, 1 Jul 2019 16:00:47 +0800 Subject: [PATCH 03/13] Fixed format error --- examples/StateForm-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/StateForm-list.tsx b/examples/StateForm-list.tsx index 9f4adf28..3ebc17fc 100644 --- a/examples/StateForm-list.tsx +++ b/examples/StateForm-list.tsx @@ -49,7 +49,7 @@ const Demo = () => { {fields.map((field, index) => ( {control => ( -
+
{ remove(index); From 7f1d951106a4caa6b78d7db712ce84fa25c59df8 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 Jul 2019 10:30:25 +0800 Subject: [PATCH 04/13] Fixed input can not be selectable Add list-draggable example --- examples/StateForm-list-draggable.tsx | 112 ++++++++++++++++++++++++++ examples/StateForm-list.tsx | 27 +------ 2 files changed, 116 insertions(+), 23 deletions(-) create mode 100644 examples/StateForm-list-draggable.tsx diff --git a/examples/StateForm-list-draggable.tsx b/examples/StateForm-list-draggable.tsx new file mode 100644 index 00000000..ac30aa54 --- /dev/null +++ b/examples/StateForm-list-draggable.tsx @@ -0,0 +1,112 @@ +/* eslint-disable react/prop-types */ + +import React from 'react'; +import Form from '../src/'; +import Input from './components/Input'; +import LabelField from './components/LabelField'; +import useDraggable from "./components/useDraggable"; +import HTML5Backend from 'react-dnd-html5-backend' +import { DndProvider} from 'react-dnd' + +const { List, useForm } = Form; + +type LabelFieldProps = Parameters[0]; +interface DraggableProps extends LabelFieldProps{ + id : string|number, + index : number, + move : (from:number,to :number)=>void, +} +const DisableDraggable = { + onDragStart(event){ + event.stopPropagation(); + event.preventDefault(); + }, + draggable : true, +}; +const Draggable :React.FunctionComponent= ({id,index,move,children})=>{ + const {ref,isDragging} = useDraggable("list-draggable",id,index,move); + return
+ {children} +
+}; +const Demo = () => { + const [form] = useForm(); + + return ( + +
+ + ); +}; + +export default Demo; \ No newline at end of file diff --git a/examples/StateForm-list.tsx b/examples/StateForm-list.tsx index 3ebc17fc..0763acff 100644 --- a/examples/StateForm-list.tsx +++ b/examples/StateForm-list.tsx @@ -1,34 +1,16 @@ /* eslint-disable react/prop-types */ -import * as React from 'react'; +import React from 'react'; import Form from '../src/'; import Input from './components/Input'; import LabelField from './components/LabelField'; -import useDraggable from "./components/useDraggable"; -import HTML5Backend from 'react-dnd-html5-backend' -import { DndProvider} from 'react-dnd' const { List, useForm } = Form; -type LabelFieldProps = Parameters[0]; -interface DraggableFieldProps extends LabelFieldProps{ - id : string|number, - index : number, - move : (from:number,to :number)=>void, -} -const DraggableField :React.FunctionComponent= ({id,index,move,...others})=>{ - const {ref,isDragging} = useDraggable("demo-list",id,index,move); - return
- -
-}; const Demo = () => { const [form] = useForm(); return ( -

List of Form

You can set Field as List

@@ -41,13 +23,13 @@ const Demo = () => { style={{ border: '1px solid red', padding: 15 }} > - {(fields, { add, remove,move }) => { + {(fields, { add, remove }) => { console.log('Demo Fields:', fields); return (

List of `users`

{fields.map((field, index) => ( - + {control => (
@@ -58,7 +40,7 @@ const Demo = () => {
)} -
+ ))}
-
); }; From 09eb43579ad7c2e58b26f5fa77601ab009716121 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 Jul 2019 11:46:05 +0800 Subject: [PATCH 05/13] Fixed format for eslint --- src/List.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/List.tsx b/src/List.tsx index 98fc2f26..c83daf37 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import warning from 'warning'; +import move from 'lodash-move'; import { InternalNamePath, NamePath, StoreValue } from './interface'; import FieldContext from './FieldContext'; import Field from './Field'; import { getNamePath } from './utils/valueUtil'; -import move from "lodash-move"; interface ListField { name: number; @@ -14,7 +14,7 @@ interface ListField { interface ListOperations { add: () => void; remove: (index: number) => void; - move:(from:number,to:number)=>void; + move:(from:number, to:number)=>void; } interface ListProps { @@ -51,8 +51,8 @@ const List: React.FunctionComponent = ({ name, children }) => { {({ value = [], onChange }) => { const { getFieldValue } = context; - const getNewValue = ()=>{ - const values = getFieldValue(prefixName || []) as StoreValue[]; + const getNewValue = () => { + const values = getFieldValue(prefixName || []) as StoreValue[]; return values || []; }; /** @@ -87,9 +87,9 @@ const List: React.FunctionComponent = ({ name, children }) => { // Trigger store change onChange(newValue.filter((_, id) => id !== index)); }, - move(from:number,to:number){ - if(from === to){ - return; + move(from: number, to: number) { + if (from === to) { + return; } const newValue = getNewValue(); @@ -99,15 +99,15 @@ const List: React.FunctionComponent = ({ name, children }) => { } // Do not handle out of range - if(to < 0 || to >= newValue.length){ + if (to < 0 || to >= newValue.length) { return; } - keyManager.keys = move(keyManager.keys,from,to); + keyManager.keys = move(keyManager.keys, from, to); // Trigger store change - onChange(move(newValue,from,to)); - } + onChange(move(newValue, from, to)); + }, }; return children( From bf47330eeac79043119b989cba5488bb82c03344 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 Jul 2019 12:04:25 +0800 Subject: [PATCH 06/13] add noneffective move test --- tests/list.test.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/tests/list.test.js b/tests/list.test.js index 3d478203..e34acdff 100644 --- a/tests/list.test.js +++ b/tests/list.test.js @@ -108,19 +108,34 @@ describe('Form.List', () => { matchKey(0, '0'); matchKey(1, '1'); matchKey(2, '2'); - - //Move - act(()=>{ - operation.move(2,0); + + // Move + act(() => { + operation.move(2, 0); + }); + + // noneffective move + act(() => { + operation.move(-1, 0); + }); + + // noneffective move + act(() => { + operation.move(0, 10); + }); + + // noneffective move + act(() => { + operation.move(0, 0); }); wrapper.update(); - matchKey(0,'2'); + matchKey(0, '2'); matchKey(1, '0'); matchKey(2, '1'); - - //Revert Move - act(()=>{ - operation.move(0,2); + + // Revert Move + act(() => { + operation.move(0, 2); }); wrapper.update(); From 4397babacb8e27b56d507a5c8dbe2180ff01b85f Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 Jul 2019 21:20:23 +0800 Subject: [PATCH 07/13] remove unnecessary action of draggable list example remove console log for performance --- examples/StateForm-list-draggable.tsx | 51 ++++++++------------------- examples/components/useDraggable.ts | 46 ++++++++++++------------ 2 files changed, 38 insertions(+), 59 deletions(-) diff --git a/examples/StateForm-list-draggable.tsx b/examples/StateForm-list-draggable.tsx index ac30aa54..6b3ffc7a 100644 --- a/examples/StateForm-list-draggable.tsx +++ b/examples/StateForm-list-draggable.tsx @@ -1,12 +1,12 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import Form from '../src/'; +import HTML5Backend from 'react-dnd-html5-backend'; +import { DndProvider } from 'react-dnd'; +import Form from '../src'; import Input from './components/Input'; import LabelField from './components/LabelField'; -import useDraggable from "./components/useDraggable"; -import HTML5Backend from 'react-dnd-html5-backend' -import { DndProvider} from 'react-dnd' +import useDraggable from './components/useDraggable'; const { List, useForm } = Form; @@ -14,22 +14,22 @@ type LabelFieldProps = Parameters[0]; interface DraggableProps extends LabelFieldProps{ id : string|number, index : number, - move : (from:number,to :number)=>void, + move : (from:number, to :number)=>void, } const DisableDraggable = { - onDragStart(event){ + onDragStart(event) { event.stopPropagation(); event.preventDefault(); }, - draggable : true, + draggable: true, }; -const Draggable :React.FunctionComponent= ({id,index,move,children})=>{ - const {ref,isDragging} = useDraggable("list-draggable",id,index,move); +const Draggable :React.FunctionComponent = ({ id, index, move, children }) => { + const { ref, isDragging } = useDraggable('list-draggable', id, index, move); return
{children} -
+
; }; const Demo = () => { const [form] = useForm(); @@ -48,13 +48,13 @@ const Demo = () => { style={{ border: '1px solid red', padding: 15 }} > - {(fields, { add, remove,move }) => { - console.log('Demo Fields:', fields); + {(fields, { add, remove, move }) => { return (

List of `users`

{fields.map((field, index) => ( - + {control => (
@@ -78,35 +78,14 @@ const Demo = () => { > + New User -
); }} - -
-

Out Of Form

- -
); }; -export default Demo; \ No newline at end of file +export default Demo; diff --git a/examples/components/useDraggable.ts b/examples/components/useDraggable.ts index 7d66fbbe..d3dd3c4a 100644 --- a/examples/components/useDraggable.ts +++ b/examples/components/useDraggable.ts @@ -1,81 +1,81 @@ -import {useRef} from "react"; -import {DragObjectWithType, useDrag, useDrop} from "react-dnd"; +import { useRef } from 'react'; +import { DragObjectWithType, useDrag, useDrop } from 'react-dnd'; type DragWithIndex = DragObjectWithType & { index : number, }; -export default function useDraggable(type : string,id : string|number,index : number,move : (from : number,to : number)=>void){ +export default function useDraggable(type : string, + id : string|number, + index : number, move : (from : number, to : number)=>void) { const ref = useRef(null); const [, drop] = useDrop({ accept: type, - hover(item : DragWithIndex,monitor){ - console.log(ref,item); - if(!ref.current){ + hover(item : DragWithIndex, monitor) { + if (!ref.current) { return; } const dragIndex = item.index; - if(dragIndex === undefined || dragIndex === null) return; + if (dragIndex === undefined || dragIndex === null) return; const hoverIndex = index; - + // Don't replace items with themselves if (dragIndex === hoverIndex) { - return + return; } - + // Determine rectangle on screen const hoverBoundingRect = ref.current.getBoundingClientRect(); - + // Get vertical middle const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2; - + // Determine mouse position const clientOffset = monitor.getClientOffset(); - + // Get pixels to the top const hoverClientY = clientOffset.y - hoverBoundingRect.top; const hoverClientX = clientOffset.x - hoverBoundingRect.left; - + // console.log(hoverBoundingRect,hoverMiddleY,clientOffset,hoverClientY, // dragIndex,hoverIndex // ); // Only perform the move when the mouse has crossed half of the items height // When dragging downwards, only move when the cursor is below 50% // When dragging upwards, only move when the cursor is above 50% - + // Dragging downwards if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY && hoverClientX < hoverMiddleX ) { return; } - + // Dragging upwards if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY && hoverClientX > hoverMiddleX ) { return; } - + // Time to actually perform the action move(dragIndex, hoverIndex); - + // Note: we're mutating the monitor item here! // Generally it's better to avoid mutations, // but it's good here for the sake of performance // to avoid expensive index searches. item.index = hoverIndex; - } , + }, }); const [{ isDragging }, drag] = useDrag({ - item: { type: type, id, index }, + item: { type, id, index }, collect: monitor => ({ isDragging: monitor.isDragging(), }), }); drag(drop(ref)); return { - ref,isDragging, - } - + ref, isDragging, + }; } From 077aba2cd07977b374f10b449c4ff55044afa1e4 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 4 Jul 2019 15:57:35 +0800 Subject: [PATCH 08/13] remove lodash-move --- package.json | 1 - src/List.tsx | 8 ++++---- src/utils/arrayMove.ts | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/utils/arrayMove.ts diff --git a/package.json b/package.json index 7179d16f..ad936ae0 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "dependencies": { "async-validator": "^1.11.2", "lodash": "^4.17.4", - "lodash-move": "^1.1.1", "rc-util": "^4.6.0", "warning": "^4.0.3" } diff --git a/src/List.tsx b/src/List.tsx index c83daf37..a1d6917a 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import warning from 'warning'; -import move from 'lodash-move'; +import { arrayMove } from './utils/arrayMove'; import { InternalNamePath, NamePath, StoreValue } from './interface'; import FieldContext from './FieldContext'; import Field from './Field'; @@ -14,7 +14,7 @@ interface ListField { interface ListOperations { add: () => void; remove: (index: number) => void; - move:(from:number, to:number)=>void; + move: (from: number, to: number) => void; } interface ListProps { @@ -103,10 +103,10 @@ const List: React.FunctionComponent = ({ name, children }) => { return; } - keyManager.keys = move(keyManager.keys, from, to); + keyManager.keys = arrayMove(keyManager.keys, from, to); // Trigger store change - onChange(move(newValue, from, to)); + onChange(arrayMove(newValue, from, to)); }, }; diff --git a/src/utils/arrayMove.ts b/src/utils/arrayMove.ts new file mode 100644 index 00000000..00c7b29c --- /dev/null +++ b/src/utils/arrayMove.ts @@ -0,0 +1,35 @@ +export function arrayMove(array: T[], moveIndex: number, toIndex: number) { + /* #move - Moves an array item from one position in an array to another. + + Note: This is a pure function so a new array will be returned, instead + of altering the array argument. + + Arguments: + 1. array (String) : Array in which to move an item. (required) + 2. moveIndex (Object) : The index of the item to move. (required) + 3. toIndex (Object) : The index to move item at moveIndex to. (required) + */ + const item = array[moveIndex]; + const { length } = array; + const diff = moveIndex - toIndex; + + if (diff > 0) { + // move left + return [ + ...array.slice(0, toIndex), + item, + ...array.slice(toIndex, moveIndex), + ...array.slice(moveIndex + 1, length), + ]; + } + if (diff < 0) { + // move right + return [ + ...array.slice(0, moveIndex), + ...array.slice(moveIndex + 1, toIndex + 1), + item, + ...array.slice(toIndex + 1, length), + ]; + } + return array; +} From 3601dc640aec604cd1cdee244dd8f5c0890d51f2 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 4 Jul 2019 17:15:15 +0800 Subject: [PATCH 09/13] add test for array move function --- tests/utils.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/utils.test.js b/tests/utils.test.js index db061817..258447de 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,7 +1,15 @@ +import { arrayMove } from '../src/utils/arrayMove'; import { isSimilar, setValues } from '../src/utils/valueUtil'; import NameMap from '../src/utils/NameMap'; describe('utils', () => { + describe('arrayMove', () => { + it('move', () => { + expect(arrayMove([0, 1, 2, 3], 0, 2)).toEqual([1, 2, 0, 3]); + expect(arrayMove([0, 1, 2, 3], 3, 1)).toEqual([0, 3, 1, 2]); + expect(arrayMove([0, 1, 2, 3], 1, 1)).toEqual([0, 1, 2, 3]); + }); + }); describe('valueUtil', () => { it('isSimilar', () => { expect(isSimilar(1, 1)).toBeTruthy(); From c348a271bb3a00c7dde06b6386c290e86ffbe3b2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 5 Jul 2019 09:40:44 +0800 Subject: [PATCH 10/13] move "arrayMove" function to valueUtil.ts file. rename "arrayMove" to "move". add checking for out of range. add test cases for out of range. --- src/List.tsx | 7 +++---- src/utils/arrayMove.ts | 35 ----------------------------------- src/utils/valueUtil.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/utils.test.js | 12 +++++++----- 4 files changed, 52 insertions(+), 44 deletions(-) delete mode 100644 src/utils/arrayMove.ts diff --git a/src/List.tsx b/src/List.tsx index a1d6917a..d02d159c 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -1,10 +1,9 @@ import * as React from 'react'; import warning from 'warning'; -import { arrayMove } from './utils/arrayMove'; import { InternalNamePath, NamePath, StoreValue } from './interface'; import FieldContext from './FieldContext'; import Field from './Field'; -import { getNamePath } from './utils/valueUtil'; +import { move, getNamePath } from './utils/valueUtil'; interface ListField { name: number; @@ -103,10 +102,10 @@ const List: React.FunctionComponent = ({ name, children }) => { return; } - keyManager.keys = arrayMove(keyManager.keys, from, to); + keyManager.keys = move(keyManager.keys, from, to); // Trigger store change - onChange(arrayMove(newValue, from, to)); + onChange(move(newValue, from, to)); }, }; diff --git a/src/utils/arrayMove.ts b/src/utils/arrayMove.ts deleted file mode 100644 index 00c7b29c..00000000 --- a/src/utils/arrayMove.ts +++ /dev/null @@ -1,35 +0,0 @@ -export function arrayMove(array: T[], moveIndex: number, toIndex: number) { - /* #move - Moves an array item from one position in an array to another. - - Note: This is a pure function so a new array will be returned, instead - of altering the array argument. - - Arguments: - 1. array (String) : Array in which to move an item. (required) - 2. moveIndex (Object) : The index of the item to move. (required) - 3. toIndex (Object) : The index to move item at moveIndex to. (required) - */ - const item = array[moveIndex]; - const { length } = array; - const diff = moveIndex - toIndex; - - if (diff > 0) { - // move left - return [ - ...array.slice(0, toIndex), - item, - ...array.slice(toIndex, moveIndex), - ...array.slice(moveIndex + 1, length), - ]; - } - if (diff < 0) { - // move right - return [ - ...array.slice(0, moveIndex), - ...array.slice(moveIndex + 1, toIndex + 1), - item, - ...array.slice(toIndex + 1, length), - ]; - } - return array; -} diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index f45df77f..74d53a29 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -120,3 +120,45 @@ export function defaultGetValueFromEvent(valuePropName: string, ...args: EventAr return event; } + +/** + * Moves an array item from one position in an array to another. + * + * Note: This is a pure function so a new array will be returned, instead + * of altering the array argument. + * + * @param array Array in which to move an item. (required) + * @param moveIndex The index of the item to move. (required) + * @param toIndex The index to move item at moveIndex to. (required) + */ +export function move(array: T[], moveIndex: number, toIndex: number) { + const { length } = array; + if (moveIndex < 0 || moveIndex >= length) { + return array; + } + if (toIndex < 0 || toIndex >= length) { + return array; + } + const item = array[moveIndex]; + const diff = moveIndex - toIndex; + + if (diff > 0) { + // move left + return [ + ...array.slice(0, toIndex), + item, + ...array.slice(toIndex, moveIndex), + ...array.slice(moveIndex + 1, length), + ]; + } + if (diff < 0) { + // move right + return [ + ...array.slice(0, moveIndex), + ...array.slice(moveIndex + 1, toIndex + 1), + item, + ...array.slice(toIndex + 1, length), + ]; + } + return array; +} diff --git a/tests/utils.test.js b/tests/utils.test.js index 258447de..bf26869d 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,13 +1,15 @@ -import { arrayMove } from '../src/utils/arrayMove'; -import { isSimilar, setValues } from '../src/utils/valueUtil'; +import { move, isSimilar, setValues } from '../src/utils/valueUtil'; import NameMap from '../src/utils/NameMap'; describe('utils', () => { describe('arrayMove', () => { it('move', () => { - expect(arrayMove([0, 1, 2, 3], 0, 2)).toEqual([1, 2, 0, 3]); - expect(arrayMove([0, 1, 2, 3], 3, 1)).toEqual([0, 3, 1, 2]); - expect(arrayMove([0, 1, 2, 3], 1, 1)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], 0, 2)).toEqual([1, 2, 0, 3]); + expect(move([0, 1, 2, 3], 3, 1)).toEqual([0, 3, 1, 2]); + expect(move([0, 1, 2, 3], 1, 1)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], -1, 3)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], -1, 5)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], 1, 5)).toEqual([0, 1, 2, 3]); }); }); describe('valueUtil', () => { From ae16986801c78887d9be05183f5a3a7a2434f1a7 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 5 Jul 2019 11:07:14 +0800 Subject: [PATCH 11/13] add more test cases for move function. merge edge case check into one if. --- src/List.tsx | 7 +------ src/utils/valueUtil.ts | 5 +---- tests/utils.test.js | 6 ++++++ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/List.tsx b/src/List.tsx index d02d159c..b7c2b808 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -93,12 +93,7 @@ const List: React.FunctionComponent = ({ name, children }) => { const newValue = getNewValue(); // Do not handle out of range - if (from < 0 || from >= newValue.length) { - return; - } - - // Do not handle out of range - if (to < 0 || to >= newValue.length) { + if (from < 0 || from >= newValue.length || to < 0 || to >= newValue.length) { return; } diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index 74d53a29..ef485f67 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -133,10 +133,7 @@ export function defaultGetValueFromEvent(valuePropName: string, ...args: EventAr */ export function move(array: T[], moveIndex: number, toIndex: number) { const { length } = array; - if (moveIndex < 0 || moveIndex >= length) { - return array; - } - if (toIndex < 0 || toIndex >= length) { + if (moveIndex < 0 || moveIndex >= length || toIndex < 0 || toIndex >= length) { return array; } const item = array[moveIndex]; diff --git a/tests/utils.test.js b/tests/utils.test.js index bf26869d..d372aac2 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -10,6 +10,12 @@ describe('utils', () => { expect(move([0, 1, 2, 3], -1, 3)).toEqual([0, 1, 2, 3]); expect(move([0, 1, 2, 3], -1, 5)).toEqual([0, 1, 2, 3]); expect(move([0, 1, 2, 3], 1, 5)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], 0, 0)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], 0, 1)).toEqual([1, 0, 2, 3]); + expect(move([0, 1, 2, 3], 1, 0)).toEqual([1, 0, 2, 3]); + expect(move([0, 1, 2, 3], 2, 3)).toEqual([0, 1, 3, 2]); + expect(move([0, 1, 2, 3], 3, 3)).toEqual([0, 1, 2, 3]); + expect(move([0, 1, 2, 3], 3, 2)).toEqual([0, 1, 3, 2]); }); }); describe('valueUtil', () => { From 06167f36dabbd361d5601f1db3b6ce58607cae02 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 16 Jul 2019 11:34:46 +0800 Subject: [PATCH 12/13] upgrade father to latest format draggable example --- examples/StateForm-list-draggable.tsx | 110 ++++++++++++++------------ package.json | 2 +- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/examples/StateForm-list-draggable.tsx b/examples/StateForm-list-draggable.tsx index 6b3ffc7a..60cce279 100644 --- a/examples/StateForm-list-draggable.tsx +++ b/examples/StateForm-list-draggable.tsx @@ -1,71 +1,82 @@ /* eslint-disable react/prop-types */ - import React from 'react'; + +/* eslint-enable react/prop-types */ import HTML5Backend from 'react-dnd-html5-backend'; import { DndProvider } from 'react-dnd'; -import Form from '../src'; +import Form, { List, useForm } from '../src'; import Input from './components/Input'; import LabelField from './components/LabelField'; import useDraggable from './components/useDraggable'; -const { List, useForm } = Form; - type LabelFieldProps = Parameters[0]; -interface DraggableProps extends LabelFieldProps{ - id : string|number, - index : number, - move : (from:number, to :number)=>void, +interface DraggableProps extends LabelFieldProps { + id: string | number; + index: number; + move: (from: number, to: number) => void; } const DisableDraggable = { - onDragStart(event) { - event.stopPropagation(); - event.preventDefault(); - }, - draggable: true, + onDragStart(event) { + event.stopPropagation(); + event.preventDefault(); + }, + draggable: true, }; -const Draggable :React.FunctionComponent = ({ id, index, move, children }) => { - const { ref, isDragging } = useDraggable('list-draggable', id, index, move); - return
- {children} -
; +const Draggable: React.FunctionComponent = ({ id, index, move, children }) => { + const { ref, isDragging } = useDraggable('list-draggable', id, index, move); + return ( +
+ {children} +
+ ); }; const Demo = () => { const [form] = useForm(); return ( -
-

Draggable List of Form

-

You can set Field as List and sortable by drag and drop

+
+

Draggable List of Form

+

You can set Field as List and sortable by drag and drop

-
{ - console.log('values:', values); - }} - style={{ border: '1px solid red', padding: 15 }} - > - - {(fields, { add, remove, move }) => { - return ( + { + console.log('values:', values); + }} + style={{ border: '1px solid red', padding: 15 }} + > + + {(fields, { add, remove, move }) => (

List of `users`

{fields.map((field, index) => ( - + - {control => ( - - )} + {control => ( + + )} ))} @@ -79,11 +90,10 @@ const Demo = () => { + New User
- ); - }} -
- -
+ )} + + +
); }; diff --git a/package.json b/package.json index ad936ae0..3e9e4c33 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.2", "enzyme-to-json": "^3.1.4", - "father": "^2.13.2", + "father": "^2.13.6", "np": "^5.0.3", "react": "^v16.9.0-alpha.0", "react-dnd": "^8.0.3", From 2e99658b04b03f38c1a55a66222e4dd720616b1d Mon Sep 17 00:00:00 2001 From: David Date: Tue, 16 Jul 2019 11:40:49 +0800 Subject: [PATCH 13/13] add missing match key tests. --- tests/list.test.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/list.test.js b/tests/list.test.js index e34acdff..540298a9 100644 --- a/tests/list.test.js +++ b/tests/list.test.js @@ -113,17 +113,40 @@ describe('Form.List', () => { act(() => { operation.move(2, 0); }); + wrapper.update(); + matchKey(0, '2'); + matchKey(1, '0'); + matchKey(2, '1'); // noneffective move act(() => { operation.move(-1, 0); }); + wrapper.update(); + matchKey(0, '2'); + matchKey(1, '0'); + matchKey(2, '1'); // noneffective move act(() => { operation.move(0, 10); }); + wrapper.update(); + matchKey(0, '2'); + matchKey(1, '0'); + matchKey(2, '1'); + + // noneffective move + act(() => { + operation.move(-1, 10); + }); + + wrapper.update(); + matchKey(0, '2'); + matchKey(1, '0'); + matchKey(2, '1'); + // noneffective move act(() => { operation.move(0, 0); @@ -138,6 +161,9 @@ describe('Form.List', () => { operation.move(0, 2); }); wrapper.update(); + matchKey(0, '0'); + matchKey(1, '1'); + matchKey(2, '2'); // Modify await changeValue(getField(getList(), 1), '222');