orn-ui 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/organisms/ReorderableList.js +35 -13
- package/lib/commonjs/organisms/ReorderableList.js.map +1 -1
- package/lib/module/organisms/ReorderableList.js +35 -13
- package/lib/module/organisms/ReorderableList.js.map +1 -1
- package/lib/typescript/organisms/ReorderableList.d.ts +20 -1
- package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
- package/package.json +1 -1
- package/registry/index.json +1 -1
- package/registry/reorderable-list.json +1 -1
- package/src/organisms/ReorderableList.tsx +45 -13
|
@@ -34,6 +34,16 @@ const useStyles = (0, _createStyles.createStyles)(() => ({
|
|
|
34
34
|
* PanResponder + Animated alcanzan porque el cálculo de índice destino
|
|
35
35
|
* necesita el valor de arrastre en JS en cada movimiento (no algo que
|
|
36
36
|
* useNativeDriver pueda resolver solo).
|
|
37
|
+
*
|
|
38
|
+
* Dentro de un scroll vertical hay que cablear `onDragStart`/`onDragEnd` a
|
|
39
|
+
* `scrollEnabled`, o en iOS el arrastre no llega a empezar:
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const [dragging, setDragging] = useState(false);
|
|
43
|
+
* <ScrollView scrollEnabled={!dragging}>
|
|
44
|
+
* <ReorderableList onDragStart={() => setDragging(true)} onDragEnd={() => setDragging(false)} … />
|
|
45
|
+
* </ScrollView>
|
|
46
|
+
* ```
|
|
37
47
|
*/
|
|
38
48
|
function ReorderableList({
|
|
39
49
|
data,
|
|
@@ -41,6 +51,8 @@ function ReorderableList({
|
|
|
41
51
|
itemHeight,
|
|
42
52
|
renderItem,
|
|
43
53
|
onReorder,
|
|
54
|
+
onDragStart,
|
|
55
|
+
onDragEnd,
|
|
44
56
|
disabled = false,
|
|
45
57
|
style
|
|
46
58
|
}) {
|
|
@@ -62,6 +74,13 @@ function ReorderableList({
|
|
|
62
74
|
// valor del momento en que esa fila se creó.
|
|
63
75
|
const disabledRef = (0, _react.useRef)(disabled);
|
|
64
76
|
disabledRef.current = disabled;
|
|
77
|
+
// Misma razón: los callbacks se leen desde closures creadas una sola vez.
|
|
78
|
+
const onReorderRef = (0, _react.useRef)(onReorder);
|
|
79
|
+
onReorderRef.current = onReorder;
|
|
80
|
+
const onDragStartRef = (0, _react.useRef)(onDragStart);
|
|
81
|
+
onDragStartRef.current = onDragStart;
|
|
82
|
+
const onDragEndRef = (0, _react.useRef)(onDragEnd);
|
|
83
|
+
onDragEndRef.current = onDragEnd;
|
|
65
84
|
const panResponders = (0, _react.useRef)(new Map()).current;
|
|
66
85
|
// Si `data` se achica, los responders de índices que ya no existen quedan
|
|
67
86
|
// colgados en el Map para siempre: se podan en cada render.
|
|
@@ -71,15 +90,28 @@ function ReorderableList({
|
|
|
71
90
|
function panResponderFor(index) {
|
|
72
91
|
let responder = panResponders.get(index);
|
|
73
92
|
if (responder) return responder;
|
|
93
|
+
const endDrag = () => {
|
|
94
|
+
dy.setValue(0);
|
|
95
|
+
activeIndexRef.current = null;
|
|
96
|
+
targetIndexRef.current = null;
|
|
97
|
+
setActiveIndex(null);
|
|
98
|
+
setTargetIndex(null);
|
|
99
|
+
onDragEndRef.current?.();
|
|
100
|
+
};
|
|
74
101
|
responder = _reactNative.PanResponder.create({
|
|
75
102
|
onStartShouldSetPanResponder: () => !disabledRef.current,
|
|
76
103
|
onMoveShouldSetPanResponder: (_, gesture) => !disabledRef.current && Math.abs(gesture.dy) > 2,
|
|
104
|
+
// Un scroll padre pide el responder apenas su gesto vertical arranca —
|
|
105
|
+
// el mismo eje que el arrastre. Cederlo cancelaría el drag a mitad de
|
|
106
|
+
// camino, así que la fila se lo queda hasta soltar.
|
|
107
|
+
onPanResponderTerminationRequest: () => false,
|
|
77
108
|
onPanResponderGrant: () => {
|
|
78
109
|
activeIndexRef.current = index;
|
|
79
110
|
targetIndexRef.current = index;
|
|
80
111
|
setActiveIndex(index);
|
|
81
112
|
setTargetIndex(index);
|
|
82
113
|
dy.setValue(0);
|
|
114
|
+
onDragStartRef.current?.(index);
|
|
83
115
|
},
|
|
84
116
|
onPanResponderMove: (_, gesture) => {
|
|
85
117
|
dy.setValue(gesture.dy);
|
|
@@ -93,21 +125,11 @@ function ReorderableList({
|
|
|
93
125
|
onPanResponderRelease: () => {
|
|
94
126
|
const from = activeIndexRef.current;
|
|
95
127
|
const to = targetIndexRef.current;
|
|
96
|
-
|
|
97
|
-
activeIndexRef.current = null;
|
|
98
|
-
targetIndexRef.current = null;
|
|
99
|
-
setActiveIndex(null);
|
|
100
|
-
setTargetIndex(null);
|
|
128
|
+
endDrag();
|
|
101
129
|
if (from === null || to === null || from === to) return;
|
|
102
|
-
|
|
130
|
+
onReorderRef.current(move(dataRef.current, from, to));
|
|
103
131
|
},
|
|
104
|
-
onPanResponderTerminate:
|
|
105
|
-
dy.setValue(0);
|
|
106
|
-
activeIndexRef.current = null;
|
|
107
|
-
targetIndexRef.current = null;
|
|
108
|
-
setActiveIndex(null);
|
|
109
|
-
setTargetIndex(null);
|
|
110
|
-
}
|
|
132
|
+
onPanResponderTerminate: endDrag
|
|
111
133
|
});
|
|
112
134
|
panResponders.set(index, responder);
|
|
113
135
|
return responder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_createStyles","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","move","data","from","to","next","slice","item","splice","useStyles","createStyles","row","width","ReorderableList","keyExtractor","itemHeight","renderItem","onReorder","disabled","style","styles","activeIndex","setActiveIndex","useState","targetIndex","setTargetIndex","dy","useRef","Animated","Value","current","activeIndexRef","targetIndexRef","dataRef","disabledRef","panResponders","Map","index","keys","length","delete","panResponderFor","responder","PanResponder","create","onStartShouldSetPanResponder","onMoveShouldSetPanResponder","_","gesture","Math","abs","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_createStyles","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","move","data","from","to","next","slice","item","splice","useStyles","createStyles","row","width","ReorderableList","keyExtractor","itemHeight","renderItem","onReorder","onDragStart","onDragEnd","disabled","style","styles","activeIndex","setActiveIndex","useState","targetIndex","setTargetIndex","dy","useRef","Animated","Value","current","activeIndexRef","targetIndexRef","dataRef","disabledRef","onReorderRef","onDragStartRef","onDragEndRef","panResponders","Map","index","keys","length","delete","panResponderFor","responder","endDrag","setValue","PanResponder","create","onStartShouldSetPanResponder","onMoveShouldSetPanResponder","_","gesture","Math","abs","onPanResponderTerminationRequest","onPanResponderGrant","onPanResponderMove","rawTarget","round","clamped","max","min","prev","onPanResponderRelease","onPanResponderTerminate","jsx","View","children","map","isActive","shift","transform","translateY","zIndex","elevation","panHandlers"],"sourceRoot":"../../../src","sources":["organisms/ReorderableList.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -28,6 +28,16 @@ const useStyles = createStyles(() => ({
|
|
|
28
28
|
* PanResponder + Animated alcanzan porque el cálculo de índice destino
|
|
29
29
|
* necesita el valor de arrastre en JS en cada movimiento (no algo que
|
|
30
30
|
* useNativeDriver pueda resolver solo).
|
|
31
|
+
*
|
|
32
|
+
* Dentro de un scroll vertical hay que cablear `onDragStart`/`onDragEnd` a
|
|
33
|
+
* `scrollEnabled`, o en iOS el arrastre no llega a empezar:
|
|
34
|
+
*
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const [dragging, setDragging] = useState(false);
|
|
37
|
+
* <ScrollView scrollEnabled={!dragging}>
|
|
38
|
+
* <ReorderableList onDragStart={() => setDragging(true)} onDragEnd={() => setDragging(false)} … />
|
|
39
|
+
* </ScrollView>
|
|
40
|
+
* ```
|
|
31
41
|
*/
|
|
32
42
|
export function ReorderableList({
|
|
33
43
|
data,
|
|
@@ -35,6 +45,8 @@ export function ReorderableList({
|
|
|
35
45
|
itemHeight,
|
|
36
46
|
renderItem,
|
|
37
47
|
onReorder,
|
|
48
|
+
onDragStart,
|
|
49
|
+
onDragEnd,
|
|
38
50
|
disabled = false,
|
|
39
51
|
style
|
|
40
52
|
}) {
|
|
@@ -56,6 +68,13 @@ export function ReorderableList({
|
|
|
56
68
|
// valor del momento en que esa fila se creó.
|
|
57
69
|
const disabledRef = useRef(disabled);
|
|
58
70
|
disabledRef.current = disabled;
|
|
71
|
+
// Misma razón: los callbacks se leen desde closures creadas una sola vez.
|
|
72
|
+
const onReorderRef = useRef(onReorder);
|
|
73
|
+
onReorderRef.current = onReorder;
|
|
74
|
+
const onDragStartRef = useRef(onDragStart);
|
|
75
|
+
onDragStartRef.current = onDragStart;
|
|
76
|
+
const onDragEndRef = useRef(onDragEnd);
|
|
77
|
+
onDragEndRef.current = onDragEnd;
|
|
59
78
|
const panResponders = useRef(new Map()).current;
|
|
60
79
|
// Si `data` se achica, los responders de índices que ya no existen quedan
|
|
61
80
|
// colgados en el Map para siempre: se podan en cada render.
|
|
@@ -65,15 +84,28 @@ export function ReorderableList({
|
|
|
65
84
|
function panResponderFor(index) {
|
|
66
85
|
let responder = panResponders.get(index);
|
|
67
86
|
if (responder) return responder;
|
|
87
|
+
const endDrag = () => {
|
|
88
|
+
dy.setValue(0);
|
|
89
|
+
activeIndexRef.current = null;
|
|
90
|
+
targetIndexRef.current = null;
|
|
91
|
+
setActiveIndex(null);
|
|
92
|
+
setTargetIndex(null);
|
|
93
|
+
onDragEndRef.current?.();
|
|
94
|
+
};
|
|
68
95
|
responder = PanResponder.create({
|
|
69
96
|
onStartShouldSetPanResponder: () => !disabledRef.current,
|
|
70
97
|
onMoveShouldSetPanResponder: (_, gesture) => !disabledRef.current && Math.abs(gesture.dy) > 2,
|
|
98
|
+
// Un scroll padre pide el responder apenas su gesto vertical arranca —
|
|
99
|
+
// el mismo eje que el arrastre. Cederlo cancelaría el drag a mitad de
|
|
100
|
+
// camino, así que la fila se lo queda hasta soltar.
|
|
101
|
+
onPanResponderTerminationRequest: () => false,
|
|
71
102
|
onPanResponderGrant: () => {
|
|
72
103
|
activeIndexRef.current = index;
|
|
73
104
|
targetIndexRef.current = index;
|
|
74
105
|
setActiveIndex(index);
|
|
75
106
|
setTargetIndex(index);
|
|
76
107
|
dy.setValue(0);
|
|
108
|
+
onDragStartRef.current?.(index);
|
|
77
109
|
},
|
|
78
110
|
onPanResponderMove: (_, gesture) => {
|
|
79
111
|
dy.setValue(gesture.dy);
|
|
@@ -87,21 +119,11 @@ export function ReorderableList({
|
|
|
87
119
|
onPanResponderRelease: () => {
|
|
88
120
|
const from = activeIndexRef.current;
|
|
89
121
|
const to = targetIndexRef.current;
|
|
90
|
-
|
|
91
|
-
activeIndexRef.current = null;
|
|
92
|
-
targetIndexRef.current = null;
|
|
93
|
-
setActiveIndex(null);
|
|
94
|
-
setTargetIndex(null);
|
|
122
|
+
endDrag();
|
|
95
123
|
if (from === null || to === null || from === to) return;
|
|
96
|
-
|
|
124
|
+
onReorderRef.current(move(dataRef.current, from, to));
|
|
97
125
|
},
|
|
98
|
-
onPanResponderTerminate:
|
|
99
|
-
dy.setValue(0);
|
|
100
|
-
activeIndexRef.current = null;
|
|
101
|
-
targetIndexRef.current = null;
|
|
102
|
-
setActiveIndex(null);
|
|
103
|
-
setTargetIndex(null);
|
|
104
|
-
}
|
|
126
|
+
onPanResponderTerminate: endDrag
|
|
105
127
|
});
|
|
106
128
|
panResponders.set(index, responder);
|
|
107
129
|
return responder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useRef","useState","Animated","PanResponder","View","createStyles","jsx","_jsx","move","data","from","to","next","slice","item","splice","useStyles","row","width","ReorderableList","keyExtractor","itemHeight","renderItem","onReorder","disabled","style","styles","activeIndex","setActiveIndex","targetIndex","setTargetIndex","dy","Value","current","activeIndexRef","targetIndexRef","dataRef","disabledRef","panResponders","Map","index","keys","length","delete","panResponderFor","responder","get","create","onStartShouldSetPanResponder","onMoveShouldSetPanResponder","_","gesture","Math","abs","
|
|
1
|
+
{"version":3,"names":["React","useRef","useState","Animated","PanResponder","View","createStyles","jsx","_jsx","move","data","from","to","next","slice","item","splice","useStyles","row","width","ReorderableList","keyExtractor","itemHeight","renderItem","onReorder","onDragStart","onDragEnd","disabled","style","styles","activeIndex","setActiveIndex","targetIndex","setTargetIndex","dy","Value","current","activeIndexRef","targetIndexRef","dataRef","disabledRef","onReorderRef","onDragStartRef","onDragEndRef","panResponders","Map","index","keys","length","delete","panResponderFor","responder","get","endDrag","setValue","create","onStartShouldSetPanResponder","onMoveShouldSetPanResponder","_","gesture","Math","abs","onPanResponderTerminationRequest","onPanResponderGrant","onPanResponderMove","rawTarget","round","clamped","max","min","prev","onPanResponderRelease","onPanResponderTerminate","set","children","map","isActive","shift","transform","translateY","zIndex","elevation","panHandlers"],"sourceRoot":"../../../src","sources":["organisms/ReorderableList.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -8,6 +8,15 @@ export interface ReorderableListProps<T> {
|
|
|
8
8
|
renderItem: (item: T, index: number, dragging: boolean) => React.ReactElement;
|
|
9
9
|
/** Se llama al soltar, con el arreglo ya reordenado. No muta `data`. */
|
|
10
10
|
onReorder: (data: T[]) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Empieza el arrastre de la fila `index`. Úsalo para apagar el scroll del
|
|
13
|
+
* contenedor (`scrollEnabled={false}`) mientras dura: en iOS el
|
|
14
|
+
* `UIScrollView` padre cancela los toques del contenido apenas su gesto
|
|
15
|
+
* vertical arranca, y sin eso el arrastre muere a los pocos píxeles.
|
|
16
|
+
*/
|
|
17
|
+
onDragStart?: (index: number) => void;
|
|
18
|
+
/** Termina el arrastre — soltando o por cancelación. Siempre corre si corrió `onDragStart`. */
|
|
19
|
+
onDragEnd?: () => void;
|
|
11
20
|
disabled?: boolean;
|
|
12
21
|
style?: StyleProp<ViewStyle>;
|
|
13
22
|
}
|
|
@@ -24,6 +33,16 @@ export declare function move<T>(data: T[], from: number, to: number): T[];
|
|
|
24
33
|
* PanResponder + Animated alcanzan porque el cálculo de índice destino
|
|
25
34
|
* necesita el valor de arrastre en JS en cada movimiento (no algo que
|
|
26
35
|
* useNativeDriver pueda resolver solo).
|
|
36
|
+
*
|
|
37
|
+
* Dentro de un scroll vertical hay que cablear `onDragStart`/`onDragEnd` a
|
|
38
|
+
* `scrollEnabled`, o en iOS el arrastre no llega a empezar:
|
|
39
|
+
*
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const [dragging, setDragging] = useState(false);
|
|
42
|
+
* <ScrollView scrollEnabled={!dragging}>
|
|
43
|
+
* <ReorderableList onDragStart={() => setDragging(true)} onDragEnd={() => setDragging(false)} … />
|
|
44
|
+
* </ScrollView>
|
|
45
|
+
* ```
|
|
27
46
|
*/
|
|
28
|
-
export declare function ReorderableList<T>({ data, keyExtractor, itemHeight, renderItem, onReorder, disabled, style, }: ReorderableListProps<T>): React.JSX.Element;
|
|
47
|
+
export declare function ReorderableList<T>({ data, keyExtractor, itemHeight, renderItem, onReorder, onDragStart, onDragEnd, disabled, style, }: ReorderableListProps<T>): React.JSX.Element;
|
|
29
48
|
//# sourceMappingURL=ReorderableList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReorderableList.d.ts","sourceRoot":"","sources":["../../../src/organisms/ReorderableList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAChD,OAAO,EAAgC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAG5F,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,YAAY,CAAC;IAC9E,wEAAwE;IACxE,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,2HAA2H;AAC3H,wBAAgB,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,CAKhE;AAMD
|
|
1
|
+
{"version":3,"file":"ReorderableList.d.ts","sourceRoot":"","sources":["../../../src/organisms/ReorderableList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAChD,OAAO,EAAgC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAG5F,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,YAAY,CAAC;IAC9E,wEAAwE;IACxE,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,2HAA2H;AAC3H,wBAAgB,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,CAKhE;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,EACjC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAgB,EAChB,KAAK,GACN,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAiHzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orn-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Fast, tree-shakeable, atomic-design component library for React Native. Zero runtime dependencies.",
|
|
5
5
|
"author": "David Trujillo <davot098@gmail.com>",
|
|
6
6
|
"main": "lib/commonjs/index.js",
|
package/registry/index.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/ReorderableList.tsx",
|
|
12
|
-
"content": "import React, { useRef, useState } from 'react';\nimport { Animated, PanResponder, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\n\nexport interface ReorderableListProps<T> {\n data: T[];\n keyExtractor: (item: T, index: number) => string;\n /** Alto fijo de cada fila: necesario para calcular el índice destino sin medir layout async. */\n itemHeight: number;\n renderItem: (item: T, index: number, dragging: boolean) => React.ReactElement;\n /** Se llama al soltar, con el arreglo ya reordenado. No muta `data`. */\n onReorder: (data: T[]) => void;\n disabled?: boolean;\n style?: StyleProp<ViewStyle>;\n}\n\n/** Reordena moviendo el ítem de `from` a `to`, sin mutar `data`. Exportada aparte por ser testeable sin simular gestos. */\nexport function move<T>(data: T[], from: number, to: number): T[] {\n const next = data.slice();\n const [item] = next.splice(from, 1) as [T];\n next.splice(to, 0, item);\n return next;\n}\n\nconst useStyles = createStyles(() => ({\n row: { width: '100%' },\n}));\n\n/**\n * ReorderableList\n * Ley de destino común (common fate): al arrastrar una fila, las filas que\n * tiene que atravesar se desplazan juntas en el mismo sentido — eso es lo\n * que comunica \"van a intercambiar lugar\" antes de soltar el dedo, no un\n * simple resaltado del ítem activo.\n *\n * Sin dependencias de gestos/animación externas (reanimated, gesture-handler):\n * PanResponder + Animated alcanzan porque el cálculo de índice destino\n * necesita el valor de arrastre en JS en cada movimiento (no algo que\n * useNativeDriver pueda resolver solo).\n */\nexport function ReorderableList<T>({\n data,\n keyExtractor,\n itemHeight,\n renderItem,\n onReorder,\n disabled = false,\n style,\n}: ReorderableListProps<T>) {\n const styles = useStyles();\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const [targetIndex, setTargetIndex] = useState<number | null>(null);\n const dy = useRef(new Animated.Value(0)).current;\n const activeIndexRef = useRef<number | null>(null);\n // El PanResponder de cada fila se crea una sola vez (se cachea en el Map de\n // abajo) y su closure queda fijo al render de esa primera vez: leer\n // `targetIndex` (state) directamente ahí siempre daría el valor del\n // montaje. Un ref sí refleja la última escritura sin importar cuándo se\n // creó la closure que lo lee.\n const targetIndexRef = useRef<number | null>(null);\n const dataRef = useRef(data);\n dataRef.current = data;\n // Igual razón que targetIndexRef: el responder de cada fila se crea una\n // sola vez, así que leer `disabled` directo del closure congelaría el\n // valor del momento en que esa fila se creó.\n const disabledRef = useRef(disabled);\n disabledRef.current = disabled;\n\n const panResponders = useRef(new Map<number, ReturnType<typeof PanResponder.create>>()).current;\n // Si `data` se achica, los responders de índices que ya no existen quedan\n // colgados en el Map para siempre: se podan en cada render.\n for (const index of panResponders.keys()) {\n if (index >= data.length) panResponders.delete(index);\n }\n\n function panResponderFor(index: number) {\n let responder = panResponders.get(index);\n if (responder) return responder;\n\n responder = PanResponder.create({\n onStartShouldSetPanResponder: () => !disabledRef.current,\n onMoveShouldSetPanResponder: (_, gesture) => !disabledRef.current && Math.abs(gesture.dy) > 2,\n onPanResponderGrant: () => {\n activeIndexRef.current = index;\n targetIndexRef.current = index;\n setActiveIndex(index);\n setTargetIndex(index);\n dy.setValue(0);\n },\n onPanResponderMove: (_, gesture) => {\n dy.setValue(gesture.dy);\n const from = activeIndexRef.current;\n if (from === null) return;\n const rawTarget = from + Math.round(gesture.dy / itemHeight);\n const clamped = Math.max(0, Math.min(dataRef.current.length - 1, rawTarget));\n targetIndexRef.current = clamped;\n setTargetIndex((prev) => (prev === clamped ? prev : clamped));\n },\n onPanResponderRelease: () => {\n const from = activeIndexRef.current;\n const to = targetIndexRef.current;\n
|
|
12
|
+
"content": "import React, { useRef, useState } from 'react';\nimport { Animated, PanResponder, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\n\nexport interface ReorderableListProps<T> {\n data: T[];\n keyExtractor: (item: T, index: number) => string;\n /** Alto fijo de cada fila: necesario para calcular el índice destino sin medir layout async. */\n itemHeight: number;\n renderItem: (item: T, index: number, dragging: boolean) => React.ReactElement;\n /** Se llama al soltar, con el arreglo ya reordenado. No muta `data`. */\n onReorder: (data: T[]) => void;\n /**\n * Empieza el arrastre de la fila `index`. Úsalo para apagar el scroll del\n * contenedor (`scrollEnabled={false}`) mientras dura: en iOS el\n * `UIScrollView` padre cancela los toques del contenido apenas su gesto\n * vertical arranca, y sin eso el arrastre muere a los pocos píxeles.\n */\n onDragStart?: (index: number) => void;\n /** Termina el arrastre — soltando o por cancelación. Siempre corre si corrió `onDragStart`. */\n onDragEnd?: () => void;\n disabled?: boolean;\n style?: StyleProp<ViewStyle>;\n}\n\n/** Reordena moviendo el ítem de `from` a `to`, sin mutar `data`. Exportada aparte por ser testeable sin simular gestos. */\nexport function move<T>(data: T[], from: number, to: number): T[] {\n const next = data.slice();\n const [item] = next.splice(from, 1) as [T];\n next.splice(to, 0, item);\n return next;\n}\n\nconst useStyles = createStyles(() => ({\n row: { width: '100%' },\n}));\n\n/**\n * ReorderableList\n * Ley de destino común (common fate): al arrastrar una fila, las filas que\n * tiene que atravesar se desplazan juntas en el mismo sentido — eso es lo\n * que comunica \"van a intercambiar lugar\" antes de soltar el dedo, no un\n * simple resaltado del ítem activo.\n *\n * Sin dependencias de gestos/animación externas (reanimated, gesture-handler):\n * PanResponder + Animated alcanzan porque el cálculo de índice destino\n * necesita el valor de arrastre en JS en cada movimiento (no algo que\n * useNativeDriver pueda resolver solo).\n *\n * Dentro de un scroll vertical hay que cablear `onDragStart`/`onDragEnd` a\n * `scrollEnabled`, o en iOS el arrastre no llega a empezar:\n *\n * ```tsx\n * const [dragging, setDragging] = useState(false);\n * <ScrollView scrollEnabled={!dragging}>\n * <ReorderableList onDragStart={() => setDragging(true)} onDragEnd={() => setDragging(false)} … />\n * </ScrollView>\n * ```\n */\nexport function ReorderableList<T>({\n data,\n keyExtractor,\n itemHeight,\n renderItem,\n onReorder,\n onDragStart,\n onDragEnd,\n disabled = false,\n style,\n}: ReorderableListProps<T>) {\n const styles = useStyles();\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const [targetIndex, setTargetIndex] = useState<number | null>(null);\n const dy = useRef(new Animated.Value(0)).current;\n const activeIndexRef = useRef<number | null>(null);\n // El PanResponder de cada fila se crea una sola vez (se cachea en el Map de\n // abajo) y su closure queda fijo al render de esa primera vez: leer\n // `targetIndex` (state) directamente ahí siempre daría el valor del\n // montaje. Un ref sí refleja la última escritura sin importar cuándo se\n // creó la closure que lo lee.\n const targetIndexRef = useRef<number | null>(null);\n const dataRef = useRef(data);\n dataRef.current = data;\n // Igual razón que targetIndexRef: el responder de cada fila se crea una\n // sola vez, así que leer `disabled` directo del closure congelaría el\n // valor del momento en que esa fila se creó.\n const disabledRef = useRef(disabled);\n disabledRef.current = disabled;\n // Misma razón: los callbacks se leen desde closures creadas una sola vez.\n const onReorderRef = useRef(onReorder);\n onReorderRef.current = onReorder;\n const onDragStartRef = useRef(onDragStart);\n onDragStartRef.current = onDragStart;\n const onDragEndRef = useRef(onDragEnd);\n onDragEndRef.current = onDragEnd;\n\n const panResponders = useRef(new Map<number, ReturnType<typeof PanResponder.create>>()).current;\n // Si `data` se achica, los responders de índices que ya no existen quedan\n // colgados en el Map para siempre: se podan en cada render.\n for (const index of panResponders.keys()) {\n if (index >= data.length) panResponders.delete(index);\n }\n\n function panResponderFor(index: number) {\n let responder = panResponders.get(index);\n if (responder) return responder;\n\n const endDrag = () => {\n dy.setValue(0);\n activeIndexRef.current = null;\n targetIndexRef.current = null;\n setActiveIndex(null);\n setTargetIndex(null);\n onDragEndRef.current?.();\n };\n\n responder = PanResponder.create({\n onStartShouldSetPanResponder: () => !disabledRef.current,\n onMoveShouldSetPanResponder: (_, gesture) => !disabledRef.current && Math.abs(gesture.dy) > 2,\n // Un scroll padre pide el responder apenas su gesto vertical arranca —\n // el mismo eje que el arrastre. Cederlo cancelaría el drag a mitad de\n // camino, así que la fila se lo queda hasta soltar.\n onPanResponderTerminationRequest: () => false,\n onPanResponderGrant: () => {\n activeIndexRef.current = index;\n targetIndexRef.current = index;\n setActiveIndex(index);\n setTargetIndex(index);\n dy.setValue(0);\n onDragStartRef.current?.(index);\n },\n onPanResponderMove: (_, gesture) => {\n dy.setValue(gesture.dy);\n const from = activeIndexRef.current;\n if (from === null) return;\n const rawTarget = from + Math.round(gesture.dy / itemHeight);\n const clamped = Math.max(0, Math.min(dataRef.current.length - 1, rawTarget));\n targetIndexRef.current = clamped;\n setTargetIndex((prev) => (prev === clamped ? prev : clamped));\n },\n onPanResponderRelease: () => {\n const from = activeIndexRef.current;\n const to = targetIndexRef.current;\n endDrag();\n if (from === null || to === null || from === to) return;\n onReorderRef.current(move(dataRef.current, from, to));\n },\n onPanResponderTerminate: endDrag,\n });\n panResponders.set(index, responder);\n return responder;\n }\n\n return (\n <View style={style}>\n {data.map((item, index) => {\n const isActive = activeIndex === index;\n // Filas entre el origen y el destino se corren un lugar entero en el\n // sentido del arrastre: es el efecto de \"grupo que se mueve junto\".\n let shift = 0;\n if (activeIndex !== null && targetIndex !== null && !isActive) {\n if (activeIndex < targetIndex && index > activeIndex && index <= targetIndex) shift = -itemHeight;\n else if (activeIndex > targetIndex && index < activeIndex && index >= targetIndex) shift = itemHeight;\n }\n\n return (\n <Animated.View\n key={keyExtractor(item, index)}\n style={[\n styles.row,\n isActive\n ? { transform: [{ translateY: dy }], zIndex: 1, elevation: 4 }\n : { transform: [{ translateY: shift }] },\n ]}\n {...panResponderFor(index).panHandlers}\n >\n {renderItem(item, index, isActive)}\n </Animated.View>\n );\n })}\n </View>\n );\n}\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
|
@@ -10,6 +10,15 @@ export interface ReorderableListProps<T> {
|
|
|
10
10
|
renderItem: (item: T, index: number, dragging: boolean) => React.ReactElement;
|
|
11
11
|
/** Se llama al soltar, con el arreglo ya reordenado. No muta `data`. */
|
|
12
12
|
onReorder: (data: T[]) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Empieza el arrastre de la fila `index`. Úsalo para apagar el scroll del
|
|
15
|
+
* contenedor (`scrollEnabled={false}`) mientras dura: en iOS el
|
|
16
|
+
* `UIScrollView` padre cancela los toques del contenido apenas su gesto
|
|
17
|
+
* vertical arranca, y sin eso el arrastre muere a los pocos píxeles.
|
|
18
|
+
*/
|
|
19
|
+
onDragStart?: (index: number) => void;
|
|
20
|
+
/** Termina el arrastre — soltando o por cancelación. Siempre corre si corrió `onDragStart`. */
|
|
21
|
+
onDragEnd?: () => void;
|
|
13
22
|
disabled?: boolean;
|
|
14
23
|
style?: StyleProp<ViewStyle>;
|
|
15
24
|
}
|
|
@@ -37,6 +46,16 @@ const useStyles = createStyles(() => ({
|
|
|
37
46
|
* PanResponder + Animated alcanzan porque el cálculo de índice destino
|
|
38
47
|
* necesita el valor de arrastre en JS en cada movimiento (no algo que
|
|
39
48
|
* useNativeDriver pueda resolver solo).
|
|
49
|
+
*
|
|
50
|
+
* Dentro de un scroll vertical hay que cablear `onDragStart`/`onDragEnd` a
|
|
51
|
+
* `scrollEnabled`, o en iOS el arrastre no llega a empezar:
|
|
52
|
+
*
|
|
53
|
+
* ```tsx
|
|
54
|
+
* const [dragging, setDragging] = useState(false);
|
|
55
|
+
* <ScrollView scrollEnabled={!dragging}>
|
|
56
|
+
* <ReorderableList onDragStart={() => setDragging(true)} onDragEnd={() => setDragging(false)} … />
|
|
57
|
+
* </ScrollView>
|
|
58
|
+
* ```
|
|
40
59
|
*/
|
|
41
60
|
export function ReorderableList<T>({
|
|
42
61
|
data,
|
|
@@ -44,6 +63,8 @@ export function ReorderableList<T>({
|
|
|
44
63
|
itemHeight,
|
|
45
64
|
renderItem,
|
|
46
65
|
onReorder,
|
|
66
|
+
onDragStart,
|
|
67
|
+
onDragEnd,
|
|
47
68
|
disabled = false,
|
|
48
69
|
style,
|
|
49
70
|
}: ReorderableListProps<T>) {
|
|
@@ -65,6 +86,13 @@ export function ReorderableList<T>({
|
|
|
65
86
|
// valor del momento en que esa fila se creó.
|
|
66
87
|
const disabledRef = useRef(disabled);
|
|
67
88
|
disabledRef.current = disabled;
|
|
89
|
+
// Misma razón: los callbacks se leen desde closures creadas una sola vez.
|
|
90
|
+
const onReorderRef = useRef(onReorder);
|
|
91
|
+
onReorderRef.current = onReorder;
|
|
92
|
+
const onDragStartRef = useRef(onDragStart);
|
|
93
|
+
onDragStartRef.current = onDragStart;
|
|
94
|
+
const onDragEndRef = useRef(onDragEnd);
|
|
95
|
+
onDragEndRef.current = onDragEnd;
|
|
68
96
|
|
|
69
97
|
const panResponders = useRef(new Map<number, ReturnType<typeof PanResponder.create>>()).current;
|
|
70
98
|
// Si `data` se achica, los responders de índices que ya no existen quedan
|
|
@@ -77,15 +105,29 @@ export function ReorderableList<T>({
|
|
|
77
105
|
let responder = panResponders.get(index);
|
|
78
106
|
if (responder) return responder;
|
|
79
107
|
|
|
108
|
+
const endDrag = () => {
|
|
109
|
+
dy.setValue(0);
|
|
110
|
+
activeIndexRef.current = null;
|
|
111
|
+
targetIndexRef.current = null;
|
|
112
|
+
setActiveIndex(null);
|
|
113
|
+
setTargetIndex(null);
|
|
114
|
+
onDragEndRef.current?.();
|
|
115
|
+
};
|
|
116
|
+
|
|
80
117
|
responder = PanResponder.create({
|
|
81
118
|
onStartShouldSetPanResponder: () => !disabledRef.current,
|
|
82
119
|
onMoveShouldSetPanResponder: (_, gesture) => !disabledRef.current && Math.abs(gesture.dy) > 2,
|
|
120
|
+
// Un scroll padre pide el responder apenas su gesto vertical arranca —
|
|
121
|
+
// el mismo eje que el arrastre. Cederlo cancelaría el drag a mitad de
|
|
122
|
+
// camino, así que la fila se lo queda hasta soltar.
|
|
123
|
+
onPanResponderTerminationRequest: () => false,
|
|
83
124
|
onPanResponderGrant: () => {
|
|
84
125
|
activeIndexRef.current = index;
|
|
85
126
|
targetIndexRef.current = index;
|
|
86
127
|
setActiveIndex(index);
|
|
87
128
|
setTargetIndex(index);
|
|
88
129
|
dy.setValue(0);
|
|
130
|
+
onDragStartRef.current?.(index);
|
|
89
131
|
},
|
|
90
132
|
onPanResponderMove: (_, gesture) => {
|
|
91
133
|
dy.setValue(gesture.dy);
|
|
@@ -99,21 +141,11 @@ export function ReorderableList<T>({
|
|
|
99
141
|
onPanResponderRelease: () => {
|
|
100
142
|
const from = activeIndexRef.current;
|
|
101
143
|
const to = targetIndexRef.current;
|
|
102
|
-
|
|
103
|
-
activeIndexRef.current = null;
|
|
104
|
-
targetIndexRef.current = null;
|
|
105
|
-
setActiveIndex(null);
|
|
106
|
-
setTargetIndex(null);
|
|
144
|
+
endDrag();
|
|
107
145
|
if (from === null || to === null || from === to) return;
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
onPanResponderTerminate: () => {
|
|
111
|
-
dy.setValue(0);
|
|
112
|
-
activeIndexRef.current = null;
|
|
113
|
-
targetIndexRef.current = null;
|
|
114
|
-
setActiveIndex(null);
|
|
115
|
-
setTargetIndex(null);
|
|
146
|
+
onReorderRef.current(move(dataRef.current, from, to));
|
|
116
147
|
},
|
|
148
|
+
onPanResponderTerminate: endDrag,
|
|
117
149
|
});
|
|
118
150
|
panResponders.set(index, responder);
|
|
119
151
|
return responder;
|