@darelmasis/signpad 1.0.0
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/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/signpad.css +1 -0
- package/dist/signpad.es.js +1321 -0
- package/dist/signpad.es.js.map +1 -0
- package/dist/signpad.umd.js +8 -0
- package/dist/signpad.umd.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SignPad Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# SignPad
|
|
2
|
+
|
|
3
|
+
> Librería React headless para captura de firmas digitales con trazos realistas usando perfect-freehand.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/signpad)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://bundlephobia.com/package/signpad)
|
|
8
|
+
|
|
9
|
+
## Características
|
|
10
|
+
|
|
11
|
+
- **Trazos Realistas** - Powered by `perfect-freehand`
|
|
12
|
+
- **Headless** - Sin UI predefinida, 100% personalizable
|
|
13
|
+
- **Responsive** - Width 100% por defecto, adaptable
|
|
14
|
+
- **Exportación Inteligente** - Solo guarda trazos, sin márgenes innecesarios
|
|
15
|
+
- **Alta Calidad** - Exportación 3x con anti-aliasing
|
|
16
|
+
- **Touch Optimizado** - Soporte completo táctil con presión
|
|
17
|
+
- **Ultra Ligero** - Solo 2 kB gzipped
|
|
18
|
+
- **Accesible** - ARIA labels incluidos
|
|
19
|
+
- **API Simple** - Métodos vía ref
|
|
20
|
+
|
|
21
|
+
## Instalación
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install signpad
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Uso Básico
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
import React, { useRef } from 'react';
|
|
31
|
+
import { SignPad } from 'signpad';
|
|
32
|
+
import 'signpad/signpad.css';
|
|
33
|
+
|
|
34
|
+
function App() {
|
|
35
|
+
const signPadRef = useRef(null);
|
|
36
|
+
|
|
37
|
+
const handleSave = async () => {
|
|
38
|
+
const dataUrl = await signPadRef.current?.save('png');
|
|
39
|
+
console.log('Firma guardada:', dataUrl);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div>
|
|
44
|
+
<SignPad ref={signPadRef} height={300} />
|
|
45
|
+
|
|
46
|
+
{/* Tus botones personalizados */}
|
|
47
|
+
<button onClick={() => signPadRef.current?.clear()}>
|
|
48
|
+
Limpiar
|
|
49
|
+
</button>
|
|
50
|
+
<button onClick={handleSave}>
|
|
51
|
+
Guardar
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Props
|
|
59
|
+
|
|
60
|
+
| Prop | Tipo | Default | Descripción |
|
|
61
|
+
|------|------|---------|-------------|
|
|
62
|
+
| `width` | `string\|number` | `'100%'` | Ancho del canvas |
|
|
63
|
+
| `height` | `number` | `300` | Alto en píxeles |
|
|
64
|
+
| `penColor` | `string` | `'#0004a6'` | Color del trazo |
|
|
65
|
+
| `penSize` | `number` | `2` | Tamaño base del trazo (1-20) |
|
|
66
|
+
| `thinning` | `number` | `0.5` | Adelgazamiento por velocidad (0-1) |
|
|
67
|
+
| `smoothing` | `number` | `0.5` | Suavizado de curvas (0-1) |
|
|
68
|
+
| `streamline` | `number` | `0.5` | Estabilización del trazo (0-1) |
|
|
69
|
+
| `backgroundColor` | `string` | `'#ffffff'` | Color de fondo (solo para JPG) |
|
|
70
|
+
| `onSave` | `function` | - | Callback: `(dataUrl, format) => void` |
|
|
71
|
+
| `onClear` | `function` | - | Callback: `() => void` |
|
|
72
|
+
| `onChange` | `function` | - | Callback al dibujar/borrar |
|
|
73
|
+
| `disabled` | `boolean` | `false` | Deshabilita interacción |
|
|
74
|
+
| `className` | `string` | `''` | Clases CSS adicionales |
|
|
75
|
+
|
|
76
|
+
## Métodos (via ref)
|
|
77
|
+
|
|
78
|
+
### `clear()`
|
|
79
|
+
Limpia completamente el canvas.
|
|
80
|
+
```jsx
|
|
81
|
+
signPadRef.current?.clear();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `undo()`
|
|
85
|
+
Deshace el último trazo dibujado.
|
|
86
|
+
```jsx
|
|
87
|
+
signPadRef.current?.undo();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `save(format, quality)`
|
|
91
|
+
Guarda la firma y retorna un DataURL.
|
|
92
|
+
- **Parámetros:**
|
|
93
|
+
- `format`: `'png'` | `'jpg'` | `'svg'` (default: `'png'`)
|
|
94
|
+
- `quality`: `0-1` (default: `1.0`)
|
|
95
|
+
- **Retorna:** `Promise<string | null>`
|
|
96
|
+
|
|
97
|
+
```jsx
|
|
98
|
+
const dataUrl = await signPadRef.current?.save('png', 1.0);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `download(filename, format)`
|
|
102
|
+
Descarga la firma automáticamente.
|
|
103
|
+
```jsx
|
|
104
|
+
signPadRef.current?.download('mi-firma', 'png');
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `toBlob(format, quality)`
|
|
108
|
+
Convierte la firma a Blob (útil para uploads).
|
|
109
|
+
```jsx
|
|
110
|
+
const blob = await signPadRef.current?.toBlob('png');
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `isEmpty()`
|
|
114
|
+
Verifica si el canvas está vacío.
|
|
115
|
+
```jsx
|
|
116
|
+
if (signPadRef.current?.isEmpty()) {
|
|
117
|
+
alert('Por favor dibuja una firma');
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `getSvg()`
|
|
122
|
+
Obtiene el elemento SVG del DOM.
|
|
123
|
+
```jsx
|
|
124
|
+
const svgElement = signPadRef.current?.getSvg();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Ejemplos
|
|
128
|
+
|
|
129
|
+
### Con Hook `useSignPad` (Opcional)
|
|
130
|
+
|
|
131
|
+
```jsx
|
|
132
|
+
import { SignPad, useSignPad } from 'signpad';
|
|
133
|
+
|
|
134
|
+
function App() {
|
|
135
|
+
const { signPadProps, clear, save, isEmpty } = useSignPad({
|
|
136
|
+
onSave: (dataUrl) => console.log('Guardado:', dataUrl),
|
|
137
|
+
onClear: () => console.log('Limpiado')
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div>
|
|
142
|
+
<SignPad {...signPadProps} height={300} />
|
|
143
|
+
<button onClick={clear} disabled={isEmpty}>Limpiar</button>
|
|
144
|
+
<button onClick={() => save('png')} disabled={isEmpty}>Guardar</button>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### En un Formulario
|
|
151
|
+
|
|
152
|
+
```jsx
|
|
153
|
+
function SignatureForm() {
|
|
154
|
+
const signPadRef = useRef(null);
|
|
155
|
+
const [signature, setSignature] = useState(null);
|
|
156
|
+
|
|
157
|
+
const handleSubmit = async (e) => {
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
|
|
160
|
+
if (signPadRef.current?.isEmpty()) {
|
|
161
|
+
alert('Por favor firma el documento');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const dataUrl = await signPadRef.current?.save('png');
|
|
166
|
+
setSignature(dataUrl);
|
|
167
|
+
// Enviar formulario...
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<form onSubmit={handleSubmit}>
|
|
172
|
+
<label>Firma:</label>
|
|
173
|
+
<SignPad ref={signPadRef} height={200} />
|
|
174
|
+
|
|
175
|
+
<button type="button" onClick={() => signPadRef.current?.clear()}>
|
|
176
|
+
Limpiar
|
|
177
|
+
</button>
|
|
178
|
+
<button type="submit">Enviar</button>
|
|
179
|
+
</form>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### En un Modal Personalizado
|
|
185
|
+
|
|
186
|
+
```jsx
|
|
187
|
+
import Modal from 'tu-libreria-modal'; // Usa tu modal favorito
|
|
188
|
+
|
|
189
|
+
function SignatureModal({ isOpen, onClose }) {
|
|
190
|
+
const signPadRef = useRef(null);
|
|
191
|
+
|
|
192
|
+
const handleSave = async () => {
|
|
193
|
+
const dataUrl = await signPadRef.current?.save('png');
|
|
194
|
+
// Hacer algo con la firma
|
|
195
|
+
onClose();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<Modal isOpen={isOpen} onClose={onClose}>
|
|
200
|
+
<h2>Firma Digital</h2>
|
|
201
|
+
<SignPad ref={signPadRef} height={300} />
|
|
202
|
+
|
|
203
|
+
<button onClick={handleSave}>Guardar</button>
|
|
204
|
+
<button onClick={onClose}>Cancelar</button>
|
|
205
|
+
</Modal>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Upload a Servidor
|
|
211
|
+
|
|
212
|
+
```jsx
|
|
213
|
+
const handleUpload = async () => {
|
|
214
|
+
const blob = await signPadRef.current?.toBlob('png');
|
|
215
|
+
|
|
216
|
+
const formData = new FormData();
|
|
217
|
+
formData.append('signature', blob, 'signature.png');
|
|
218
|
+
|
|
219
|
+
await fetch('/api/upload', {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
body: formData
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Personalización
|
|
227
|
+
|
|
228
|
+
### Colores y Grosor
|
|
229
|
+
|
|
230
|
+
```jsx
|
|
231
|
+
<SignPad
|
|
232
|
+
penColor="#2196f3"
|
|
233
|
+
penSize={3}
|
|
234
|
+
backgroundColor="#f5f5f5"
|
|
235
|
+
thinning={0.7}
|
|
236
|
+
smoothing={0.8}
|
|
237
|
+
streamline={0.6}
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Estilos CSS Personalizados
|
|
242
|
+
|
|
243
|
+
```jsx
|
|
244
|
+
<SignPad className="mi-firma-custom" height={250} />
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```css
|
|
248
|
+
.mi-firma-custom .signpad-canvas {
|
|
249
|
+
border: 2px solid #2196f3;
|
|
250
|
+
border-radius: 12px;
|
|
251
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Exportación en Diferentes Formatos
|
|
256
|
+
|
|
257
|
+
```jsx
|
|
258
|
+
// PNG transparente (recomendado)
|
|
259
|
+
const pngUrl = await save('png', 1.0);
|
|
260
|
+
|
|
261
|
+
// JPG con fondo blanco
|
|
262
|
+
const jpgUrl = await save('jpg', 0.95);
|
|
263
|
+
|
|
264
|
+
// SVG vectorial (escalable infinitamente)
|
|
265
|
+
const svgUrl = await save('svg');
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Filosofía Headless
|
|
269
|
+
|
|
270
|
+
SignPad es una librería **headless** - proporciona la funcionalidad sin imponer diseños.
|
|
271
|
+
|
|
272
|
+
**Tú controlas:**
|
|
273
|
+
- 🎨 Diseño de botones
|
|
274
|
+
- 📦 Contenedores y modales
|
|
275
|
+
- ✅ Validaciones
|
|
276
|
+
- 🎯 Flujo de usuario
|
|
277
|
+
- 🌈 Branding completo
|
|
278
|
+
|
|
279
|
+
**La librería proporciona:**
|
|
280
|
+
- Canvas SVG con trazos realistas
|
|
281
|
+
- Métodos para control programático
|
|
282
|
+
- Exportación en alta calidad
|
|
283
|
+
- Estilos CSS mínimos
|
|
284
|
+
|
|
285
|
+
## Bundle Size
|
|
286
|
+
|
|
287
|
+
- **ES Module:** 44.50 kB
|
|
288
|
+
- **Gzipped:** 2.01 kB (0.72 kB gzip)
|
|
289
|
+
- **CSS:** 2.01 kB
|
|
290
|
+
|
|
291
|
+
Ultra ligero y optimizado para producción.
|
|
292
|
+
|
|
293
|
+
## Contribuir
|
|
294
|
+
|
|
295
|
+
Las contribuciones son bienvenidas:
|
|
296
|
+
|
|
297
|
+
1. Fork el repositorio
|
|
298
|
+
2. Crea una rama (`git checkout -b feature/amazing`)
|
|
299
|
+
3. Commit tus cambios (`git commit -m 'Add amazing feature'`)
|
|
300
|
+
4. Push a la rama (`git push origin feature/amazing`)
|
|
301
|
+
5. Abre un Pull Request
|
|
302
|
+
|
|
303
|
+
## Licencia
|
|
304
|
+
|
|
305
|
+
MIT © [Darel Masis](https://github.com/darelmasis)
|
|
306
|
+
|
|
307
|
+
## Créditos
|
|
308
|
+
|
|
309
|
+
- [perfect-freehand](https://github.com/steveruizok/perfect-freehand) - Trazos realistas
|
|
310
|
+
- [React](https://react.dev) - Framework base
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
**¿Problemas o sugerencias?** [Abre un issue](https://github.com/darelmasis/signpad/issues)
|
package/dist/signpad.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.signpad-container{display:flex;flex-direction:column;width:100%;position:relative;user-select:none;-webkit-user-select:none;touch-action:none}.signpad-canvas{display:block;width:100%;touch-action:none;border-radius:4px;background-color:#fff}.signpad-disabled{opacity:.6;pointer-events:none;cursor:not-allowed!important}
|