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,315 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zone de téléchargement de fichiers avec drag & drop
|
|
5
|
+
* @class
|
|
6
|
+
* @extends Component
|
|
7
|
+
* @property {string} label - Texte affiché
|
|
8
|
+
* @property {string} sublabel - Sous-texte
|
|
9
|
+
* @property {string} accept - Types de fichiers acceptés
|
|
10
|
+
* @property {boolean} multiple - Accepter plusieurs fichiers
|
|
11
|
+
* @property {number} maxSize - Taille max en bytes
|
|
12
|
+
* @property {Array} files - Fichiers sélectionnés
|
|
13
|
+
* @property {boolean} isDragOver - État de survol
|
|
14
|
+
* @property {string} borderColor - Couleur de bordure
|
|
15
|
+
* @property {string} bgColor - Couleur de fond
|
|
16
|
+
* @property {string} iconColor - Couleur de l'icône
|
|
17
|
+
* @property {Function} onFilesSelected - Callback
|
|
18
|
+
* @property {Function} onError - Callback d'erreur
|
|
19
|
+
*/
|
|
20
|
+
class FileUpload extends Component {
|
|
21
|
+
/**
|
|
22
|
+
* Crée une instance de FileUpload
|
|
23
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
24
|
+
* @param {Object} [options={}] - Options de configuration
|
|
25
|
+
* @param {string} [options.label='Drag & drop files here'] - Label
|
|
26
|
+
* @param {string} [options.sublabel='or click to browse'] - Sublabel
|
|
27
|
+
* @param {string} [options.accept='*'] - Types acceptés
|
|
28
|
+
* @param {boolean} [options.multiple=true] - Multiple fichiers
|
|
29
|
+
* @param {number} [options.maxSize=10485760] - Taille max (10MB)
|
|
30
|
+
* @param {Function} [options.onFilesSelected] - Callback
|
|
31
|
+
* @param {Function} [options.onError] - Callback erreur
|
|
32
|
+
*/
|
|
33
|
+
constructor(framework, options = {}) {
|
|
34
|
+
super(framework, options);
|
|
35
|
+
|
|
36
|
+
this.label = options.label || 'Drag & drop files here';
|
|
37
|
+
this.sublabel = options.sublabel || 'or click to browse';
|
|
38
|
+
this.accept = options.accept || '*';
|
|
39
|
+
this.multiple = options.multiple !== false;
|
|
40
|
+
this.maxSize = options.maxSize || 10485760; // 10MB
|
|
41
|
+
this.files = [];
|
|
42
|
+
this.isDragOver = false;
|
|
43
|
+
|
|
44
|
+
const platform = framework.platform;
|
|
45
|
+
|
|
46
|
+
// Styles selon la plateforme
|
|
47
|
+
if (platform === 'material') {
|
|
48
|
+
this.borderColor = '#6200EE';
|
|
49
|
+
this.bgColor = 'rgba(98, 0, 238, 0.05)';
|
|
50
|
+
this.iconColor = '#6200EE';
|
|
51
|
+
this.borderRadius = 4;
|
|
52
|
+
this.borderWidth = 2;
|
|
53
|
+
} else {
|
|
54
|
+
this.borderColor = '#007AFF';
|
|
55
|
+
this.bgColor = 'rgba(0, 122, 255, 0.05)';
|
|
56
|
+
this.iconColor = '#007AFF';
|
|
57
|
+
this.borderRadius = 12;
|
|
58
|
+
this.borderWidth = 2;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.onFilesSelected = options.onFilesSelected || null;
|
|
62
|
+
this.onError = options.onError || null;
|
|
63
|
+
|
|
64
|
+
// Créer un input file caché
|
|
65
|
+
this.createFileInput();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Crée l'input file HTML caché
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
createFileInput() {
|
|
73
|
+
this.fileInput = document.createElement('input');
|
|
74
|
+
this.fileInput.type = 'file';
|
|
75
|
+
this.fileInput.accept = this.accept;
|
|
76
|
+
this.fileInput.multiple = this.multiple;
|
|
77
|
+
this.fileInput.style.display = 'none';
|
|
78
|
+
document.body.appendChild(this.fileInput);
|
|
79
|
+
|
|
80
|
+
this.fileInput.addEventListener('change', (e) => {
|
|
81
|
+
this.handleFiles(Array.from(e.target.files));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Gère les fichiers sélectionnés
|
|
87
|
+
* @param {Array} fileList - Liste des fichiers
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
handleFiles(fileList) {
|
|
91
|
+
const validFiles = [];
|
|
92
|
+
|
|
93
|
+
for (let file of fileList) {
|
|
94
|
+
// Vérifier la taille
|
|
95
|
+
if (file.size > this.maxSize) {
|
|
96
|
+
if (this.onError) {
|
|
97
|
+
this.onError({
|
|
98
|
+
type: 'size',
|
|
99
|
+
message: `${file.name} exceeds max size of ${this.formatBytes(this.maxSize)}`,
|
|
100
|
+
file: file
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Vérifier le type si spécifié
|
|
107
|
+
if (this.accept !== '*') {
|
|
108
|
+
const acceptedTypes = this.accept.split(',').map(t => t.trim());
|
|
109
|
+
const fileType = file.type;
|
|
110
|
+
const fileExt = '.' + file.name.split('.').pop();
|
|
111
|
+
|
|
112
|
+
const isAccepted = acceptedTypes.some(type => {
|
|
113
|
+
if (type.startsWith('.')) {
|
|
114
|
+
return fileExt === type;
|
|
115
|
+
} else if (type.endsWith('/*')) {
|
|
116
|
+
return fileType.startsWith(type.replace('/*', ''));
|
|
117
|
+
} else {
|
|
118
|
+
return fileType === type;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!isAccepted) {
|
|
123
|
+
if (this.onError) {
|
|
124
|
+
this.onError({
|
|
125
|
+
type: 'type',
|
|
126
|
+
message: `${file.name} is not an accepted file type`,
|
|
127
|
+
file: file
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
validFiles.push(file);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (validFiles.length > 0) {
|
|
138
|
+
this.files = validFiles;
|
|
139
|
+
if (this.onFilesSelected) {
|
|
140
|
+
this.onFilesSelected(validFiles);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Reset input
|
|
145
|
+
this.fileInput.value = '';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Formate les bytes en format lisible
|
|
150
|
+
* @param {number} bytes - Nombre de bytes
|
|
151
|
+
* @returns {string} Taille formatée
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
formatBytes(bytes) {
|
|
155
|
+
if (bytes === 0) return '0 Bytes';
|
|
156
|
+
const k = 1024;
|
|
157
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
158
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
159
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Dessine le composant
|
|
164
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
165
|
+
*/
|
|
166
|
+
draw(ctx) {
|
|
167
|
+
ctx.save();
|
|
168
|
+
|
|
169
|
+
// Fond
|
|
170
|
+
ctx.fillStyle = this.isDragOver || this.pressed ?
|
|
171
|
+
this.lightenColor(this.bgColor) : this.bgColor;
|
|
172
|
+
ctx.beginPath();
|
|
173
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
174
|
+
ctx.fill();
|
|
175
|
+
|
|
176
|
+
// Bordure en pointillés
|
|
177
|
+
ctx.strokeStyle = this.borderColor;
|
|
178
|
+
ctx.lineWidth = this.borderWidth;
|
|
179
|
+
ctx.setLineDash([8, 8]);
|
|
180
|
+
ctx.beginPath();
|
|
181
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
182
|
+
ctx.stroke();
|
|
183
|
+
ctx.setLineDash([]);
|
|
184
|
+
|
|
185
|
+
// Icône de fichier (simple)
|
|
186
|
+
const iconSize = 40;
|
|
187
|
+
const iconX = this.x + this.width / 2 - iconSize / 2;
|
|
188
|
+
const iconY = this.y + this.height / 2 - 40;
|
|
189
|
+
|
|
190
|
+
ctx.strokeStyle = this.iconColor;
|
|
191
|
+
ctx.lineWidth = 3;
|
|
192
|
+
|
|
193
|
+
// Document
|
|
194
|
+
ctx.beginPath();
|
|
195
|
+
ctx.moveTo(iconX, iconY);
|
|
196
|
+
ctx.lineTo(iconX + iconSize * 0.7, iconY);
|
|
197
|
+
ctx.lineTo(iconX + iconSize, iconY + iconSize * 0.3);
|
|
198
|
+
ctx.lineTo(iconX + iconSize, iconY + iconSize);
|
|
199
|
+
ctx.lineTo(iconX, iconY + iconSize);
|
|
200
|
+
ctx.closePath();
|
|
201
|
+
ctx.stroke();
|
|
202
|
+
|
|
203
|
+
// Coin plié
|
|
204
|
+
ctx.beginPath();
|
|
205
|
+
ctx.moveTo(iconX + iconSize * 0.7, iconY);
|
|
206
|
+
ctx.lineTo(iconX + iconSize * 0.7, iconY + iconSize * 0.3);
|
|
207
|
+
ctx.lineTo(iconX + iconSize, iconY + iconSize * 0.3);
|
|
208
|
+
ctx.stroke();
|
|
209
|
+
|
|
210
|
+
// Flèche montante
|
|
211
|
+
const arrowX = iconX + iconSize / 2;
|
|
212
|
+
const arrowY = iconY + iconSize * 0.5;
|
|
213
|
+
const arrowSize = 12;
|
|
214
|
+
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.moveTo(arrowX, arrowY - arrowSize);
|
|
217
|
+
ctx.lineTo(arrowX, arrowY + arrowSize);
|
|
218
|
+
ctx.stroke();
|
|
219
|
+
|
|
220
|
+
ctx.beginPath();
|
|
221
|
+
ctx.moveTo(arrowX - arrowSize / 2, arrowY - arrowSize / 2);
|
|
222
|
+
ctx.lineTo(arrowX, arrowY - arrowSize);
|
|
223
|
+
ctx.lineTo(arrowX + arrowSize / 2, arrowY - arrowSize / 2);
|
|
224
|
+
ctx.stroke();
|
|
225
|
+
|
|
226
|
+
// Texte
|
|
227
|
+
ctx.fillStyle = '#000000';
|
|
228
|
+
ctx.font = '16px -apple-system, BlinkMacSystemFont, Roboto, sans-serif';
|
|
229
|
+
ctx.textAlign = 'center';
|
|
230
|
+
ctx.textBaseline = 'middle';
|
|
231
|
+
ctx.fillText(this.label, this.x + this.width / 2, this.y + this.height / 2 + 30);
|
|
232
|
+
|
|
233
|
+
ctx.fillStyle = '#666666';
|
|
234
|
+
ctx.font = '14px -apple-system, BlinkMacSystemFont, Roboto, sans-serif';
|
|
235
|
+
ctx.fillText(this.sublabel, this.x + this.width / 2, this.y + this.height / 2 + 52);
|
|
236
|
+
|
|
237
|
+
// Afficher les fichiers sélectionnés
|
|
238
|
+
if (this.files.length > 0) {
|
|
239
|
+
ctx.fillStyle = this.borderColor;
|
|
240
|
+
ctx.font = '12px -apple-system, BlinkMacSystemFont, Roboto, sans-serif';
|
|
241
|
+
const fileText = this.files.length === 1 ?
|
|
242
|
+
this.files[0].name :
|
|
243
|
+
`${this.files.length} files selected`;
|
|
244
|
+
ctx.fillText(fileText, this.x + this.width / 2, this.y + this.height - 20);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
ctx.restore();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Dessine un rectangle avec coins arrondis
|
|
252
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
253
|
+
* @param {number} x - Position X
|
|
254
|
+
* @param {number} y - Position Y
|
|
255
|
+
* @param {number} width - Largeur
|
|
256
|
+
* @param {number} height - Hauteur
|
|
257
|
+
* @param {number} radius - Rayon des coins
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
261
|
+
ctx.moveTo(x + radius, y);
|
|
262
|
+
ctx.lineTo(x + width - radius, y);
|
|
263
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
264
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
265
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
266
|
+
ctx.lineTo(x + radius, y + height);
|
|
267
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
268
|
+
ctx.lineTo(x, y + radius);
|
|
269
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Éclaircit une couleur
|
|
274
|
+
* @param {string} color - Couleur
|
|
275
|
+
* @returns {string} Couleur éclaircie
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
lightenColor(color) {
|
|
279
|
+
if (color.startsWith('rgba')) {
|
|
280
|
+
return color.replace(/[\d.]+\)$/g, '0.15)');
|
|
281
|
+
}
|
|
282
|
+
return color;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Vérifie si un point est dans les limites
|
|
287
|
+
* @param {number} x - Coordonnée X
|
|
288
|
+
* @param {number} y - Coordonnée Y
|
|
289
|
+
* @returns {boolean} True si le point est dans le composant
|
|
290
|
+
*/
|
|
291
|
+
isPointInside(x, y) {
|
|
292
|
+
return x >= this.x &&
|
|
293
|
+
x <= this.x + this.width &&
|
|
294
|
+
y >= this.y &&
|
|
295
|
+
y <= this.y + this.height;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Override du onClick pour ouvrir le file picker
|
|
300
|
+
*/
|
|
301
|
+
onClick() {
|
|
302
|
+
this.fileInput.click();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Nettoie le composant
|
|
307
|
+
*/
|
|
308
|
+
destroy() {
|
|
309
|
+
if (this.fileInput && this.fileInput.parentNode) {
|
|
310
|
+
this.fileInput.parentNode.removeChild(this.fileInput);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default FileUpload;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sélecteur de date iOS (style roue)
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {Date} selectedDate - Date sélectionnée
|
|
7
|
+
* @property {Function} onChange - Callback au changement
|
|
8
|
+
* @property {number} monthWheel - Mois sélectionné
|
|
9
|
+
* @property {number} dayWheel - Jour sélectionné
|
|
10
|
+
* @property {number} yearWheel - Année sélectionnée
|
|
11
|
+
* @property {number} wheelHeight - Hauteur de la roue
|
|
12
|
+
* @property {number} itemHeight - Hauteur d'un item
|
|
13
|
+
* @property {number} visibleItems - Nombre d'items visibles
|
|
14
|
+
* @property {boolean} dragging - En cours de drag
|
|
15
|
+
* @property {number} dragStartY - Position Y du début du drag
|
|
16
|
+
* @property {number|null} dragWheel - Roue en cours de drag
|
|
17
|
+
* @property {number} lastDeltaY - Dernier delta Y
|
|
18
|
+
* @property {boolean} wasDragging - Drag effectué
|
|
19
|
+
*/
|
|
20
|
+
class IOSDatePickerWheel extends Component {
|
|
21
|
+
/**
|
|
22
|
+
* Crée une instance de IOSDatePickerWheel
|
|
23
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
24
|
+
* @param {Object} [options={}] - Options de configuration
|
|
25
|
+
* @param {Date} [options.selectedDate=new Date()] - Date initiale
|
|
26
|
+
* @param {Function} [options.onChange] - Callback au changement
|
|
27
|
+
*/
|
|
28
|
+
constructor(framework, options = {}) {
|
|
29
|
+
super(framework, options);
|
|
30
|
+
this.selectedDate = options.selectedDate || new Date();
|
|
31
|
+
this.onChange = options.onChange;
|
|
32
|
+
|
|
33
|
+
// Roues de sélection
|
|
34
|
+
this.monthWheel = this.selectedDate.getMonth();
|
|
35
|
+
this.dayWheel = this.selectedDate.getDate();
|
|
36
|
+
this.yearWheel = this.selectedDate.getFullYear();
|
|
37
|
+
|
|
38
|
+
this.wheelHeight = 200;
|
|
39
|
+
this.itemHeight = 40;
|
|
40
|
+
this.visibleItems = 5;
|
|
41
|
+
|
|
42
|
+
// AJOUTER CES LIGNES :
|
|
43
|
+
this.dragging = false;
|
|
44
|
+
this.dragStartY = 0;
|
|
45
|
+
this.dragWheel = null; // 0=mois, 1=jour, 2=année
|
|
46
|
+
this.lastDeltaY = 0; // Pour éviter les micro-déplacements
|
|
47
|
+
this.wasDragging = false; // Pour savoir si on a vraiment déplacé
|
|
48
|
+
|
|
49
|
+
// CORRECTION : Définir les méthodes de gestion d'événements
|
|
50
|
+
this.onPress = this.handlePress.bind(this);
|
|
51
|
+
this.onMove = this.handleMove.bind(this);
|
|
52
|
+
this.onRelease = this.handleRelease.bind(this); // Nouveau : pour le relâchement
|
|
53
|
+
|
|
54
|
+
// CORRECTION : NE PAS REDÉFINIR width et height ici
|
|
55
|
+
// Au lieu de cela, utiliser les options passées ou des valeurs par défaut
|
|
56
|
+
// Les propriétés width et height sont déjà définies par super()
|
|
57
|
+
|
|
58
|
+
// Si aucune width n'a été passée dans options, on en définit une
|
|
59
|
+
if (!options.width) {
|
|
60
|
+
this.width = framework.width - 40;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// S'assurer que la hauteur correspond à wheelHeight
|
|
64
|
+
if (!options.height) {
|
|
65
|
+
this.height = this.wheelHeight;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gère le relâchement
|
|
71
|
+
* @param {number} x - Coordonnée X
|
|
72
|
+
* @param {number} y - Coordonnée Y
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
handleRelease(x, y) {
|
|
76
|
+
if (this.dragging) {
|
|
77
|
+
this.dragging = false;
|
|
78
|
+
this.dragWheel = null;
|
|
79
|
+
this.lastDeltaY = 0;
|
|
80
|
+
this.wasDragging = false;
|
|
81
|
+
|
|
82
|
+
// IMPORTANT : Réinitialiser le composant actif du framework
|
|
83
|
+
if (this.framework.activeComponent === this) {
|
|
84
|
+
this.framework.activeComponent = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gère la pression
|
|
91
|
+
* @param {number} x - Coordonnée X
|
|
92
|
+
* @param {number} y - Coordonnée Y
|
|
93
|
+
* @returns {boolean} True si le point est dans le composant
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
handlePress(x, y) {
|
|
97
|
+
// Ajuster y avec le scrollOffset
|
|
98
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
99
|
+
|
|
100
|
+
// Vérifier si on clique dans le DatePicker
|
|
101
|
+
if (this.isPointInside(x, adjustedY)) {
|
|
102
|
+
this.dragging = true;
|
|
103
|
+
this.dragStartY = adjustedY;
|
|
104
|
+
this.lastDeltaY = 0;
|
|
105
|
+
this.wasDragging = false;
|
|
106
|
+
|
|
107
|
+
// Déterminer quelle roue est touchée
|
|
108
|
+
const wheelWidth = this.width / 3;
|
|
109
|
+
if (x < this.x + wheelWidth) {
|
|
110
|
+
this.dragWheel = 0; // Mois
|
|
111
|
+
} else if (x < this.x + wheelWidth * 2) {
|
|
112
|
+
this.dragWheel = 1; // Jour
|
|
113
|
+
} else {
|
|
114
|
+
this.dragWheel = 2; // Année
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// CRITIQUE : Définir ce composant comme actif dans le framework
|
|
118
|
+
this.framework.activeComponent = this;
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Gère le mouvement
|
|
126
|
+
* @param {number} x - Coordonnée X
|
|
127
|
+
* @param {number} y - Coordonnée Y
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
handleMove(x, y) {
|
|
131
|
+
if (!this.dragging) return;
|
|
132
|
+
|
|
133
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
134
|
+
const deltaY = adjustedY - this.dragStartY;
|
|
135
|
+
|
|
136
|
+
// Seuil de mouvement pour éviter les micro-déplacements
|
|
137
|
+
if (Math.abs(deltaY - this.lastDeltaY) > 2) {
|
|
138
|
+
this.wasDragging = true;
|
|
139
|
+
const steps = Math.round((deltaY - this.lastDeltaY) / this.itemHeight);
|
|
140
|
+
|
|
141
|
+
if (steps !== 0) {
|
|
142
|
+
if (this.dragWheel === 0) {
|
|
143
|
+
// Mois
|
|
144
|
+
this.monthWheel = Math.max(0, Math.min(11, this.monthWheel - steps));
|
|
145
|
+
} else if (this.dragWheel === 1) {
|
|
146
|
+
// Jour
|
|
147
|
+
this.dayWheel = Math.max(1, Math.min(31, this.dayWheel - steps));
|
|
148
|
+
} else if (this.dragWheel === 2) {
|
|
149
|
+
// Année
|
|
150
|
+
this.yearWheel = Math.max(1900, Math.min(2100, this.yearWheel - steps));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Mettre à jour la date
|
|
154
|
+
this.selectedDate = new Date(this.yearWheel, this.monthWheel, this.dayWheel);
|
|
155
|
+
if (this.onChange) this.onChange(this.selectedDate);
|
|
156
|
+
|
|
157
|
+
this.lastDeltaY = deltaY;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Vérifie si un point est dans les limites
|
|
164
|
+
* @param {number} x - Coordonnée X
|
|
165
|
+
* @param {number} y - Coordonnée Y
|
|
166
|
+
* @returns {boolean} True si le point est dans le composant
|
|
167
|
+
*/
|
|
168
|
+
isPointInside(x, y) {
|
|
169
|
+
// Ajuster y avec le scrollOffset pour la détection
|
|
170
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
171
|
+
return x >= this.x &&
|
|
172
|
+
x <= this.x + this.width &&
|
|
173
|
+
adjustedY >= this.y &&
|
|
174
|
+
adjustedY <= this.y + this.wheelHeight;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Dessine le sélecteur de date
|
|
179
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
180
|
+
*/
|
|
181
|
+
draw(ctx) {
|
|
182
|
+
ctx.save();
|
|
183
|
+
|
|
184
|
+
const wheelWidth = this.width / 3;
|
|
185
|
+
|
|
186
|
+
// Fond
|
|
187
|
+
ctx.fillStyle = '#F9F9F9';
|
|
188
|
+
ctx.fillRect(this.x, this.y, this.width, this.wheelHeight);
|
|
189
|
+
|
|
190
|
+
// Bande de sélection
|
|
191
|
+
const selectionY = this.y + this.wheelHeight / 2 - this.itemHeight / 2;
|
|
192
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
|
|
193
|
+
ctx.fillRect(this.x, selectionY, this.width, this.itemHeight);
|
|
194
|
+
|
|
195
|
+
// Lignes de séparation
|
|
196
|
+
ctx.strokeStyle = '#C7C7CC';
|
|
197
|
+
ctx.lineWidth = 0.5;
|
|
198
|
+
ctx.beginPath();
|
|
199
|
+
ctx.moveTo(this.x, selectionY);
|
|
200
|
+
ctx.lineTo(this.x + this.width, selectionY);
|
|
201
|
+
ctx.moveTo(this.x, selectionY + this.itemHeight);
|
|
202
|
+
ctx.lineTo(this.x + this.width, selectionY + this.itemHeight);
|
|
203
|
+
ctx.stroke();
|
|
204
|
+
|
|
205
|
+
// Dividers verticaux
|
|
206
|
+
ctx.beginPath();
|
|
207
|
+
ctx.moveTo(this.x + wheelWidth, this.y);
|
|
208
|
+
ctx.lineTo(this.x + wheelWidth, this.y + this.wheelHeight);
|
|
209
|
+
ctx.moveTo(this.x + wheelWidth * 2, this.y);
|
|
210
|
+
ctx.lineTo(this.x + wheelWidth * 2, this.y + this.wheelHeight);
|
|
211
|
+
ctx.stroke();
|
|
212
|
+
|
|
213
|
+
// Mois
|
|
214
|
+
const monthNames = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
|
|
215
|
+
'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
|
216
|
+
this.drawWheel(ctx, this.x, monthNames, this.monthWheel);
|
|
217
|
+
|
|
218
|
+
// Jour
|
|
219
|
+
const days = Array.from({length: 31}, (_, i) => (i + 1).toString());
|
|
220
|
+
this.drawWheel(ctx, this.x + wheelWidth, days, this.dayWheel - 1);
|
|
221
|
+
|
|
222
|
+
// Année
|
|
223
|
+
const currentYear = new Date().getFullYear();
|
|
224
|
+
const years = Array.from({length: 100}, (_, i) => (currentYear - 50 + i).toString());
|
|
225
|
+
const yearIndex = this.yearWheel - (currentYear - 50);
|
|
226
|
+
this.drawWheel(ctx, this.x + wheelWidth * 2, years, yearIndex);
|
|
227
|
+
|
|
228
|
+
ctx.restore();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Dessine une roue de sélection
|
|
233
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
234
|
+
* @param {number} x - Position X
|
|
235
|
+
* @param {string[]} items - Items à afficher
|
|
236
|
+
* @param {number} selectedIndex - Index sélectionné
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
drawWheel(ctx, x, items, selectedIndex) {
|
|
240
|
+
const wheelWidth = this.width / 3;
|
|
241
|
+
const centerY = this.y + this.wheelHeight / 2;
|
|
242
|
+
|
|
243
|
+
ctx.save();
|
|
244
|
+
ctx.beginPath();
|
|
245
|
+
ctx.rect(x, this.y, wheelWidth, this.wheelHeight);
|
|
246
|
+
ctx.clip();
|
|
247
|
+
|
|
248
|
+
for (let i = -2; i <= 2; i++) {
|
|
249
|
+
const index = selectedIndex + i;
|
|
250
|
+
if (index >= 0 && index < items.length) {
|
|
251
|
+
const itemY = centerY + i * this.itemHeight;
|
|
252
|
+
const distance = Math.abs(itemY - centerY);
|
|
253
|
+
const scale = 1 - (distance / this.wheelHeight);
|
|
254
|
+
const opacity = Math.max(0.3, scale);
|
|
255
|
+
|
|
256
|
+
ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`;
|
|
257
|
+
ctx.font = `${i === 0 ? 'bold ' : ''}${18 + scale * 2}px -apple-system, sans-serif`;
|
|
258
|
+
ctx.textAlign = 'center';
|
|
259
|
+
ctx.textBaseline = 'middle';
|
|
260
|
+
ctx.fillText(items[index], x + wheelWidth / 2, itemY);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
ctx.restore();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export default IOSDatePickerWheel;
|