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,364 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fenêtre modale
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {string} title - Titre
|
|
7
|
+
* @property {Component[]} children - Enfants
|
|
8
|
+
* @property {number} padding - Padding interne
|
|
9
|
+
* @property {string} bgColor - Couleur de fond
|
|
10
|
+
* @property {string} overlayColor - Couleur de l'overlay
|
|
11
|
+
* @property {number} borderRadius - Rayon des coins
|
|
12
|
+
* @property {string} shadowColor - Couleur de l'ombre
|
|
13
|
+
* @property {boolean} showCloseButton - Afficher le bouton fermer
|
|
14
|
+
* @property {boolean} closeOnOverlayClick - Fermer au clic sur l'overlay
|
|
15
|
+
* @property {number} modalWidth - Largeur du modal
|
|
16
|
+
* @property {number} modalHeight - Hauteur du modal
|
|
17
|
+
* @property {number} opacity - Opacité
|
|
18
|
+
* @property {number} scale - Échelle (animation)
|
|
19
|
+
* @property {boolean} isVisible - Visibilité
|
|
20
|
+
* @property {boolean} animating - En cours d'animation
|
|
21
|
+
* @property {number} closeButtonSize - Taille du bouton fermer
|
|
22
|
+
* @property {Object|null} closeButtonRect - Rectangle du bouton fermer
|
|
23
|
+
*/
|
|
24
|
+
class Modal extends Component {
|
|
25
|
+
/**
|
|
26
|
+
* Crée une instance de Modal
|
|
27
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
28
|
+
* @param {Object} [options={}] - Options de configuration
|
|
29
|
+
* @param {string} [options.title=''] - Titre
|
|
30
|
+
* @param {number} [options.width] - Largeur (auto selon framework)
|
|
31
|
+
* @param {number} [options.height] - Hauteur
|
|
32
|
+
* @param {number} [options.padding=20] - Padding interne
|
|
33
|
+
* @param {string} [options.bgColor='#FFFFFF'] - Couleur de fond
|
|
34
|
+
* @param {string} [options.overlayColor='rgba(0,0,0,0.7)'] - Couleur overlay
|
|
35
|
+
* @param {number} [options.borderRadius=12] - Rayon des coins
|
|
36
|
+
* @param {string} [options.shadowColor='rgba(0,0,0,0.3)'] - Couleur ombre
|
|
37
|
+
* @param {boolean} [options.showCloseButton=true] - Afficher bouton fermer
|
|
38
|
+
* @param {boolean} [options.closeOnOverlayClick=true] - Fermer sur clic overlay
|
|
39
|
+
*/
|
|
40
|
+
constructor(framework, options = {}) {
|
|
41
|
+
super(framework, {
|
|
42
|
+
x: 0,
|
|
43
|
+
y: 0,
|
|
44
|
+
width: framework.width,
|
|
45
|
+
height: framework.height,
|
|
46
|
+
visible: false,
|
|
47
|
+
...options
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.title = options.title || '';
|
|
51
|
+
this.children = []; // Composants enfants
|
|
52
|
+
this.padding = options.padding || 20;
|
|
53
|
+
this.bgColor = options.bgColor || '#FFFFFF';
|
|
54
|
+
this.overlayColor = options.overlayColor || 'rgba(0, 0, 0, 0.7)';
|
|
55
|
+
this.borderRadius = options.borderRadius || 12;
|
|
56
|
+
this.shadowColor = options.shadowColor || 'rgba(0, 0, 0, 0.3)';
|
|
57
|
+
this.showCloseButton = options.showCloseButton !== false;
|
|
58
|
+
this.closeOnOverlayClick = options.closeOnOverlayClick !== false;
|
|
59
|
+
|
|
60
|
+
// Dimensions
|
|
61
|
+
this.modalWidth = options.width || Math.min(400, framework.width - 40);
|
|
62
|
+
this.modalHeight = options.height || 300;
|
|
63
|
+
|
|
64
|
+
// Animation
|
|
65
|
+
this.opacity = 0;
|
|
66
|
+
this.scale = 0.8;
|
|
67
|
+
this.isVisible = false;
|
|
68
|
+
this.animating = false;
|
|
69
|
+
|
|
70
|
+
// Bouton de fermeture
|
|
71
|
+
this.closeButtonSize = 30;
|
|
72
|
+
this.closeButtonRect = null;
|
|
73
|
+
|
|
74
|
+
// Définir onPress pour la fermeture
|
|
75
|
+
this.onPress = this.handlePress.bind(this);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Ajoute un enfant au modal
|
|
80
|
+
* @param {Component} child - Composant enfant
|
|
81
|
+
* @returns {Component} L'enfant ajouté
|
|
82
|
+
*/
|
|
83
|
+
add(child) {
|
|
84
|
+
this.children.push(child);
|
|
85
|
+
return child;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Affiche le modal
|
|
90
|
+
*/
|
|
91
|
+
show() {
|
|
92
|
+
this.isVisible = true;
|
|
93
|
+
this.visible = true;
|
|
94
|
+
this.animateIn();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Cache le modal
|
|
99
|
+
*/
|
|
100
|
+
hide() {
|
|
101
|
+
this.animateOut();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Anime l'entrée
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
animateIn() {
|
|
109
|
+
if (this.animating) return;
|
|
110
|
+
this.animating = true;
|
|
111
|
+
|
|
112
|
+
const animate = () => {
|
|
113
|
+
this.opacity += 0.1;
|
|
114
|
+
this.scale += 0.04;
|
|
115
|
+
|
|
116
|
+
if (this.opacity >= 1) {
|
|
117
|
+
this.opacity = 1;
|
|
118
|
+
this.scale = 1;
|
|
119
|
+
this.animating = false;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
requestAnimationFrame(animate);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
animate();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Anime la sortie
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
animateOut() {
|
|
134
|
+
if (this.animating) return;
|
|
135
|
+
this.animating = true;
|
|
136
|
+
|
|
137
|
+
const animate = () => {
|
|
138
|
+
this.opacity -= 0.1;
|
|
139
|
+
this.scale -= 0.04;
|
|
140
|
+
|
|
141
|
+
if (this.opacity <= 0) {
|
|
142
|
+
this.opacity = 0;
|
|
143
|
+
this.scale = 0.8;
|
|
144
|
+
this.isVisible = false;
|
|
145
|
+
this.visible = false;
|
|
146
|
+
this.animating = false;
|
|
147
|
+
this.framework.remove(this);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
requestAnimationFrame(animate);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
animate();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Dessine le modal
|
|
159
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
160
|
+
*/
|
|
161
|
+
draw(ctx) {
|
|
162
|
+
if (!this.isVisible || this.opacity <= 0) return;
|
|
163
|
+
|
|
164
|
+
ctx.save();
|
|
165
|
+
ctx.globalAlpha = this.opacity;
|
|
166
|
+
|
|
167
|
+
// Overlay
|
|
168
|
+
ctx.fillStyle = this.overlayColor;
|
|
169
|
+
ctx.fillRect(0, 0, this.framework.width, this.framework.height);
|
|
170
|
+
|
|
171
|
+
// Calculer la position du modal
|
|
172
|
+
const modalX = (this.framework.width - this.modalWidth) / 2;
|
|
173
|
+
const modalY = (this.framework.height - this.modalHeight) / 2;
|
|
174
|
+
|
|
175
|
+
// Appliquer l'animation de scale
|
|
176
|
+
ctx.translate(modalX + this.modalWidth / 2, modalY + this.modalHeight / 2);
|
|
177
|
+
ctx.scale(this.scale, this.scale);
|
|
178
|
+
ctx.translate(-modalX - this.modalWidth / 2, -modalY - this.modalHeight / 2);
|
|
179
|
+
|
|
180
|
+
// Fond du modal
|
|
181
|
+
ctx.fillStyle = this.bgColor;
|
|
182
|
+
ctx.shadowColor = this.shadowColor;
|
|
183
|
+
ctx.shadowBlur = 20;
|
|
184
|
+
ctx.shadowOffsetY = 10;
|
|
185
|
+
|
|
186
|
+
ctx.beginPath();
|
|
187
|
+
this.roundRect(ctx, modalX, modalY, this.modalWidth, this.modalHeight, this.borderRadius);
|
|
188
|
+
ctx.fill();
|
|
189
|
+
|
|
190
|
+
ctx.shadowColor = 'transparent';
|
|
191
|
+
|
|
192
|
+
// Titre
|
|
193
|
+
if (this.title) {
|
|
194
|
+
ctx.fillStyle = '#000000';
|
|
195
|
+
ctx.font = 'bold 18px -apple-system, sans-serif';
|
|
196
|
+
ctx.textAlign = 'center';
|
|
197
|
+
ctx.textBaseline = 'middle';
|
|
198
|
+
ctx.fillText(this.title, modalX + this.modalWidth / 2, modalY + 30);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Bouton de fermeture
|
|
202
|
+
if (this.showCloseButton) {
|
|
203
|
+
const closeX = modalX + this.modalWidth - this.closeButtonSize - 10;
|
|
204
|
+
const closeY = modalY + 10;
|
|
205
|
+
|
|
206
|
+
this.closeButtonRect = {
|
|
207
|
+
x: closeX,
|
|
208
|
+
y: closeY,
|
|
209
|
+
width: this.closeButtonSize,
|
|
210
|
+
height: this.closeButtonSize
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Cercle du bouton
|
|
214
|
+
ctx.fillStyle = '#F0F0F0';
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.arc(closeX + this.closeButtonSize/2, closeY + this.closeButtonSize/2,
|
|
217
|
+
this.closeButtonSize/2, 0, Math.PI * 2);
|
|
218
|
+
ctx.fill();
|
|
219
|
+
|
|
220
|
+
// Croix (X)
|
|
221
|
+
ctx.strokeStyle = '#666666';
|
|
222
|
+
ctx.lineWidth = 2;
|
|
223
|
+
ctx.lineCap = 'round';
|
|
224
|
+
ctx.beginPath();
|
|
225
|
+
ctx.moveTo(closeX + 8, closeY + 8);
|
|
226
|
+
ctx.lineTo(closeX + this.closeButtonSize - 8, closeY + this.closeButtonSize - 8);
|
|
227
|
+
ctx.stroke();
|
|
228
|
+
|
|
229
|
+
ctx.beginPath();
|
|
230
|
+
ctx.moveTo(closeX + this.closeButtonSize - 8, closeY + 8);
|
|
231
|
+
ctx.lineTo(closeX + 8, closeY + this.closeButtonSize - 8);
|
|
232
|
+
ctx.stroke();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Zone de contenu (clipping)
|
|
236
|
+
const contentX = modalX + this.padding;
|
|
237
|
+
const contentY = modalY + (this.title ? 50 : this.padding);
|
|
238
|
+
const contentWidth = this.modalWidth - (this.padding * 2);
|
|
239
|
+
const contentHeight = this.modalHeight - contentY + modalY - this.padding;
|
|
240
|
+
|
|
241
|
+
ctx.save();
|
|
242
|
+
ctx.beginPath();
|
|
243
|
+
ctx.rect(contentX, contentY, contentWidth, contentHeight);
|
|
244
|
+
ctx.clip();
|
|
245
|
+
|
|
246
|
+
// Dessiner les enfants
|
|
247
|
+
for (let child of this.children) {
|
|
248
|
+
if (child.visible) {
|
|
249
|
+
// Sauvegarder les coordonnées originales
|
|
250
|
+
const originalX = child.x;
|
|
251
|
+
const originalY = child.y;
|
|
252
|
+
|
|
253
|
+
// Ajuster pour la position du modal
|
|
254
|
+
child.x = contentX + originalX;
|
|
255
|
+
child.y = contentY + originalY;
|
|
256
|
+
|
|
257
|
+
child.draw(ctx);
|
|
258
|
+
|
|
259
|
+
// Restaurer les coordonnées
|
|
260
|
+
child.x = originalX;
|
|
261
|
+
child.y = originalY;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
ctx.restore();
|
|
266
|
+
|
|
267
|
+
ctx.restore();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Gère la pression (clic)
|
|
272
|
+
* @param {number} x - Coordonnée X
|
|
273
|
+
* @param {number} y - Coordonnée Y
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
handlePress(x, y) {
|
|
277
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
278
|
+
const modalX = (this.framework.width - this.modalWidth) / 2;
|
|
279
|
+
const modalY = (this.framework.height - this.modalHeight) / 2;
|
|
280
|
+
|
|
281
|
+
// Vérifier le bouton de fermeture
|
|
282
|
+
if (this.showCloseButton && this.closeButtonRect) {
|
|
283
|
+
if (adjustedY >= this.closeButtonRect.y &&
|
|
284
|
+
adjustedY <= this.closeButtonRect.y + this.closeButtonRect.height &&
|
|
285
|
+
x >= this.closeButtonRect.x &&
|
|
286
|
+
x <= this.closeButtonRect.x + this.closeButtonRect.width) {
|
|
287
|
+
this.hide();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Vérifier si on clique en dehors du modal (overlay)
|
|
293
|
+
if (this.closeOnOverlayClick) {
|
|
294
|
+
const isInModal = x >= modalX && x <= modalX + this.modalWidth &&
|
|
295
|
+
adjustedY >= modalY && adjustedY <= modalY + this.modalHeight;
|
|
296
|
+
|
|
297
|
+
if (!isInModal) {
|
|
298
|
+
this.hide();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Vérifier les clics sur les enfants
|
|
304
|
+
const contentX = modalX + this.padding;
|
|
305
|
+
const contentY = modalY + (this.title ? 50 : this.padding);
|
|
306
|
+
|
|
307
|
+
for (let child of this.children) {
|
|
308
|
+
const childAbsX = contentX + child.x;
|
|
309
|
+
const childAbsY = contentY + child.y;
|
|
310
|
+
|
|
311
|
+
if (adjustedY >= childAbsY &&
|
|
312
|
+
adjustedY <= childAbsY + child.height &&
|
|
313
|
+
x >= childAbsX &&
|
|
314
|
+
x <= childAbsX + child.width) {
|
|
315
|
+
|
|
316
|
+
// Si l'enfant a un onClick, le déclencher
|
|
317
|
+
if (child.onClick) {
|
|
318
|
+
child.onClick();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Si l'enfant a un onPress, le déclencher
|
|
323
|
+
if (child.onPress) {
|
|
324
|
+
child.onPress(x - childAbsX, adjustedY - childAbsY);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Dessine un rectangle avec coins arrondis
|
|
333
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
334
|
+
* @param {number} x - Position X
|
|
335
|
+
* @param {number} y - Position Y
|
|
336
|
+
* @param {number} width - Largeur
|
|
337
|
+
* @param {number} height - Hauteur
|
|
338
|
+
* @param {number} radius - Rayon des coins
|
|
339
|
+
* @private
|
|
340
|
+
*/
|
|
341
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
342
|
+
ctx.beginPath();
|
|
343
|
+
ctx.moveTo(x + radius, y);
|
|
344
|
+
ctx.lineTo(x + width - radius, y);
|
|
345
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
346
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
347
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
348
|
+
ctx.lineTo(x + radius, y + height);
|
|
349
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
350
|
+
ctx.lineTo(x, y + radius);
|
|
351
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
352
|
+
ctx.closePath();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Vérifie si un point est dans les limites
|
|
357
|
+
* @returns {boolean} True si visible
|
|
358
|
+
*/
|
|
359
|
+
isPointInside(x, y) {
|
|
360
|
+
return this.isVisible;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export default Modal;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import Modal from '../components/Modal.js';
|
|
2
|
+
/**
|
|
3
|
+
* Modal pour la sélection multiple d'options avec checkboxes
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Modal
|
|
6
|
+
* @param {Framework} framework - Instance du framework
|
|
7
|
+
* @param {Object} [options={}] - Options de configuration
|
|
8
|
+
* @param {string} [options.title='Sélectionner'] - Titre du modal
|
|
9
|
+
* @param {string[]} [options.options=[]] - Liste des options
|
|
10
|
+
* @param {number[]} [options.selectedIndices=[]] - Indices des options sélectionnées
|
|
11
|
+
* @param {Function} [options.onSelect] - Callback lors de la validation
|
|
12
|
+
* @example
|
|
13
|
+
* const multiSelect = new MultiSelectDialog(framework, {
|
|
14
|
+
* title: 'Choisir vos intérêts',
|
|
15
|
+
* options: ['Sport', 'Musique', 'Lecture', 'Voyage'],
|
|
16
|
+
* selectedIndices: [0, 2],
|
|
17
|
+
* onSelect: (indices, values) => console.log('Selected:', values)
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
class MultiSelectDialog extends Modal {
|
|
21
|
+
/**
|
|
22
|
+
* @constructs MultiSelectDialog
|
|
23
|
+
*/
|
|
24
|
+
constructor(framework, options = {}) {
|
|
25
|
+
const optionsCount = options.options?.length || 0;
|
|
26
|
+
const itemHeight = 50;
|
|
27
|
+
const dialogHeight = Math.min(400, Math.max(200, optionsCount * itemHeight + 150));
|
|
28
|
+
|
|
29
|
+
super(framework, {
|
|
30
|
+
title: options.title || 'Sélectionner',
|
|
31
|
+
width: Math.min(350, framework.width - 40),
|
|
32
|
+
height: dialogHeight,
|
|
33
|
+
showCloseButton: true,
|
|
34
|
+
closeOnOverlayClick: true,
|
|
35
|
+
padding: 0,
|
|
36
|
+
...options
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** @type {string[]} */
|
|
40
|
+
this.options = options.options || [];
|
|
41
|
+
/** @type {number[]} */
|
|
42
|
+
this.selectedIndices = options.selectedIndices || [];
|
|
43
|
+
/** @type {Function|undefined} */
|
|
44
|
+
this.onSelect = options.onSelect;
|
|
45
|
+
/** @type {number} */
|
|
46
|
+
this.itemHeight = itemHeight;
|
|
47
|
+
|
|
48
|
+
/** @type {string[]} */
|
|
49
|
+
this.buttons = ['Annuler', 'Valider'];
|
|
50
|
+
// AJOUTER: Définir onPress pour que le framework l'appelle
|
|
51
|
+
this.onPress = this.handlePress.bind(this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Dessine le modal de sélection multiple
|
|
56
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
57
|
+
*/
|
|
58
|
+
draw(ctx) {
|
|
59
|
+
if (!this.isVisible) return;
|
|
60
|
+
|
|
61
|
+
ctx.save();
|
|
62
|
+
ctx.globalAlpha = this.opacity;
|
|
63
|
+
|
|
64
|
+
// Overlay
|
|
65
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
|
66
|
+
ctx.fillRect(0, 0, this.framework.width, this.framework.height);
|
|
67
|
+
|
|
68
|
+
const modalX = (this.framework.width - this.modalWidth) / 2;
|
|
69
|
+
const modalY = (this.framework.height - this.modalHeight) / 2;
|
|
70
|
+
|
|
71
|
+
// Fond du modal
|
|
72
|
+
ctx.fillStyle = '#FFFFFF';
|
|
73
|
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
|
74
|
+
ctx.shadowBlur = 20;
|
|
75
|
+
ctx.shadowOffsetY = 10;
|
|
76
|
+
|
|
77
|
+
ctx.beginPath();
|
|
78
|
+
this.roundRect(ctx, modalX, modalY, this.modalWidth, this.modalHeight, 12);
|
|
79
|
+
ctx.fill();
|
|
80
|
+
|
|
81
|
+
ctx.shadowColor = 'transparent';
|
|
82
|
+
|
|
83
|
+
// Titre
|
|
84
|
+
if (this.title) {
|
|
85
|
+
ctx.fillStyle = '#000000';
|
|
86
|
+
ctx.font = 'bold 18px -apple-system, sans-serif';
|
|
87
|
+
ctx.textAlign = 'center';
|
|
88
|
+
ctx.textBaseline = 'middle';
|
|
89
|
+
ctx.fillText(this.title, modalX + this.modalWidth / 2, modalY + 30);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Options avec checkboxes
|
|
93
|
+
const contentX = modalX + 20;
|
|
94
|
+
const contentY = modalY + 60;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
97
|
+
const optionY = contentY + i * this.itemHeight;
|
|
98
|
+
const isSelected = this.selectedIndices.includes(i);
|
|
99
|
+
|
|
100
|
+
// Checkbox
|
|
101
|
+
ctx.strokeStyle = isSelected ? '#6200EE' : '#666666';
|
|
102
|
+
ctx.lineWidth = 2;
|
|
103
|
+
ctx.strokeRect(contentX, optionY + 15, 20, 20);
|
|
104
|
+
|
|
105
|
+
if (isSelected) {
|
|
106
|
+
ctx.fillStyle = '#6200EE';
|
|
107
|
+
ctx.fillRect(contentX + 4, optionY + 19, 12, 12);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Texte
|
|
111
|
+
ctx.fillStyle = '#000000';
|
|
112
|
+
ctx.font = '16px -apple-system, sans-serif';
|
|
113
|
+
ctx.textAlign = 'left';
|
|
114
|
+
ctx.textBaseline = 'middle';
|
|
115
|
+
ctx.fillText(this.options[i], contentX + 35, optionY + 25);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Boutons
|
|
119
|
+
const buttonY = modalY + this.modalHeight - 60;
|
|
120
|
+
const buttonWidth = (this.modalWidth - 60) / 2;
|
|
121
|
+
|
|
122
|
+
// Bouton Annuler
|
|
123
|
+
ctx.fillStyle = '#666666';
|
|
124
|
+
ctx.font = 'bold 16px -apple-system, sans-serif';
|
|
125
|
+
ctx.textAlign = 'center';
|
|
126
|
+
ctx.textBaseline = 'middle';
|
|
127
|
+
ctx.fillText('Annuler', modalX + buttonWidth / 2 + 10, buttonY);
|
|
128
|
+
|
|
129
|
+
// Bouton Valider
|
|
130
|
+
ctx.fillStyle = '#007AFF';
|
|
131
|
+
ctx.fillText('Valider', modalX + this.modalWidth - buttonWidth / 2 - 10, buttonY);
|
|
132
|
+
|
|
133
|
+
ctx.restore();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gère le clic dans le modal de sélection multiple
|
|
138
|
+
* @param {number} x - Position X du clic
|
|
139
|
+
* @param {number} y - Position Y du clic
|
|
140
|
+
*/
|
|
141
|
+
handlePress(x, y) {
|
|
142
|
+
// Coordonnées brutes pour les modals fixes
|
|
143
|
+
const modalX = (this.framework.width - this.modalWidth) / 2;
|
|
144
|
+
const modalY = (this.framework.height - this.modalHeight) / 2;
|
|
145
|
+
const contentX = modalX + 20;
|
|
146
|
+
const contentY = modalY + 60;
|
|
147
|
+
const buttonY = modalY + this.modalHeight - 60;
|
|
148
|
+
const buttonWidth = (this.modalWidth - 60) / 2;
|
|
149
|
+
|
|
150
|
+
// Vérifier les clics sur les options (coordonnées brutes)
|
|
151
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
152
|
+
const optionY = contentY + i * this.itemHeight;
|
|
153
|
+
|
|
154
|
+
if (y >= optionY && y <= optionY + this.itemHeight &&
|
|
155
|
+
x >= contentX && x <= modalX + this.modalWidth - 20) {
|
|
156
|
+
|
|
157
|
+
const index = this.selectedIndices.indexOf(i);
|
|
158
|
+
if (index > -1) {
|
|
159
|
+
this.selectedIndices.splice(index, 1);
|
|
160
|
+
} else {
|
|
161
|
+
this.selectedIndices.push(i);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Bouton Annuler (coordonnées brutes)
|
|
168
|
+
if (y >= buttonY - 20 && y <= buttonY + 20 &&
|
|
169
|
+
x >= modalX + 10 && x <= modalX + buttonWidth + 10) {
|
|
170
|
+
this.hide();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Bouton Valider (coordonnées brutes)
|
|
175
|
+
if (y >= buttonY - 20 && y <= buttonY + 20 &&
|
|
176
|
+
x >= modalX + this.modalWidth - buttonWidth - 10 && x <= modalX + this.modalWidth - 10) {
|
|
177
|
+
if (this.onSelect) {
|
|
178
|
+
this.onSelect(this.selectedIndices, this.selectedIndices.map(i => this.options[i]));
|
|
179
|
+
}
|
|
180
|
+
this.hide();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Overlay ou bouton de fermeture
|
|
185
|
+
super.handlePress(x, y);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Vérifie si un point est à l'intérieur du modal
|
|
190
|
+
* @param {number} x - Position X
|
|
191
|
+
* @param {number} y - Position Y
|
|
192
|
+
* @returns {boolean} True si le point est à l'intérieur du modal
|
|
193
|
+
*/
|
|
194
|
+
isPointInside(x, y) {
|
|
195
|
+
const modalX = (this.framework.width - this.modalWidth) / 2;
|
|
196
|
+
const modalY = (this.framework.height - this.modalHeight) / 2;
|
|
197
|
+
|
|
198
|
+
// Les modals sont des composants fixes, donc pas d'ajustement de scroll
|
|
199
|
+
return x >= modalX &&
|
|
200
|
+
x <= modalX + this.modalWidth &&
|
|
201
|
+
y >= modalY &&
|
|
202
|
+
y <= modalY + this.modalHeight;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default MultiSelectDialog;
|