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.
Files changed (85) hide show
  1. package/README.md +554 -0
  2. package/components/Accordion.js +252 -0
  3. package/components/AndroidDatePickerDialog.js +398 -0
  4. package/components/AppBar.js +225 -0
  5. package/components/Avatar.js +202 -0
  6. package/components/BottomNavigationBar.js +205 -0
  7. package/components/BottomSheet.js +374 -0
  8. package/components/Button.js +225 -0
  9. package/components/Card.js +193 -0
  10. package/components/Checkbox.js +180 -0
  11. package/components/Chip.js +212 -0
  12. package/components/CircularProgress.js +143 -0
  13. package/components/ContextMenu.js +116 -0
  14. package/components/DatePicker.js +257 -0
  15. package/components/Dialog.js +367 -0
  16. package/components/Divider.js +125 -0
  17. package/components/Drawer.js +261 -0
  18. package/components/FAB.js +270 -0
  19. package/components/FileUpload.js +315 -0
  20. package/components/IOSDatePickerWheel.js +268 -0
  21. package/components/ImageCarousel.js +193 -0
  22. package/components/ImageComponent.js +223 -0
  23. package/components/Input.js +309 -0
  24. package/components/List.js +94 -0
  25. package/components/ListItem.js +223 -0
  26. package/components/Modal.js +364 -0
  27. package/components/MultiSelectDialog.js +206 -0
  28. package/components/NumberInput.js +271 -0
  29. package/components/ProgressBar.js +88 -0
  30. package/components/RadioButton.js +142 -0
  31. package/components/SearchInput.js +315 -0
  32. package/components/SegmentedControl.js +202 -0
  33. package/components/Select.js +199 -0
  34. package/components/SelectDialog.js +255 -0
  35. package/components/Slider.js +113 -0
  36. package/components/Snackbar.js +243 -0
  37. package/components/Stepper.js +281 -0
  38. package/components/SwipeableListItem.js +179 -0
  39. package/components/Switch.js +147 -0
  40. package/components/Table.js +492 -0
  41. package/components/Tabs.js +125 -0
  42. package/components/Text.js +141 -0
  43. package/components/TextField.js +331 -0
  44. package/components/Toast.js +236 -0
  45. package/components/TreeView.js +420 -0
  46. package/components/Video.js +397 -0
  47. package/components/View.js +140 -0
  48. package/components/VirtualList.js +120 -0
  49. package/core/CanvasFramework.js +1271 -0
  50. package/core/CanvasWork.js +32 -0
  51. package/core/Component.js +153 -0
  52. package/core/LogicWorker.js +25 -0
  53. package/core/WebGLCanvasAdapter.js +1369 -0
  54. package/features/Column.js +43 -0
  55. package/features/Grid.js +47 -0
  56. package/features/LayoutComponent.js +43 -0
  57. package/features/OpenStreetMap.js +310 -0
  58. package/features/Positioned.js +33 -0
  59. package/features/PullToRefresh.js +328 -0
  60. package/features/Row.js +40 -0
  61. package/features/SignaturePad.js +257 -0
  62. package/features/Skeleton.js +84 -0
  63. package/features/Stack.js +21 -0
  64. package/index.js +101 -0
  65. package/manager/AccessibilityManager.js +107 -0
  66. package/manager/ErrorHandler.js +59 -0
  67. package/manager/FeatureFlags.js +60 -0
  68. package/manager/MemoryManager.js +107 -0
  69. package/manager/PerformanceMonitor.js +84 -0
  70. package/manager/SecurityManager.js +54 -0
  71. package/package.json +28 -0
  72. package/utils/AnimationEngine.js +428 -0
  73. package/utils/DataStore.js +403 -0
  74. package/utils/EventBus.js +407 -0
  75. package/utils/FetchClient.js +74 -0
  76. package/utils/FormValidator.js +355 -0
  77. package/utils/GeoLocationService.js +62 -0
  78. package/utils/I18n.js +207 -0
  79. package/utils/IndexedDBManager.js +273 -0
  80. package/utils/OfflineSyncManager.js +342 -0
  81. package/utils/QueryBuilder.js +478 -0
  82. package/utils/SafeArea.js +64 -0
  83. package/utils/SecureStorage.js +289 -0
  84. package/utils/StateManager.js +207 -0
  85. 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;