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.
@@ -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
- dy.setValue(0);
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
- onReorder(move(dataRef.current, from, to));
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","onPanResponderGrant","setValue","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":[]}
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
- dy.setValue(0);
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
- onReorder(move(dataRef.current, from, to));
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","onPanResponderGrant","setValue","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":[]}
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;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,EACjC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,UAAU,EACV,SAAS,EACT,QAAgB,EAChB,KAAK,GACN,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAsGzB"}
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.4",
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",
@@ -552,7 +552,7 @@
552
552
  "registryDependencies": [
553
553
  "core"
554
554
  ],
555
- "bytes": 5899
555
+ "bytes": 7326
556
556
  }
557
557
  ]
558
558
  }
@@ -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 dy.setValue(0);\n activeIndexRef.current = null;\n targetIndexRef.current = null;\n setActiveIndex(null);\n setTargetIndex(null);\n if (from === null || to === null || from === to) return;\n onReorder(move(dataRef.current, from, to));\n },\n onPanResponderTerminate: () => {\n dy.setValue(0);\n activeIndexRef.current = null;\n targetIndexRef.current = null;\n setActiveIndex(null);\n setTargetIndex(null);\n },\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"
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
- dy.setValue(0);
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
- onReorder(move(dataRef.current, from, to));
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;