canvasframework 0.3.6
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/README.md +554 -0
- package/components/Accordion.js +252 -0
- package/components/AndroidDatePickerDialog.js +398 -0
- package/components/AppBar.js +225 -0
- package/components/Avatar.js +202 -0
- package/components/BottomNavigationBar.js +205 -0
- package/components/BottomSheet.js +374 -0
- package/components/Button.js +225 -0
- package/components/Card.js +193 -0
- package/components/Checkbox.js +180 -0
- package/components/Chip.js +212 -0
- package/components/CircularProgress.js +143 -0
- package/components/ContextMenu.js +116 -0
- package/components/DatePicker.js +257 -0
- package/components/Dialog.js +367 -0
- package/components/Divider.js +125 -0
- package/components/Drawer.js +261 -0
- package/components/FAB.js +270 -0
- package/components/FileUpload.js +315 -0
- package/components/IOSDatePickerWheel.js +268 -0
- package/components/ImageCarousel.js +193 -0
- package/components/ImageComponent.js +223 -0
- package/components/Input.js +309 -0
- package/components/List.js +94 -0
- package/components/ListItem.js +223 -0
- package/components/Modal.js +364 -0
- package/components/MultiSelectDialog.js +206 -0
- package/components/NumberInput.js +271 -0
- package/components/ProgressBar.js +88 -0
- package/components/RadioButton.js +142 -0
- package/components/SearchInput.js +315 -0
- package/components/SegmentedControl.js +202 -0
- package/components/Select.js +199 -0
- package/components/SelectDialog.js +255 -0
- package/components/Slider.js +113 -0
- package/components/Snackbar.js +243 -0
- package/components/Stepper.js +281 -0
- package/components/SwipeableListItem.js +179 -0
- package/components/Switch.js +147 -0
- package/components/Table.js +492 -0
- package/components/Tabs.js +125 -0
- package/components/Text.js +141 -0
- package/components/TextField.js +331 -0
- package/components/Toast.js +236 -0
- package/components/TreeView.js +420 -0
- package/components/Video.js +397 -0
- package/components/View.js +140 -0
- package/components/VirtualList.js +120 -0
- package/core/CanvasFramework.js +1271 -0
- package/core/CanvasWork.js +32 -0
- package/core/Component.js +153 -0
- package/core/LogicWorker.js +25 -0
- package/core/WebGLCanvasAdapter.js +1369 -0
- package/features/Column.js +43 -0
- package/features/Grid.js +47 -0
- package/features/LayoutComponent.js +43 -0
- package/features/OpenStreetMap.js +310 -0
- package/features/Positioned.js +33 -0
- package/features/PullToRefresh.js +328 -0
- package/features/Row.js +40 -0
- package/features/SignaturePad.js +257 -0
- package/features/Skeleton.js +84 -0
- package/features/Stack.js +21 -0
- package/index.js +101 -0
- package/manager/AccessibilityManager.js +107 -0
- package/manager/ErrorHandler.js +59 -0
- package/manager/FeatureFlags.js +60 -0
- package/manager/MemoryManager.js +107 -0
- package/manager/PerformanceMonitor.js +84 -0
- package/manager/SecurityManager.js +54 -0
- package/package.json +28 -0
- package/utils/AnimationEngine.js +428 -0
- package/utils/DataStore.js +403 -0
- package/utils/EventBus.js +407 -0
- package/utils/FetchClient.js +74 -0
- package/utils/FormValidator.js +355 -0
- package/utils/GeoLocationService.js +62 -0
- package/utils/I18n.js +207 -0
- package/utils/IndexedDBManager.js +273 -0
- package/utils/OfflineSyncManager.js +342 -0
- package/utils/QueryBuilder.js +478 -0
- package/utils/SafeArea.js +64 -0
- package/utils/SecureStorage.js +289 -0
- package/utils/StateManager.js +207 -0
- package/utils/WebSocketClient.js +66 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Composant Pull-to-Refresh complètement autonome
|
|
5
|
+
* Intercepte les événements touch/mouse directement
|
|
6
|
+
* @class
|
|
7
|
+
* @extends Component
|
|
8
|
+
*/
|
|
9
|
+
class PullToRefresh extends Component {
|
|
10
|
+
constructor(framework, options = {}) {
|
|
11
|
+
super(framework, {
|
|
12
|
+
x: 0,
|
|
13
|
+
y: -100,
|
|
14
|
+
width: framework.width,
|
|
15
|
+
height: 100
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
this.framework = framework;
|
|
19
|
+
this.onRefresh = options.onRefresh;
|
|
20
|
+
this.state = 'idle';
|
|
21
|
+
this.pullDistance = 0;
|
|
22
|
+
this.refreshThreshold = options.refreshThreshold || 80;
|
|
23
|
+
this.isRefreshing = false;
|
|
24
|
+
|
|
25
|
+
// ✅ Propres gestionnaires d'événements
|
|
26
|
+
this.myIsDragging = false;
|
|
27
|
+
this.startY = 0;
|
|
28
|
+
this.currentY = 0;
|
|
29
|
+
this.canPull = false; // Seulement si on commence en haut
|
|
30
|
+
|
|
31
|
+
// ✅ Installer nos propres écouteurs d'événements
|
|
32
|
+
this.setupOwnEventListeners();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configure les écouteurs d'événements propres au composant
|
|
37
|
+
*/
|
|
38
|
+
setupOwnEventListeners() {
|
|
39
|
+
const canvas = this.framework.canvas;
|
|
40
|
+
|
|
41
|
+
// Touch events
|
|
42
|
+
this.handleTouchStartBound = this.handleOwnTouchStart.bind(this);
|
|
43
|
+
this.handleTouchMoveBound = this.handleOwnTouchMove.bind(this);
|
|
44
|
+
this.handleTouchEndBound = this.handleOwnTouchEnd.bind(this);
|
|
45
|
+
|
|
46
|
+
canvas.addEventListener('touchstart', this.handleTouchStartBound, { passive: false });
|
|
47
|
+
canvas.addEventListener('touchmove', this.handleTouchMoveBound, { passive: false });
|
|
48
|
+
canvas.addEventListener('touchend', this.handleTouchEndBound, { passive: false });
|
|
49
|
+
|
|
50
|
+
// Mouse events (pour desktop)
|
|
51
|
+
this.handleMouseDownBound = this.handleOwnMouseDown.bind(this);
|
|
52
|
+
this.handleMouseMoveBound = this.handleOwnMouseMove.bind(this);
|
|
53
|
+
this.handleMouseUpBound = this.handleOwnMouseUp.bind(this);
|
|
54
|
+
|
|
55
|
+
canvas.addEventListener('mousedown', this.handleMouseDownBound);
|
|
56
|
+
canvas.addEventListener('mousemove', this.handleMouseMoveBound);
|
|
57
|
+
canvas.addEventListener('mouseup', this.handleMouseUpBound);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Gère le début du touch
|
|
62
|
+
*/
|
|
63
|
+
handleOwnTouchStart(e) {
|
|
64
|
+
const scrollOffset = this.framework.scrollOffset || 0;
|
|
65
|
+
|
|
66
|
+
// ✅ On peut pull SEULEMENT si on est en haut (scrollOffset === 0)
|
|
67
|
+
if (Math.abs(scrollOffset) < 1) {
|
|
68
|
+
this.canPull = true;
|
|
69
|
+
const touch = e.touches[0];
|
|
70
|
+
this.startY = touch.clientY;
|
|
71
|
+
this.currentY = touch.clientY;
|
|
72
|
+
this.myIsDragging = false;
|
|
73
|
+
} else {
|
|
74
|
+
this.canPull = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gère le mouvement du touch
|
|
80
|
+
*/
|
|
81
|
+
handleOwnTouchMove(e) {
|
|
82
|
+
if (!this.canPull) return;
|
|
83
|
+
|
|
84
|
+
const touch = e.touches[0];
|
|
85
|
+
this.currentY = touch.clientY;
|
|
86
|
+
const deltaY = this.currentY - this.startY;
|
|
87
|
+
|
|
88
|
+
// ✅ Si on tire vers le bas (deltaY > 0)
|
|
89
|
+
if (deltaY > 10) {
|
|
90
|
+
this.myIsDragging = true;
|
|
91
|
+
this.state = 'pulling';
|
|
92
|
+
|
|
93
|
+
// ✅ Effet de résistance (plus on tire, plus c'est dur)
|
|
94
|
+
this.pullDistance = Math.min(deltaY * 0.5, this.refreshThreshold * 1.5);
|
|
95
|
+
|
|
96
|
+
// ✅ Empêcher le scroll du framework si on est en train de pull
|
|
97
|
+
if (deltaY > 20) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
e.stopPropagation();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gère la fin du touch
|
|
106
|
+
*/
|
|
107
|
+
handleOwnTouchEnd(e) {
|
|
108
|
+
if (!this.canPull) return;
|
|
109
|
+
|
|
110
|
+
if (this.myIsDragging && this.state === 'pulling') {
|
|
111
|
+
if (this.pullDistance >= this.refreshThreshold) {
|
|
112
|
+
this.triggerRefresh();
|
|
113
|
+
} else {
|
|
114
|
+
this.reset();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.myIsDragging = false;
|
|
119
|
+
this.canPull = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Gère le début du clic souris
|
|
124
|
+
*/
|
|
125
|
+
handleOwnMouseDown(e) {
|
|
126
|
+
const scrollOffset = this.framework.scrollOffset || 0;
|
|
127
|
+
|
|
128
|
+
if (Math.abs(scrollOffset) < 1) {
|
|
129
|
+
this.canPull = true;
|
|
130
|
+
this.startY = e.clientY;
|
|
131
|
+
this.currentY = e.clientY;
|
|
132
|
+
this.myIsDragging = false;
|
|
133
|
+
} else {
|
|
134
|
+
this.canPull = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Gère le mouvement de la souris
|
|
140
|
+
*/
|
|
141
|
+
handleOwnMouseMove(e) {
|
|
142
|
+
if (!this.canPull) return;
|
|
143
|
+
|
|
144
|
+
this.currentY = e.clientY;
|
|
145
|
+
const deltaY = this.currentY - this.startY;
|
|
146
|
+
|
|
147
|
+
if (deltaY > 10) {
|
|
148
|
+
this.myIsDragging = true;
|
|
149
|
+
this.state = 'pulling';
|
|
150
|
+
this.pullDistance = Math.min(deltaY * 0.5, this.refreshThreshold * 1.5);
|
|
151
|
+
|
|
152
|
+
if (deltaY > 20) {
|
|
153
|
+
e.preventDefault();
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Gère le relâchement de la souris
|
|
161
|
+
*/
|
|
162
|
+
handleOwnMouseUp(e) {
|
|
163
|
+
if (!this.canPull) return;
|
|
164
|
+
|
|
165
|
+
if (this.myIsDragging && this.state === 'pulling') {
|
|
166
|
+
if (this.pullDistance >= this.refreshThreshold) {
|
|
167
|
+
this.triggerRefresh();
|
|
168
|
+
} else {
|
|
169
|
+
this.reset();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.myIsDragging = false;
|
|
174
|
+
this.canPull = false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Déclenche le rafraîchissement
|
|
179
|
+
*/
|
|
180
|
+
async triggerRefresh() {
|
|
181
|
+
if (this.isRefreshing) return;
|
|
182
|
+
|
|
183
|
+
console.log('🔄 Refresh déclenché!');
|
|
184
|
+
this.state = 'refreshing';
|
|
185
|
+
this.isRefreshing = true;
|
|
186
|
+
this.pullDistance = 60;
|
|
187
|
+
|
|
188
|
+
if (this.onRefresh) {
|
|
189
|
+
try {
|
|
190
|
+
await this.onRefresh();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Erreur refresh:', error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
this.reset();
|
|
198
|
+
}, 300);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Réinitialise l'état
|
|
203
|
+
*/
|
|
204
|
+
reset() {
|
|
205
|
+
console.log('🔄 Reset PullToRefresh');
|
|
206
|
+
|
|
207
|
+
// Animation de retour élastique
|
|
208
|
+
const animate = () => {
|
|
209
|
+
if (this.pullDistance > 0) {
|
|
210
|
+
this.pullDistance *= 0.8;
|
|
211
|
+
if (this.pullDistance < 1) {
|
|
212
|
+
this.pullDistance = 0;
|
|
213
|
+
this.state = 'idle';
|
|
214
|
+
this.isRefreshing = false;
|
|
215
|
+
}
|
|
216
|
+
requestAnimationFrame(animate);
|
|
217
|
+
} else {
|
|
218
|
+
this.state = 'idle';
|
|
219
|
+
this.isRefreshing = false;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
animate();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Nettoie les écouteurs d'événements
|
|
227
|
+
*/
|
|
228
|
+
destroy() {
|
|
229
|
+
const canvas = this.framework.canvas;
|
|
230
|
+
|
|
231
|
+
canvas.removeEventListener('touchstart', this.handleTouchStartBound);
|
|
232
|
+
canvas.removeEventListener('touchmove', this.handleTouchMoveBound);
|
|
233
|
+
canvas.removeEventListener('touchend', this.handleTouchEndBound);
|
|
234
|
+
|
|
235
|
+
canvas.removeEventListener('mousedown', this.handleMouseDownBound);
|
|
236
|
+
canvas.removeEventListener('mousemove', this.handleMouseMoveBound);
|
|
237
|
+
canvas.removeEventListener('mouseup', this.handleMouseUpBound);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Dessine le composant
|
|
242
|
+
*/
|
|
243
|
+
draw(ctx) {
|
|
244
|
+
// ✅ Ne rien dessiner si pullDistance <= 0
|
|
245
|
+
if (this.pullDistance <= 0) return;
|
|
246
|
+
|
|
247
|
+
ctx.save();
|
|
248
|
+
|
|
249
|
+
const progress = Math.min(1, this.pullDistance / this.refreshThreshold);
|
|
250
|
+
const displayHeight = Math.min(this.pullDistance, 100);
|
|
251
|
+
|
|
252
|
+
// Background
|
|
253
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
|
|
254
|
+
ctx.fillRect(0, 0, this.width, displayHeight);
|
|
255
|
+
|
|
256
|
+
// Séparateur
|
|
257
|
+
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
|
258
|
+
ctx.lineWidth = 1;
|
|
259
|
+
ctx.beginPath();
|
|
260
|
+
ctx.moveTo(0, displayHeight);
|
|
261
|
+
ctx.lineTo(this.width, displayHeight);
|
|
262
|
+
ctx.stroke();
|
|
263
|
+
|
|
264
|
+
const centerX = this.width / 2;
|
|
265
|
+
const centerY = Math.min(displayHeight / 0.6, 90);
|
|
266
|
+
const spinnerRadius = 16;
|
|
267
|
+
|
|
268
|
+
if (this.state === 'refreshing' || this.isRefreshing) {
|
|
269
|
+
// Spinner animé
|
|
270
|
+
const rotation = (Date.now() / 1000) * Math.PI * 2;
|
|
271
|
+
ctx.strokeStyle = '#6200EE';
|
|
272
|
+
ctx.lineWidth = 3;
|
|
273
|
+
ctx.lineCap = 'round';
|
|
274
|
+
ctx.beginPath();
|
|
275
|
+
ctx.arc(centerX, centerY, spinnerRadius, rotation, rotation + Math.PI * 1.5);
|
|
276
|
+
ctx.stroke();
|
|
277
|
+
|
|
278
|
+
if (displayHeight > 35) {
|
|
279
|
+
ctx.fillStyle = '#666666';
|
|
280
|
+
ctx.font = '14px -apple-system, sans-serif';
|
|
281
|
+
ctx.textAlign = 'center';
|
|
282
|
+
ctx.textBaseline = 'middle';
|
|
283
|
+
ctx.fillText('Actualisation...', centerX, centerY + 28);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
} else if (this.state === 'pulling') {
|
|
287
|
+
// Cercle de progression
|
|
288
|
+
ctx.strokeStyle = progress >= 1 ? '#6200EE' : '#CCCCCC';
|
|
289
|
+
ctx.lineWidth = 3;
|
|
290
|
+
ctx.lineCap = 'round';
|
|
291
|
+
ctx.beginPath();
|
|
292
|
+
ctx.arc(centerX, centerY, spinnerRadius, -Math.PI / 2, (-Math.PI / 2) + (Math.PI * 2 * progress));
|
|
293
|
+
ctx.stroke();
|
|
294
|
+
|
|
295
|
+
// Flèche
|
|
296
|
+
ctx.save();
|
|
297
|
+
ctx.translate(centerX, centerY);
|
|
298
|
+
ctx.rotate(progress >= 1 ? Math.PI : 0);
|
|
299
|
+
|
|
300
|
+
ctx.beginPath();
|
|
301
|
+
ctx.moveTo(0, -8);
|
|
302
|
+
ctx.lineTo(-5, -3);
|
|
303
|
+
ctx.lineTo(5, -3);
|
|
304
|
+
ctx.closePath();
|
|
305
|
+
ctx.fillStyle = progress >= 1 ? '#6200EE' : '#999999';
|
|
306
|
+
ctx.fill();
|
|
307
|
+
|
|
308
|
+
ctx.restore();
|
|
309
|
+
|
|
310
|
+
if (displayHeight > 35) {
|
|
311
|
+
ctx.fillStyle = '#666666';
|
|
312
|
+
ctx.font = '14px -apple-system, sans-serif';
|
|
313
|
+
ctx.textAlign = 'center';
|
|
314
|
+
ctx.textBaseline = 'middle';
|
|
315
|
+
|
|
316
|
+
if (progress >= 1) {
|
|
317
|
+
ctx.fillText('Relâchez pour actualiser', centerX, centerY + 28);
|
|
318
|
+
} else {
|
|
319
|
+
ctx.fillText('Tirez pour actualiser', centerX, centerY + 28);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
ctx.restore();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export default PullToRefresh;
|
package/features/Row.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import LayoutComponent from './LayoutComponent.js';
|
|
2
|
+
|
|
3
|
+
class Row extends LayoutComponent {
|
|
4
|
+
constructor(framework, options = {}) {
|
|
5
|
+
super(framework, options);
|
|
6
|
+
this.align = options.align || 'start'; // start | center | end
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
layout() {
|
|
10
|
+
let x = this.x;
|
|
11
|
+
let maxHeight = 0;
|
|
12
|
+
|
|
13
|
+
for (const child of this.children) {
|
|
14
|
+
child.x = x;
|
|
15
|
+
|
|
16
|
+
if (this.align === 'center') {
|
|
17
|
+
child.y = this.y + (this.height - child.height) / 2;
|
|
18
|
+
} else if (this.align === 'end') {
|
|
19
|
+
child.y = this.y + this.height - child.height;
|
|
20
|
+
} else {
|
|
21
|
+
child.y = this.y;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
x += child.width + this.spacing;
|
|
25
|
+
maxHeight = Math.max(maxHeight, child.height);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.width = x - this.x - this.spacing;
|
|
29
|
+
if (!this.height) this.height = maxHeight;
|
|
30
|
+
|
|
31
|
+
// Layout récursif automatique des enfants
|
|
32
|
+
for (const child of this.children) {
|
|
33
|
+
if (typeof child.layoutRecursive === 'function') {
|
|
34
|
+
child.layoutRecursive();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default Row;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pad de signature électronique
|
|
5
|
+
* @class
|
|
6
|
+
* @extends Component
|
|
7
|
+
*/
|
|
8
|
+
class SignaturePad extends Component {
|
|
9
|
+
constructor(framework, options = {}) {
|
|
10
|
+
super(framework, {
|
|
11
|
+
width: options.width || 300,
|
|
12
|
+
height: options.height || 200,
|
|
13
|
+
...options
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
this.points = [];
|
|
17
|
+
this.isDrawing = false;
|
|
18
|
+
this.strokeColor = options.strokeColor || '#000000';
|
|
19
|
+
this.strokeWidth = options.strokeWidth || 3; // Plus épais par défaut
|
|
20
|
+
this.backgroundColor = options.backgroundColor || '#FFFFFF';
|
|
21
|
+
this.onSignatureChange = options.onSignatureChange;
|
|
22
|
+
this.lastPoint = null;
|
|
23
|
+
|
|
24
|
+
// Canvas interne pour stocker la signature
|
|
25
|
+
this.signatureCanvas = document.createElement('canvas');
|
|
26
|
+
this.signatureCanvas.width = this.width;
|
|
27
|
+
this.signatureCanvas.height = this.height;
|
|
28
|
+
this.signatureCtx = this.signatureCanvas.getContext('2d');
|
|
29
|
+
|
|
30
|
+
// IMPORTANT: Configurer le contexte UNE SEULE FOIS
|
|
31
|
+
this.signatureCtx.strokeStyle = this.strokeColor;
|
|
32
|
+
this.signatureCtx.fillStyle = this.strokeColor;
|
|
33
|
+
this.signatureCtx.lineWidth = this.strokeWidth;
|
|
34
|
+
this.signatureCtx.lineCap = 'round';
|
|
35
|
+
this.signatureCtx.lineJoin = 'round';
|
|
36
|
+
|
|
37
|
+
// Initialiser le canvas
|
|
38
|
+
this.clearSignature();
|
|
39
|
+
|
|
40
|
+
// Bind des méthodes
|
|
41
|
+
this.handlePress = this.handlePress.bind(this);
|
|
42
|
+
this.handleMove = this.handleMove.bind(this);
|
|
43
|
+
this.handleRelease = this.handleRelease.bind(this);
|
|
44
|
+
|
|
45
|
+
// IMPORTANT: Définir les handlers pour le framework
|
|
46
|
+
this.onPress = this.handlePress;
|
|
47
|
+
this.onMove = this.handleMove;
|
|
48
|
+
this.onRelease = this.handleRelease;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gère le début du dessin
|
|
53
|
+
*/
|
|
54
|
+
handlePress(x, y) {
|
|
55
|
+
if (!this.isPointInside(x, y)) return false;
|
|
56
|
+
|
|
57
|
+
this.isDrawing = true;
|
|
58
|
+
const relativeX = x - this.x;
|
|
59
|
+
const relativeY = (y - this.framework.scrollOffset) - this.y;
|
|
60
|
+
|
|
61
|
+
// Stocker le point
|
|
62
|
+
this.lastPoint = { x: relativeX, y: relativeY };
|
|
63
|
+
this.points.push([{ x: relativeX, y: relativeY }]);
|
|
64
|
+
|
|
65
|
+
// Dessiner un point initial
|
|
66
|
+
this.signatureCtx.fillStyle = this.strokeColor;
|
|
67
|
+
this.signatureCtx.beginPath();
|
|
68
|
+
this.signatureCtx.arc(relativeX, relativeY, this.strokeWidth / 2, 0, Math.PI * 2);
|
|
69
|
+
this.signatureCtx.fill();
|
|
70
|
+
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Gère le dessin
|
|
76
|
+
*/
|
|
77
|
+
handleMove(x, y) {
|
|
78
|
+
if (!this.isDrawing) return false;
|
|
79
|
+
|
|
80
|
+
const relativeX = x - this.x;
|
|
81
|
+
const relativeY = (y - this.framework.scrollOffset) - this.y;
|
|
82
|
+
|
|
83
|
+
// S'assurer que les coordonnées sont dans les limites
|
|
84
|
+
if (relativeX < 0 || relativeX > this.width ||
|
|
85
|
+
relativeY < 0 || relativeY > this.height) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// OPTIMISATION: Dessiner directement sans vérifications lourdes
|
|
90
|
+
this.signatureCtx.beginPath();
|
|
91
|
+
this.signatureCtx.moveTo(this.lastPoint.x, this.lastPoint.y);
|
|
92
|
+
this.signatureCtx.lineTo(relativeX, relativeY);
|
|
93
|
+
this.signatureCtx.stroke();
|
|
94
|
+
|
|
95
|
+
// Ajouter le point (simplifié)
|
|
96
|
+
if (this.points.length > 0) {
|
|
97
|
+
this.points[this.points.length - 1].push({ x: relativeX, y: relativeY });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Mettre à jour le dernier point
|
|
101
|
+
this.lastPoint = { x: relativeX, y: relativeY };
|
|
102
|
+
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gère la fin du dessin
|
|
108
|
+
*/
|
|
109
|
+
handleRelease(x, y) {
|
|
110
|
+
if (this.isDrawing) {
|
|
111
|
+
this.isDrawing = false;
|
|
112
|
+
this.lastPoint = null;
|
|
113
|
+
|
|
114
|
+
// Callback seulement à la fin
|
|
115
|
+
if (this.onSignatureChange) {
|
|
116
|
+
this.onSignatureChange(this.points);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Efface la signature
|
|
126
|
+
*/
|
|
127
|
+
clear() {
|
|
128
|
+
this.points = [];
|
|
129
|
+
this.clearSignature();
|
|
130
|
+
|
|
131
|
+
if (this.onSignatureChange) {
|
|
132
|
+
this.onSignatureChange([]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Efface le canvas interne
|
|
138
|
+
*/
|
|
139
|
+
clearSignature() {
|
|
140
|
+
this.signatureCtx.fillStyle = this.backgroundColor;
|
|
141
|
+
this.signatureCtx.fillRect(0, 0, this.width, this.height);
|
|
142
|
+
|
|
143
|
+
// Réinitialiser le style du trait
|
|
144
|
+
this.signatureCtx.strokeStyle = this.strokeColor;
|
|
145
|
+
this.signatureCtx.fillStyle = this.strokeColor;
|
|
146
|
+
this.signatureCtx.lineWidth = this.strokeWidth;
|
|
147
|
+
this.signatureCtx.lineCap = 'round';
|
|
148
|
+
this.signatureCtx.lineJoin = 'round';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Récupère la signature comme Data URL
|
|
153
|
+
*/
|
|
154
|
+
getSignatureAsDataURL(type = 'image/png', quality = 1.0) {
|
|
155
|
+
return this.signatureCanvas.toDataURL(type, quality);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Récupère la signature comme blob
|
|
160
|
+
*/
|
|
161
|
+
getSignatureAsBlob(callback, type = 'image/png', quality = 1.0) {
|
|
162
|
+
this.signatureCanvas.toBlob(callback, type, quality);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Vérifie si la signature est vide
|
|
167
|
+
*/
|
|
168
|
+
isEmpty() {
|
|
169
|
+
return this.points.length === 0 || this.points.every(stroke => stroke.length === 0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Dessine le composant
|
|
174
|
+
*/
|
|
175
|
+
draw(ctx) {
|
|
176
|
+
ctx.save();
|
|
177
|
+
|
|
178
|
+
// Fond du pad avec bordure visible
|
|
179
|
+
ctx.fillStyle = this.backgroundColor;
|
|
180
|
+
ctx.strokeStyle = '#2196F3'; // Bleu pour voir le pad
|
|
181
|
+
ctx.lineWidth = 2;
|
|
182
|
+
|
|
183
|
+
// Rectangle avec bordure
|
|
184
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
185
|
+
ctx.strokeRect(this.x, this.y, this.width, this.height);
|
|
186
|
+
|
|
187
|
+
// Ligne de signature (guide)
|
|
188
|
+
ctx.strokeStyle = '#DDDDDD';
|
|
189
|
+
ctx.lineWidth = 1;
|
|
190
|
+
ctx.setLineDash([5, 5]);
|
|
191
|
+
ctx.beginPath();
|
|
192
|
+
ctx.moveTo(this.x + 20, this.y + this.height - 40);
|
|
193
|
+
ctx.lineTo(this.x + this.width - 20, this.y + this.height - 40);
|
|
194
|
+
ctx.stroke();
|
|
195
|
+
ctx.setLineDash([]);
|
|
196
|
+
|
|
197
|
+
// Texte indicatif
|
|
198
|
+
if (this.isEmpty()) {
|
|
199
|
+
ctx.fillStyle = '#999999';
|
|
200
|
+
ctx.font = '16px Arial';
|
|
201
|
+
ctx.textAlign = 'center';
|
|
202
|
+
ctx.textBaseline = 'middle';
|
|
203
|
+
ctx.fillText('Signez ici', this.x + this.width / 2, this.y + this.height / 2);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Dessiner la signature depuis le canvas interne
|
|
207
|
+
ctx.drawImage(this.signatureCanvas, this.x, this.y);
|
|
208
|
+
|
|
209
|
+
// Indicateur de dessin en cours
|
|
210
|
+
if (this.isDrawing && this.lastPoint) {
|
|
211
|
+
ctx.fillStyle = 'rgba(33, 150, 243, 0.3)';
|
|
212
|
+
ctx.beginPath();
|
|
213
|
+
ctx.arc(
|
|
214
|
+
this.x + this.lastPoint.x,
|
|
215
|
+
this.y + this.lastPoint.y,
|
|
216
|
+
this.strokeWidth * 3,
|
|
217
|
+
0,
|
|
218
|
+
Math.PI * 2
|
|
219
|
+
);
|
|
220
|
+
ctx.fill();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// DEBUG: Afficher les coordonnées
|
|
224
|
+
ctx.fillStyle = '#666666';
|
|
225
|
+
ctx.font = '10px Arial';
|
|
226
|
+
ctx.textAlign = 'left';
|
|
227
|
+
ctx.fillText(
|
|
228
|
+
`x:${this.x} y:${this.y} ${this.width}x${this.height}`,
|
|
229
|
+
this.x + 5,
|
|
230
|
+
this.y + 15
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
ctx.restore();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Vérifie si un point est dans les limites
|
|
238
|
+
*/
|
|
239
|
+
isPointInside(x, y) {
|
|
240
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
241
|
+
return x >= this.x &&
|
|
242
|
+
x <= this.x + this.width &&
|
|
243
|
+
adjustedY >= this.y &&
|
|
244
|
+
adjustedY <= this.y + this.height;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Nettoie les ressources
|
|
249
|
+
*/
|
|
250
|
+
destroy() {
|
|
251
|
+
this.signatureCanvas.width = 0;
|
|
252
|
+
this.signatureCanvas.height = 0;
|
|
253
|
+
super.destroy && super.destroy();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default SignaturePad;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Composant Skeleton (placeholder animé pendant le chargement)
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @param {Framework} framework - Instance du framework
|
|
7
|
+
* @param {Object} [options={}] - Options de configuration
|
|
8
|
+
* @param {string} [options.type='text'] - Type de skeleton ('text', 'circle', 'rectangle')
|
|
9
|
+
* @param {number} [options.animationSpeed=0.03] - Vitesse de l'animation
|
|
10
|
+
* @example
|
|
11
|
+
* const skeleton = new Skeleton(framework, {
|
|
12
|
+
* type: 'text',
|
|
13
|
+
* width: 200,
|
|
14
|
+
* height: 100
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
class Skeleton extends Component {
|
|
18
|
+
/**
|
|
19
|
+
* @constructs Skeleton
|
|
20
|
+
*/
|
|
21
|
+
constructor(framework, options = {}) {
|
|
22
|
+
super(framework, options);
|
|
23
|
+
/** @type {string} */
|
|
24
|
+
this.type = options.type || 'text'; // text, circle, rectangle
|
|
25
|
+
/** @type {number} */
|
|
26
|
+
this.animationSpeed = options.animationSpeed || 0.03;
|
|
27
|
+
/** @type {number} */
|
|
28
|
+
this.animationProgress = 0;
|
|
29
|
+
this.startAnimation();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Démarre l'animation du skeleton
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
startAnimation() {
|
|
37
|
+
const animate = () => {
|
|
38
|
+
if (!this.visible) return;
|
|
39
|
+
|
|
40
|
+
this.animationProgress += this.animationSpeed;
|
|
41
|
+
if (this.animationProgress > Math.PI * 2) {
|
|
42
|
+
this.animationProgress = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
requestAnimationFrame(animate);
|
|
46
|
+
};
|
|
47
|
+
animate();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Dessine le skeleton avec animation
|
|
52
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
53
|
+
*/
|
|
54
|
+
draw(ctx) {
|
|
55
|
+
ctx.save();
|
|
56
|
+
|
|
57
|
+
const brightness = 0.9 + 0.1 * Math.sin(this.animationProgress);
|
|
58
|
+
ctx.fillStyle = `rgba(240, 240, 240, ${brightness})`;
|
|
59
|
+
|
|
60
|
+
switch(this.type) {
|
|
61
|
+
case 'circle':
|
|
62
|
+
ctx.beginPath();
|
|
63
|
+
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2);
|
|
64
|
+
ctx.fill();
|
|
65
|
+
break;
|
|
66
|
+
case 'text':
|
|
67
|
+
// Simuler des lignes de texte
|
|
68
|
+
const lineHeight = 20;
|
|
69
|
+
const lines = Math.floor(this.height / lineHeight);
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < lines; i++) {
|
|
72
|
+
const lineWidth = i === lines - 1 ? this.width * 0.6 : this.width;
|
|
73
|
+
ctx.fillRect(this.x, this.y + i * lineHeight, lineWidth, 16);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ctx.restore();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default Skeleton;
|