react-crud-mobile 1.3.557 → 1.3.560

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-crud-mobile",
3
- "version": "1.3.557",
3
+ "version": "1.3.560",
4
4
  "license": "MIT",
5
5
  "description": "Uma biblioteca de componentes para React Native v1",
6
6
  "author": "juarez",
@@ -122,7 +122,7 @@ export default function UIButton(props: ChildType) {
122
122
  def = { ...def, borderRadius: 20 };
123
123
  }
124
124
 
125
- let css = style('button', def);
125
+ let css = { ...def, ...style('button') };
126
126
 
127
127
  if (!css.width) {
128
128
  let h = css.height;
@@ -69,7 +69,7 @@ export default function UIModal(props: UIModalType) {
69
69
  );
70
70
  };
71
71
  return (
72
- <Modal key={key} animationType="slide" transparent={true} visible={true} onRequestClose={onClose}>
72
+ <Modal key={key} animationType="slide" visible={true} onRequestClose={onClose}>
73
73
  <ModalInner />
74
74
  </Modal>
75
75
  );
@@ -1,286 +1,250 @@
1
1
  import React, { useRef, useState, useEffect } from 'react';
2
2
  import { ChildType, OptionType, Utils } from 'react-crud-utils';
3
- import {
4
- View,
5
- Text,
6
- StyleSheet,
7
- Animated,
8
- PanResponder,
9
- TouchableOpacity,
10
- } from 'react-native';
3
+ import { View, Text, StyleSheet, Animated, PanResponder, TouchableOpacity } from 'react-native';
11
4
 
12
5
  const ITEM_HEIGHT = 70;
13
6
 
14
7
  export default function UIOrder(props: ChildType) {
15
- const scope = props.scope;
16
- const element = scope.original;
17
- const initial: OptionType[] = scope.getOptions();
18
-
19
- const [items, setItems] = useState<OptionType[]>(initial);
20
- const [draggingId, setDraggingId] = useState<string | null>(null);
21
-
22
- // O tipo Animated.Value em um ambiente TypeScript puro não tem o método ._value,
23
- // mas é a propriedade interna usada para acessar o valor diretamente.
24
- // Usamos 'any' no acesso para suprimir o erro TS.
25
- const positions = useRef<Record<string, Animated.Value>>(
26
- Object.fromEntries(
27
- initial.map((it, i) => [it.value, new Animated.Value(i * ITEM_HEIGHT)])
28
- )
29
- ).current;
30
-
31
- const panRespondersRef = useRef<Record<string, any>>({});
32
-
33
- const activeItem = useRef<OptionType | null>(null);
34
- const activeIndex = useRef<number>(-1);
35
- const offsetY = useRef(0);
36
-
37
- // Armazena a lista mais recente em uma ref para uso em funções de PanResponder
38
- const itemsRef = useRef(items);
39
- useEffect(() => {
40
- itemsRef.current = items;
41
- }, [items]);
42
-
43
- // 1. Atualiza posições apenas para itens existentes
44
- useEffect(() => {
45
- items.forEach((it, idx) => {
46
- if (!positions[it.value])
47
- positions[it.value] = new Animated.Value(idx * ITEM_HEIGHT);
48
-
49
- Animated.spring(positions[it.value], {
50
- toValue: idx * ITEM_HEIGHT,
51
- useNativeDriver: false,
52
- }).start();
53
- });
54
- }, [items]);
55
-
56
- const onChange = (updated: any[]) => {
57
- const array: any[] = [];
58
- const items = scope.getItems();
59
-
60
- items.splice(0);
61
-
62
- Utils.each(updated, o => {
63
- const v = o.object;
64
-
65
- array.push(v);
66
- items.push(v);
67
- });
68
-
69
- scope.changeValue(array);
70
- };
71
-
72
- const createPanResponder = (item: OptionType) =>
73
- PanResponder.create({
74
- onStartShouldSetPanResponder: () => true,
75
- onPanResponderGrant: () => {
76
- // Use itemsRef.current para obter a lista mais recente no momento do arrasto.
77
- const currentItems = itemsRef.current;
78
- if (
79
- !positions[item.value] ||
80
- !currentItems.find(it => it.value === item.value)
81
- )
82
- return;
83
-
84
- activeItem.current = item;
85
- activeIndex.current = currentItems.findIndex(
86
- it => it.value === item.value
87
- );
88
- // CORREÇÃO: Usa a propriedade interna _value para acessar o valor atual
89
- offsetY.current = (positions[item.value] as any)._value; // 👈 CORREÇÃO APLICADA
90
- setDraggingId(item.value);
91
- },
92
- onPanResponderMove: (_, gesture) => {
93
- const currentItems = itemsRef.current;
94
- if (
95
- !positions[item.value] ||
96
- !currentItems.find(it => it.value === item.value)
97
- )
98
- return;
99
-
100
- const y = offsetY.current + gesture.dy;
101
- positions[item.value].setValue(y);
102
-
103
- const targetIndex = Math.max(
104
- 0,
105
- Math.min(
106
- currentItems.length - 1,
107
- Math.floor((y + ITEM_HEIGHT / 2) / ITEM_HEIGHT)
108
- )
109
- );
110
-
111
- currentItems.forEach((other, idx) => {
112
- if (other.value === item.value || !positions[other.value]) return;
113
- let targetY = idx * ITEM_HEIGHT;
114
- if (idx > activeIndex.current && idx <= targetIndex)
115
- targetY = (idx - 1) * ITEM_HEIGHT;
116
- else if (idx < activeIndex.current && idx >= targetIndex)
117
- targetY = (idx + 1) * ITEM_HEIGHT;
118
-
119
- Animated.timing(positions[other.value], {
120
- toValue: targetY,
121
- duration: 120,
122
- useNativeDriver: false,
123
- }).start();
124
- });
125
- },
126
- onPanResponderRelease: () => {
127
- const moved = activeItem.current;
128
- const currentItems = itemsRef.current;
129
-
130
- if (
131
- !moved ||
132
- !positions[moved.value] ||
133
- !currentItems.find(it => it.value === moved.value)
134
- )
135
- return;
136
-
137
- // CORREÇÃO: Usa a propriedade interna _value para obter a posição final
138
- const finalY = (positions[moved.value] as any)._value; // 👈 CORREÇÃO APLICADA
139
- const newIndex = Math.max(
140
- 0,
141
- Math.min(
142
- currentItems.length - 1,
143
- Math.floor((finalY + ITEM_HEIGHT / 2) / ITEM_HEIGHT)
144
- )
145
- );
146
-
147
- const oldIndex = activeIndex.current;
148
- const updated = [...currentItems];
149
- const [removed] = updated.splice(oldIndex, 1);
150
- updated.splice(newIndex, 0, removed);
151
-
152
- // Atualiza o state com a nova ordem.
153
- setItems(updated);
154
- onChange(updated);
155
-
156
- // A animação final agora está no useEffect, mas fazemos a limpeza das refs.
157
- setDraggingId(null);
158
- activeItem.current = null;
159
- activeIndex.current = -1;
160
- },
161
- });
162
-
163
- // Cria/Reusa os PanResponders
164
- items.forEach(it => {
165
- if (!panRespondersRef.current[it.value]) {
166
- panRespondersRef.current[it.value] = createPanResponder(it);
167
- }
168
- });
169
-
170
- const removeItem = (id: string) => {
171
- if (draggingId === id) {
172
- setDraggingId(null);
173
- activeItem.current = null;
174
- activeIndex.current = -1;
175
- }
176
-
177
- // CORREÇÃO: Delete a referência Animated.Value *e* a referência PanResponder
178
- if (positions[id]) {
179
- delete positions[id];
180
- }
181
- if (panRespondersRef.current[id]) {
182
- delete panRespondersRef.current[id];
183
- }
184
-
185
- setItems(prev => {
186
- const updated = prev.filter(it => it.value !== id);
187
-
188
- // Anima os itens restantes para seus novos índices
189
- updated.forEach((it, idx) => {
190
- if (positions[it.value])
191
- Animated.spring(positions[it.value], {
8
+ const scope = props.scope;
9
+ const element = scope.original;
10
+ const initial: OptionType[] = scope.getOptions();
11
+
12
+ const [items, setItems] = useState<OptionType[]>(initial);
13
+ const [draggingId, setDraggingId] = useState<string | null>(null);
14
+
15
+ // O tipo Animated.Value em um ambiente TypeScript puro não tem o método ._value,
16
+ // mas é a propriedade interna usada para acessar o valor diretamente.
17
+ // Usamos 'any' no acesso para suprimir o erro TS.
18
+ const positions = useRef<Record<string, Animated.Value>>(
19
+ Object.fromEntries(initial.map((it, i) => [it.value, new Animated.Value(i * ITEM_HEIGHT)])),
20
+ ).current;
21
+
22
+ const panRespondersRef = useRef<Record<string, any>>({});
23
+
24
+ const activeItem = useRef<OptionType | null>(null);
25
+ const activeIndex = useRef<number>(-1);
26
+ const offsetY = useRef(0);
27
+
28
+ // Armazena a lista mais recente em uma ref para uso em funções de PanResponder
29
+ const itemsRef = useRef(items);
30
+ useEffect(() => {
31
+ itemsRef.current = items;
32
+ }, [items]);
33
+
34
+ // 1. Atualiza posições apenas para itens existentes
35
+ useEffect(() => {
36
+ items.forEach((it, idx) => {
37
+ if (!positions[it.value]) positions[it.value] = new Animated.Value(idx * ITEM_HEIGHT);
38
+
39
+ Animated.spring(positions[it.value], {
192
40
  toValue: idx * ITEM_HEIGHT,
193
41
  useNativeDriver: false,
194
- }).start();
42
+ }).start();
195
43
  });
44
+ }, [items]);
45
+
46
+ const onChange = (updated: any[]) => {
47
+ const array: any[] = [];
48
+ const items = scope.getItems();
49
+
50
+ items.splice(0);
196
51
 
197
- onChange(updated);
198
- return updated;
199
- });
200
- };
201
-
202
- const Empty = () => {
203
- if (Utils.isEmpty(items)) {
204
- let empty = scope.part('empty', 'Sem registro');
205
-
206
- if (empty !== false) return <>{empty}</>;
207
- }
208
-
209
- return <></>;
210
- };
211
-
212
- return (
213
- <View style={styles.container}>
214
- <Empty />
215
- <View style={{ height: items.length * ITEM_HEIGHT, width: '100%' }}>
216
- {items.map(item => {
217
- const y = positions[item.value];
218
- if (!y) return null;
219
-
220
- const isDragging = draggingId === item.value;
221
- const itemStyle = scope.getStyle('row', { ...styles.item });
222
-
223
- return (
224
- <Animated.View
225
- key={item.value}
226
- {...(panRespondersRef.current[item.value]?.panHandlers ?? {})}
227
- style={[
228
- itemStyle,
229
- {
230
- position: 'absolute',
231
- left: 0,
232
- right: 0,
233
- height: ITEM_HEIGHT - 8,
234
- transform: [{ translateY: y }],
235
- zIndex: isDragging ? 999 : 0,
236
- elevation: isDragging ? 999 : 0,
237
- },
238
- ]}
239
- >
240
- <View style={styles.handle}>
241
- <Text style={styles.handleText}>≡</Text>
242
- </View>
243
- <Text style={styles.itemText}>{item.label}</Text>
244
- {element.remove && (
245
- <TouchableOpacity
246
- onPress={() => removeItem(item.value)}
247
- style={styles.del}
248
- >
249
- <Text style={{ color: '#fff' }}>X</Text>
250
- </TouchableOpacity>
251
- )}
252
- </Animated.View>
253
- );
254
- })}
52
+ Utils.each(updated, (o) => {
53
+ const v = o.object;
54
+
55
+ array.push(v);
56
+ items.push(v);
57
+ });
58
+
59
+ scope.changeValue(array);
60
+ };
61
+
62
+ const createPanResponder = (item: OptionType) =>
63
+ PanResponder.create({
64
+ onStartShouldSetPanResponder: () => true,
65
+ onPanResponderGrant: () => {
66
+ // Use itemsRef.current para obter a lista mais recente no momento do arrasto.
67
+ const currentItems = itemsRef.current;
68
+ if (!positions[item.value] || !currentItems.find((it) => it.value === item.value)) return;
69
+
70
+ activeItem.current = item;
71
+ activeIndex.current = currentItems.findIndex((it) => it.value === item.value);
72
+ // CORREÇÃO: Usa a propriedade interna _value para acessar o valor atual
73
+ offsetY.current = (positions[item.value] as any)._value; // 👈 CORREÇÃO APLICADA
74
+ setDraggingId(item.value);
75
+ },
76
+ onPanResponderMove: (_, gesture) => {
77
+ const currentItems = itemsRef.current;
78
+ if (!positions[item.value] || !currentItems.find((it) => it.value === item.value)) return;
79
+
80
+ const y = offsetY.current + gesture.dy;
81
+ positions[item.value].setValue(y);
82
+
83
+ const targetIndex = Math.max(
84
+ 0,
85
+ Math.min(currentItems.length - 1, Math.floor((y + ITEM_HEIGHT / 2) / ITEM_HEIGHT)),
86
+ );
87
+
88
+ currentItems.forEach((other, idx) => {
89
+ if (other.value === item.value || !positions[other.value]) return;
90
+ let targetY = idx * ITEM_HEIGHT;
91
+ if (idx > activeIndex.current && idx <= targetIndex) targetY = (idx - 1) * ITEM_HEIGHT;
92
+ else if (idx < activeIndex.current && idx >= targetIndex) targetY = (idx + 1) * ITEM_HEIGHT;
93
+
94
+ Animated.timing(positions[other.value], {
95
+ toValue: targetY,
96
+ duration: 120,
97
+ useNativeDriver: false,
98
+ }).start();
99
+ });
100
+ },
101
+ onPanResponderRelease: () => {
102
+ const moved = activeItem.current;
103
+ const currentItems = itemsRef.current;
104
+
105
+ if (!moved || !positions[moved.value] || !currentItems.find((it) => it.value === moved.value)) return;
106
+
107
+ // CORREÇÃO: Usa a propriedade interna _value para obter a posição final
108
+ const finalY = (positions[moved.value] as any)._value; // 👈 CORREÇÃO APLICADA
109
+ const newIndex = Math.max(
110
+ 0,
111
+ Math.min(currentItems.length - 1, Math.floor((finalY + ITEM_HEIGHT / 2) / ITEM_HEIGHT)),
112
+ );
113
+
114
+ const oldIndex = activeIndex.current;
115
+ const updated = [...currentItems];
116
+ const [removed] = updated.splice(oldIndex, 1);
117
+ updated.splice(newIndex, 0, removed);
118
+
119
+ // Atualiza o state com a nova ordem.
120
+ setItems(updated);
121
+ onChange(updated);
122
+
123
+ // A animação final agora está no useEffect, mas fazemos a limpeza das refs.
124
+ setDraggingId(null);
125
+ activeItem.current = null;
126
+ activeIndex.current = -1;
127
+ },
128
+ });
129
+
130
+ // Cria/Reusa os PanResponders
131
+ items.forEach((it) => {
132
+ if (!panRespondersRef.current[it.value]) {
133
+ panRespondersRef.current[it.value] = createPanResponder(it);
134
+ }
135
+ });
136
+
137
+ const removeItem = (id: string) => {
138
+ if (draggingId === id) {
139
+ setDraggingId(null);
140
+ activeItem.current = null;
141
+ activeIndex.current = -1;
142
+ }
143
+
144
+ // CORREÇÃO: Delete a referência Animated.Value *e* a referência PanResponder
145
+ if (positions[id]) {
146
+ delete positions[id];
147
+ }
148
+ if (panRespondersRef.current[id]) {
149
+ delete panRespondersRef.current[id];
150
+ }
151
+
152
+ setItems((prev) => {
153
+ const updated = prev.filter((it) => it.value !== id);
154
+
155
+ // Anima os itens restantes para seus novos índices
156
+ updated.forEach((it, idx) => {
157
+ if (positions[it.value])
158
+ Animated.spring(positions[it.value], {
159
+ toValue: idx * ITEM_HEIGHT,
160
+ useNativeDriver: false,
161
+ }).start();
162
+ });
163
+
164
+ onChange(updated);
165
+ return updated;
166
+ });
167
+ };
168
+
169
+ const Empty = () => {
170
+ if (Utils.isEmpty(items)) {
171
+ let empty = scope.part('empty', 'Sem registro');
172
+
173
+ if (empty !== false) return <>{empty}</>;
174
+ }
175
+
176
+ return <></>;
177
+ };
178
+
179
+ return (
180
+ <View style={styles.container}>
181
+ <Empty />
182
+ <View style={{ height: items.length * ITEM_HEIGHT, width: '100%', ...scope.getStyle('container') }}>
183
+ {items.map((item) => {
184
+ const y = positions[item.value];
185
+ if (!y) return null;
186
+
187
+ const isDragging = draggingId === item.value;
188
+ const itemStyle = scope.getStyle('row', { ...styles.item });
189
+
190
+ return (
191
+ <Animated.View
192
+ key={item.value}
193
+ {...(panRespondersRef.current[item.value]?.panHandlers ?? {})}
194
+ style={[
195
+ itemStyle,
196
+ {
197
+ position: 'absolute',
198
+ left: 0,
199
+ right: 0,
200
+ height: ITEM_HEIGHT - 8,
201
+ transform: [{ translateY: y }],
202
+ zIndex: isDragging ? 999 : 0,
203
+ elevation: isDragging ? 999 : 0,
204
+ },
205
+ ]}
206
+ >
207
+ <View style={styles.handle}>
208
+ <Text style={styles.handleText}>≡</Text>
209
+ </View>
210
+ <Text style={styles.itemText}>{item.label}</Text>
211
+ {element.remove && (
212
+ <TouchableOpacity onPress={() => removeItem(item.value)} style={styles.del}>
213
+ <Text style={{ color: '#fff' }}>X</Text>
214
+ </TouchableOpacity>
215
+ )}
216
+ </Animated.View>
217
+ );
218
+ })}
219
+ </View>
255
220
  </View>
256
- </View>
257
- );
221
+ );
258
222
  }
259
223
 
260
224
  const styles = StyleSheet.create({
261
- container: { flex: 1 },
262
- title: { fontSize: 18, fontWeight: '600', marginBottom: 12 },
263
- item: {
264
- backgroundColor: '#fff',
265
- borderRadius: 10,
266
- marginVertical: 4,
267
- paddingHorizontal: 12,
268
- alignItems: 'center',
269
- flexDirection: 'row',
270
- elevation: 3,
271
- shadowColor: '#000',
272
- shadowOpacity: 0.05,
273
- shadowRadius: 6,
274
- },
275
- handle: { width: 40, alignItems: 'center', justifyContent: 'center' },
276
- handleText: { fontSize: 22, color: '#666' },
277
- itemText: { fontSize: 16, flex: 1 },
278
- del: {
279
- backgroundColor: '#e74c3c',
280
- height: 34,
281
- width: 34,
282
- borderRadius: 6,
283
- alignItems: 'center',
284
- justifyContent: 'center',
285
- },
225
+ container: { flex: 1 },
226
+ title: { fontSize: 18, fontWeight: '600', marginBottom: 12 },
227
+ item: {
228
+ backgroundColor: '#fff',
229
+ borderRadius: 10,
230
+ marginVertical: 4,
231
+ paddingHorizontal: 12,
232
+ alignItems: 'center',
233
+ flexDirection: 'row',
234
+ elevation: 3,
235
+ shadowColor: '#000',
236
+ shadowOpacity: 0.05,
237
+ shadowRadius: 6,
238
+ },
239
+ handle: { width: 40, alignItems: 'center', justifyContent: 'center' },
240
+ handleText: { fontSize: 22, color: '#666' },
241
+ itemText: { fontSize: 16, flex: 1 },
242
+ del: {
243
+ backgroundColor: '#e74c3c',
244
+ height: 34,
245
+ width: 34,
246
+ borderRadius: 6,
247
+ alignItems: 'center',
248
+ justifyContent: 'center',
249
+ },
286
250
  });