canvasframework 0.5.16 → 0.5.18

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 (112) hide show
  1. package/dist/canvasframework.js +2 -0
  2. package/dist/canvasframework.js.LICENSE.txt +1 -0
  3. package/package.json +18 -17
  4. package/components/Accordion.js +0 -265
  5. package/components/AndroidDatePickerDialog.js +0 -406
  6. package/components/AppBar.js +0 -398
  7. package/components/AudioPlayer.js +0 -611
  8. package/components/Avatar.js +0 -202
  9. package/components/Banner.js +0 -342
  10. package/components/BottomNavigationBar.js +0 -433
  11. package/components/BottomSheet.js +0 -234
  12. package/components/Button.js +0 -360
  13. package/components/Camera.js +0 -644
  14. package/components/Card.js +0 -193
  15. package/components/Chart.js +0 -700
  16. package/components/Checkbox.js +0 -166
  17. package/components/Chip.js +0 -212
  18. package/components/CircularProgress.js +0 -327
  19. package/components/ContextMenu.js +0 -116
  20. package/components/DatePicker.js +0 -298
  21. package/components/Dialog.js +0 -337
  22. package/components/Divider.js +0 -125
  23. package/components/Drawer.js +0 -276
  24. package/components/FAB.js +0 -270
  25. package/components/FileUpload.js +0 -315
  26. package/components/FloatedCamera.js +0 -644
  27. package/components/IOSDatePickerWheel.js +0 -430
  28. package/components/ImageCarousel.js +0 -219
  29. package/components/ImageComponent.js +0 -223
  30. package/components/Input.js +0 -831
  31. package/components/InputDatalist.js +0 -723
  32. package/components/InputTags.js +0 -624
  33. package/components/List.js +0 -95
  34. package/components/ListItem.js +0 -269
  35. package/components/Modal.js +0 -364
  36. package/components/MorphingFAB.js +0 -428
  37. package/components/MultiSelectDialog.js +0 -206
  38. package/components/NumberInput.js +0 -271
  39. package/components/PasswordInput.js +0 -462
  40. package/components/ProgressBar.js +0 -88
  41. package/components/QRCodeReader.js +0 -539
  42. package/components/RadioButton.js +0 -151
  43. package/components/SearchInput.js +0 -315
  44. package/components/SegmentedControl.js +0 -357
  45. package/components/Select.js +0 -199
  46. package/components/SelectDialog.js +0 -255
  47. package/components/Slider.js +0 -113
  48. package/components/SliverAppBar.js +0 -139
  49. package/components/Snackbar.js +0 -243
  50. package/components/SpeedDialFAB.js +0 -397
  51. package/components/Stepper.js +0 -281
  52. package/components/SwipeableListItem.js +0 -327
  53. package/components/Switch.js +0 -147
  54. package/components/Table.js +0 -492
  55. package/components/Tabs.js +0 -423
  56. package/components/Text.js +0 -141
  57. package/components/TextField.js +0 -151
  58. package/components/TimePicker.js +0 -934
  59. package/components/Toast.js +0 -236
  60. package/components/TreeView.js +0 -420
  61. package/components/Video.js +0 -397
  62. package/components/View.js +0 -140
  63. package/components/VirtualList.js +0 -120
  64. package/core/CanvasFramework.js +0 -3034
  65. package/core/Component.js +0 -243
  66. package/core/ThemeManager.js +0 -358
  67. package/core/UIBuilder.js +0 -267
  68. package/core/WebGLCanvasAdapter.js +0 -782
  69. package/features/Column.js +0 -43
  70. package/features/Grid.js +0 -47
  71. package/features/LayoutComponent.js +0 -43
  72. package/features/OpenStreetMap.js +0 -310
  73. package/features/Positioned.js +0 -33
  74. package/features/PullToRefresh.js +0 -328
  75. package/features/Row.js +0 -40
  76. package/features/SignaturePad.js +0 -257
  77. package/features/Skeleton.js +0 -193
  78. package/features/Stack.js +0 -21
  79. package/index.js +0 -119
  80. package/manager/AccessibilityManager.js +0 -107
  81. package/manager/ErrorHandler.js +0 -59
  82. package/manager/FeatureFlags.js +0 -60
  83. package/manager/MemoryManager.js +0 -107
  84. package/manager/PerformanceMonitor.js +0 -84
  85. package/manager/SecurityManager.js +0 -54
  86. package/utils/AnimationEngine.js +0 -734
  87. package/utils/CryptoManager.js +0 -303
  88. package/utils/DataStore.js +0 -403
  89. package/utils/DevTools.js +0 -1618
  90. package/utils/DevToolsConsole.js +0 -201
  91. package/utils/EventBus.js +0 -407
  92. package/utils/FetchClient.js +0 -74
  93. package/utils/FirebaseAuth.js +0 -653
  94. package/utils/FirebaseCore.js +0 -246
  95. package/utils/FirebaseFirestore.js +0 -581
  96. package/utils/FirebaseFunctions.js +0 -97
  97. package/utils/FirebaseRealtimeDB.js +0 -498
  98. package/utils/FirebaseStorage.js +0 -612
  99. package/utils/FormValidator.js +0 -355
  100. package/utils/GeoLocationService.js +0 -62
  101. package/utils/I18n.js +0 -207
  102. package/utils/IndexedDBManager.js +0 -273
  103. package/utils/InspectionOverlay.js +0 -308
  104. package/utils/NotificationManager.js +0 -60
  105. package/utils/OfflineSyncManager.js +0 -342
  106. package/utils/PayPalPayment.js +0 -678
  107. package/utils/QueryBuilder.js +0 -478
  108. package/utils/SafeArea.js +0 -64
  109. package/utils/SecureStorage.js +0 -289
  110. package/utils/StateManager.js +0 -207
  111. package/utils/StripePayment.js +0 -552
  112. package/utils/WebSocketClient.js +0 -66
@@ -1,723 +0,0 @@
1
- import Component from '../core/Component.js';
2
-
3
- /**
4
- * Champ de saisie avec suggestions (datalist)
5
- * @class
6
- * @extends Component
7
- * @property {string} placeholder - Texte d'indication
8
- * @property {string} value - Valeur
9
- * @property {Array} options - Liste des suggestions
10
- * @property {Array} filteredOptions - Options filtrées
11
- * @property {number} fontSize - Taille de police
12
- * @property {boolean} focused - Focus actif
13
- * @property {string} platform - Plateforme
14
- * @property {boolean} cursorVisible - Curseur visible
15
- * @property {number} cursorPosition - Position du curseur
16
- * @property {HTMLInputElement} hiddenInput - Input HTML caché
17
- * @property {number} selectedIndex - Index de l'option sélectionnée
18
- * @property {boolean} showDropdown - Afficher la liste déroulante
19
- * @property {number} maxDropdownItems - Nombre max d'éléments affichés
20
- * @property {number} dropdownItemHeight - Hauteur d'un élément
21
- */
22
- class InputDatalist extends Component {
23
- static activeInput = null;
24
- static allInputs = new Set();
25
- static globalClickHandler = null;
26
-
27
- /**
28
- * Crée une instance de InputDatalist
29
- * @param {CanvasFramework} framework - Framework parent
30
- * @param {Object} [options={}] - Options de configuration
31
- * @param {string} [options.placeholder=''] - Texte d'indication
32
- * @param {string} [options.value=''] - Valeur initiale
33
- * @param {Array} [options.options=[]] - Liste des suggestions
34
- * @param {number} [options.fontSize=16] - Taille de police
35
- * @param {Function} [options.onFocus] - Callback au focus
36
- * @param {Function} [options.onBlur] - Callback au blur
37
- * @param {Function} [options.onSelect] - Callback quand une option est sélectionnée
38
- * @param {Function} [options.onInput] - Callback quand la valeur change
39
- * @param {number} [options.maxDropdownItems=5] - Nombre max d'éléments affichés
40
- * @param {string} [options.dropdownBackground='#FFFFFF'] - Couleur de fond du dropdown
41
- * @param {string} [options.hoverBackground='#F5F5F5'] - Couleur de fond au survol
42
- * @param {string} [options.borderColor='#E0E0E0'] - Couleur de bordure du dropdown
43
- */
44
- constructor(framework, options = {}) {
45
- super(framework, options);
46
- this.placeholder = options.placeholder || '';
47
- this.value = options.value || '';
48
- this.options = Array.isArray(options.options) ? [...options.options] : [];
49
- this.filteredOptions = [];
50
- this.fontSize = options.fontSize || 16;
51
- this.focused = false;
52
- this.platform = framework.platform;
53
- this.cursorVisible = true;
54
- this.cursorPosition = this.value.length;
55
- this.selectedIndex = -1;
56
- this.showDropdown = false;
57
- this.maxDropdownItems = options.maxDropdownItems || 5;
58
- this.dropdownItemHeight = this.fontSize + 16;
59
-
60
- // Options de style
61
- this.dropdownBackground = options.dropdownBackground || '#FFFFFF';
62
- this.hoverBackground = options.hoverBackground || '#F5F5F5';
63
- this.borderColor = options.borderColor || '#E0E0E0';
64
- this.selectedBackground = options.selectedBackground || '#E3F2FD';
65
-
66
- // Callbacks
67
- this.onSelect = options.onSelect || (() => {});
68
- this.onInput = options.onInput || (() => {});
69
-
70
- // Gestion du focus
71
- this.onFocus = this.onFocus.bind(this);
72
- this.onBlur = this.onBlur.bind(this);
73
- this.filterOptions = this.filterOptions.bind(this);
74
-
75
- // Enregistrer cet input
76
- InputDatalist.allInputs.add(this);
77
-
78
- // Animation du curseur
79
- this.cursorInterval = setInterval(() => {
80
- if (this.focused) this.cursorVisible = !this.cursorVisible;
81
- }, 500);
82
-
83
- // Écouter les clics globaux pour détecter les clics hors input
84
- this.setupGlobalClickHandler();
85
-
86
- // Filtrer les options initiales
87
- if (this.value) {
88
- this.filterOptions(this.value);
89
- }
90
- }
91
-
92
- /**
93
- * Écoute les clics globaux pour détecter les clics hors input
94
- */
95
- setupGlobalClickHandler() {
96
- if (!InputDatalist.globalClickHandler) {
97
- InputDatalist.globalClickHandler = (e) => {
98
- let clickedOnInput = false;
99
-
100
- for (let input of InputDatalist.allInputs) {
101
- if (input.hiddenInput && e.target === input.hiddenInput) {
102
- clickedOnInput = true;
103
- break;
104
- }
105
- }
106
-
107
- if (!clickedOnInput) {
108
- InputDatalist.removeAllHiddenInputs();
109
- }
110
- };
111
-
112
- document.addEventListener('click', InputDatalist.globalClickHandler, true);
113
- document.addEventListener('touchstart', InputDatalist.globalClickHandler, true);
114
- }
115
- }
116
-
117
- /**
118
- * Configure l'input HTML caché
119
- * @private
120
- */
121
- setupHiddenInput() {
122
- if (this.hiddenInput) return;
123
-
124
- this.hiddenInput = document.createElement('input');
125
- this.hiddenInput.style.position = 'fixed';
126
- this.hiddenInput.type = 'text'; // Important: type text pour la saisie normale
127
- this.hiddenInput.style.opacity = '0';
128
- this.hiddenInput.style.pointerEvents = 'none';
129
- this.hiddenInput.style.top = '-100px';
130
- this.hiddenInput.style.zIndex = '9999';
131
- document.body.appendChild(this.hiddenInput);
132
-
133
- this.hiddenInput.addEventListener('input', (e) => {
134
- if (this.focused) {
135
- this.value = e.target.value;
136
- this.cursorPosition = this.value.length;
137
- this.filterOptions(this.value);
138
- this.onInput(this.value);
139
- }
140
- });
141
-
142
- this.hiddenInput.addEventListener('keydown', (e) => {
143
- switch (e.key) {
144
- case 'ArrowDown':
145
- e.preventDefault();
146
- this.selectNextOption();
147
- break;
148
-
149
- case 'ArrowUp':
150
- e.preventDefault();
151
- this.selectPreviousOption();
152
- break;
153
-
154
- case 'Enter':
155
- e.preventDefault();
156
- this.selectCurrentOption();
157
- break;
158
-
159
- case 'Escape':
160
- e.preventDefault();
161
- this.hideDropdown();
162
- // Garder le focus
163
- if (this.hiddenInput) {
164
- setTimeout(() => this.hiddenInput.focus(), 10);
165
- }
166
- break;
167
-
168
- case 'Tab':
169
- this.selectCurrentOption();
170
- break;
171
- }
172
- });
173
-
174
- this.hiddenInput.addEventListener('blur', () => {
175
- // Temporisation plus longue pour permettre la sélection avec la souris
176
- setTimeout(() => {
177
- if (!this.isDropdownActive) {
178
- this.focused = false;
179
- this.cursorVisible = false;
180
- this.hideDropdown();
181
-
182
- setTimeout(() => {
183
- this.destroyHiddenInput();
184
- }, 100);
185
- }
186
- }, 300);
187
- });
188
- }
189
-
190
- /**
191
- * Vérifie si le dropdown est actif (souris dessus)
192
- */
193
- get isDropdownActive() {
194
- // Cette propriété serait gérée via des événements mouseenter/mouseleave
195
- // Pour l'instant, on retourne false par défaut
196
- return false;
197
- }
198
-
199
- /**
200
- * Filtre les options selon la recherche
201
- * @param {string} search - Texte de recherche
202
- */
203
- filterOptions(search) {
204
- const searchLower = search.toLowerCase();
205
-
206
- if (search === '') {
207
- this.filteredOptions = [...this.options];
208
- } else {
209
- this.filteredOptions = this.options.filter(option =>
210
- option.toLowerCase().includes(searchLower)
211
- );
212
- }
213
-
214
- this.selectedIndex = this.filteredOptions.length > 0 ? 0 : -1;
215
- this.showDropdown = this.filteredOptions.length > 0 && this.focused;
216
- }
217
-
218
- /**
219
- * Sélectionne l'option suivante
220
- */
221
- selectNextOption() {
222
- if (this.filteredOptions.length === 0) return;
223
-
224
- this.selectedIndex = (this.selectedIndex + 1) % this.filteredOptions.length;
225
- this.ensureSelectedVisible();
226
- }
227
-
228
- /**
229
- * Sélectionne l'option précédente
230
- */
231
- selectPreviousOption() {
232
- if (this.filteredOptions.length === 0) return;
233
-
234
- this.selectedIndex = this.selectedIndex <= 0
235
- ? this.filteredOptions.length - 1
236
- : this.selectedIndex - 1;
237
- this.ensureSelectedVisible();
238
- }
239
-
240
- /**
241
- * Assure que l'option sélectionnée est visible
242
- */
243
- ensureSelectedVisible() {
244
- // Pour l'instant, on s'assure juste que l'index est valide
245
- if (this.selectedIndex >= this.filteredOptions.length) {
246
- this.selectedIndex = this.filteredOptions.length - 1;
247
- }
248
- }
249
-
250
- /**
251
- * Sélectionne l'option actuelle
252
- */
253
- selectCurrentOption() {
254
- if (this.selectedIndex >= 0 && this.selectedIndex < this.filteredOptions.length) {
255
- const selectedOption = this.filteredOptions[this.selectedIndex];
256
- this.value = selectedOption;
257
-
258
- if (this.hiddenInput) {
259
- this.hiddenInput.value = selectedOption;
260
- }
261
-
262
- this.filterOptions(selectedOption);
263
- this.onSelect(selectedOption);
264
- this.hideDropdown();
265
- }
266
- }
267
-
268
- /**
269
- * Masque le dropdown
270
- */
271
- hideDropdown() {
272
- this.showDropdown = false;
273
- this.selectedIndex = -1;
274
- }
275
-
276
- /**
277
- * Ajoute une option à la liste
278
- * @param {string} option - Option à ajouter
279
- */
280
- addOption(option) {
281
- if (!this.options.includes(option)) {
282
- this.options.push(option);
283
- this.filterOptions(this.value);
284
- }
285
- }
286
-
287
- /**
288
- * Supprime une option de la liste
289
- * @param {string} option - Option à supprimer
290
- */
291
- removeOption(option) {
292
- const index = this.options.indexOf(option);
293
- if (index > -1) {
294
- this.options.splice(index, 1);
295
- this.filterOptions(this.value);
296
- }
297
- }
298
-
299
- /**
300
- * Remplace toutes les options
301
- * @param {Array} newOptions - Nouvelles options
302
- */
303
- setOptions(newOptions) {
304
- this.options = Array.isArray(newOptions) ? [...newOptions] : [];
305
- this.filterOptions(this.value);
306
- }
307
-
308
- /**
309
- * Gère le focus
310
- */
311
- onFocus() {
312
- if (InputDatalist.activeInput === this) {
313
- return;
314
- }
315
-
316
- InputDatalist.removeAllHiddenInputs();
317
-
318
- for (let input of InputDatalist.allInputs) {
319
- if (input !== this) {
320
- input.focused = false;
321
- input.cursorVisible = false;
322
- input.hideDropdown();
323
- }
324
- }
325
-
326
- this.focused = true;
327
- this.cursorVisible = true;
328
- InputDatalist.activeInput = this;
329
-
330
- // Filtrer et montrer les options
331
- this.filterOptions(this.value);
332
- this.showDropdown = this.filteredOptions.length > 0;
333
-
334
- this.setupHiddenInput();
335
-
336
- if (this.hiddenInput) {
337
- this.hiddenInput.value = this.value;
338
-
339
- const adjustedY = this.y + this.framework.scrollOffset;
340
- this.hiddenInput.style.top = `${adjustedY}px`;
341
-
342
- setTimeout(() => {
343
- if (this.hiddenInput && this.focused) {
344
- this.hiddenInput.focus();
345
- this.hiddenInput.setSelectionRange(this.value.length, this.value.length);
346
- }
347
- }, 50);
348
- }
349
- }
350
-
351
- /**
352
- * Gère le blur
353
- */
354
- onBlur() {
355
- this.focused = false;
356
- this.cursorVisible = false;
357
- // Le dropdown sera caché par le blur handler de l'input HTML
358
- }
359
-
360
- /**
361
- * Détruit l'input HTML
362
- */
363
- destroyHiddenInput() {
364
- if (this.hiddenInput && this.hiddenInput.parentNode) {
365
- this.hiddenInput.parentNode.removeChild(this.hiddenInput);
366
- this.hiddenInput = null;
367
- }
368
- }
369
-
370
- /**
371
- * Vérifie si un point est dans le dropdown
372
- * @param {number} x - Coordonnée X
373
- * @param {number} y - Coordonnée Y
374
- * @returns {number|null} Index de l'option ou null
375
- */
376
- getDropdownOptionAtPoint(x, y) {
377
- if (!this.showDropdown || this.filteredOptions.length === 0) {
378
- return null;
379
- }
380
-
381
- const dropdownY = this.y + this.height;
382
- const itemsToShow = Math.min(this.filteredOptions.length, this.maxDropdownItems);
383
- const dropdownHeight = itemsToShow * this.dropdownItemHeight;
384
-
385
- // Vérifier si le point est dans la zone du dropdown
386
- if (x >= this.x &&
387
- x <= this.x + this.width &&
388
- y >= dropdownY &&
389
- y <= dropdownY + dropdownHeight) {
390
-
391
- const relativeY = y - dropdownY;
392
- const optionIndex = Math.floor(relativeY / this.dropdownItemHeight);
393
-
394
- if (optionIndex >= 0 && optionIndex < itemsToShow) {
395
- // Retourner l'index réel dans filteredOptions
396
- return optionIndex;
397
- }
398
- }
399
-
400
- return null;
401
- }
402
-
403
- /**
404
- * Gère le clic
405
- * @param {number} x - Coordonnée X du clic
406
- * @param {number} y - Coordonnée Y du clic
407
- * @returns {boolean} True si le clic a été géré
408
- */
409
- onClick(x, y) {
410
- // Vérifier si on clique sur une option du dropdown
411
- const optionIndex = this.getDropdownOptionAtPoint(x, y);
412
- if (optionIndex !== null) {
413
- this.selectedIndex = optionIndex;
414
- this.selectCurrentOption();
415
- return true;
416
- }
417
-
418
- // Vérifier si on clique dans la zone d'input
419
- if (this.isPointInside(x, y)) {
420
- this.onFocus();
421
- return true;
422
- }
423
-
424
- // Clic hors de l'input et du dropdown
425
- if (this.showDropdown) {
426
- this.hideDropdown();
427
- }
428
-
429
- return false;
430
- }
431
-
432
- /**
433
- * Méthode statique pour détruire tous les inputs HTML
434
- */
435
- static removeAllHiddenInputs() {
436
- for (let input of InputDatalist.allInputs) {
437
- input.focused = false;
438
- input.cursorVisible = false;
439
- input.hideDropdown();
440
-
441
- if (input.hiddenInput && input.hiddenInput.parentNode) {
442
- input.hiddenInput.parentNode.removeChild(input.hiddenInput);
443
- input.hiddenInput = null;
444
- }
445
- }
446
-
447
- InputDatalist.activeInput = null;
448
- }
449
-
450
- /**
451
- * Vérifie si un point est dans les limites
452
- * @param {number} x - Coordonnée X
453
- * @param {number} y - Coordonnée Y
454
- * @returns {boolean} True si le point est dans l'input
455
- */
456
- isPointInside(x, y) {
457
- return x >= this.x &&
458
- x <= this.x + this.width &&
459
- y >= this.y &&
460
- y <= this.y + this.height;
461
- }
462
-
463
- /**
464
- * Dessine l'input
465
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
466
- */
467
- draw(ctx) {
468
- ctx.save();
469
-
470
- // Style de base de l'input
471
- if (this.platform === 'material') {
472
- ctx.strokeStyle = this.focused ? '#6200EE' : '#CCCCCC';
473
- ctx.lineWidth = this.focused ? 2 : 1;
474
- ctx.beginPath();
475
- ctx.moveTo(this.x, this.y + this.height);
476
- ctx.lineTo(this.x + this.width, this.y + this.height);
477
- ctx.stroke();
478
- } else {
479
- ctx.strokeStyle = this.focused ? '#007AFF' : '#C7C7CC';
480
- ctx.lineWidth = 1;
481
- ctx.beginPath();
482
- this.roundRect(ctx, this.x, this.y, this.width, this.height, 8);
483
- ctx.stroke();
484
- }
485
-
486
- // Texte de l'input
487
- ctx.fillStyle = this.value ? '#000000' : '#999999';
488
- ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
489
- ctx.textAlign = 'left';
490
- ctx.textBaseline = 'middle';
491
-
492
- const displayText = this.value || this.placeholder;
493
- const textX = this.x + 10;
494
- const textY = this.y + this.height / 2;
495
-
496
- // Tronquer le texte si trop long
497
- const maxTextWidth = this.width - 30; // 10px padding + 20px pour l'icône
498
- let finalDisplayText = displayText;
499
- const textWidth = ctx.measureText(displayText).width;
500
-
501
- if (textWidth > maxTextWidth) {
502
- // Tronquer avec "..."
503
- for (let i = displayText.length; i > 0; i--) {
504
- const truncated = displayText.substring(0, i) + '...';
505
- if (ctx.measureText(truncated).width <= maxTextWidth) {
506
- finalDisplayText = truncated;
507
- break;
508
- }
509
- }
510
- }
511
-
512
- ctx.fillText(finalDisplayText, textX, textY);
513
-
514
- // Curseur
515
- if (this.focused && this.cursorVisible && this.value) {
516
- const cursorTextWidth = ctx.measureText(finalDisplayText).width;
517
- ctx.fillStyle = '#000000';
518
- ctx.fillRect(textX + cursorTextWidth, this.y + 10, 2, this.height - 20);
519
- }
520
-
521
- // Icône de dropdown (flèche) seulement si des options existent
522
- if (this.options.length > 0) {
523
- this.drawDropdownIcon(ctx);
524
- }
525
-
526
- // Dropdown - TOUJOURS dessiner en dernier pour être au-dessus
527
- if (this.showDropdown && this.filteredOptions.length > 0) {
528
- this.drawDropdown(ctx);
529
- }
530
-
531
- ctx.restore();
532
- }
533
-
534
- /**
535
- * Dessine l'icône de dropdown
536
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
537
- */
538
- drawDropdownIcon(ctx) {
539
- const iconSize = 12;
540
- const iconX = this.x + this.width - iconSize - 10;
541
- const iconY = this.y + this.height / 2 - iconSize / 2;
542
-
543
- ctx.strokeStyle = '#666666';
544
- ctx.lineWidth = 2;
545
- ctx.beginPath();
546
- ctx.moveTo(iconX, iconY + iconSize / 3);
547
- ctx.lineTo(iconX + iconSize / 2, iconY + 2 * iconSize / 3);
548
- ctx.lineTo(iconX + iconSize, iconY + iconSize / 3);
549
- ctx.stroke();
550
- }
551
-
552
- /**
553
- * Dessine le dropdown
554
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
555
- */
556
- drawDropdown(ctx) {
557
- const dropdownY = this.y + this.height;
558
- const itemsToShow = Math.min(this.filteredOptions.length, this.maxDropdownItems);
559
- const dropdownHeight = itemsToShow * this.dropdownItemHeight;
560
-
561
- // Sauvegarder l'état du contexte
562
- ctx.save();
563
-
564
- // Ombre portée
565
- ctx.shadowColor = 'rgba(0, 0, 0, 0.1)';
566
- ctx.shadowBlur = 10;
567
- ctx.shadowOffsetX = 0;
568
- ctx.shadowOffsetY = 2;
569
-
570
- // Fond du dropdown
571
- ctx.fillStyle = this.dropdownBackground;
572
- this.roundRect(ctx, this.x, dropdownY, this.width, dropdownHeight, 4);
573
- ctx.fill();
574
-
575
- // Désactiver l'ombre pour la bordure
576
- ctx.shadowColor = 'transparent';
577
- ctx.shadowBlur = 0;
578
- ctx.shadowOffsetY = 0;
579
-
580
- // Bordure
581
- ctx.strokeStyle = this.borderColor;
582
- ctx.lineWidth = 1;
583
- this.roundRect(ctx, this.x, dropdownY, this.width, dropdownHeight, 4);
584
- ctx.stroke();
585
-
586
- // Options
587
- ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
588
- ctx.textAlign = 'left';
589
- ctx.textBaseline = 'middle';
590
-
591
- for (let i = 0; i < itemsToShow; i++) {
592
- const optionY = dropdownY + i * this.dropdownItemHeight;
593
- const optionHeight = this.dropdownItemHeight;
594
-
595
- // Fond de l'option (si survolée/sélectionnée)
596
- if (i === this.selectedIndex) {
597
- ctx.fillStyle = this.selectedBackground;
598
- ctx.fillRect(this.x, optionY, this.width, optionHeight);
599
- }
600
-
601
- // Séparateur (sauf pour le premier élément)
602
- if (i > 0) {
603
- ctx.strokeStyle = this.borderColor;
604
- ctx.lineWidth = 0.5;
605
- ctx.beginPath();
606
- ctx.moveTo(this.x + 10, optionY);
607
- ctx.lineTo(this.x + this.width - 10, optionY);
608
- ctx.stroke();
609
- }
610
-
611
- // RÉINITIALISER la couleur du texte à chaque itération
612
- ctx.fillStyle = '#000000';
613
-
614
- // Dessiner le texte de l'option
615
- const optionText = this.filteredOptions[i];
616
- const textX = this.x + 10;
617
- const textY = optionY + optionHeight / 2;
618
-
619
- // Tronquer le texte si trop long
620
- const maxOptionWidth = this.width - 20;
621
- let displayOptionText = optionText;
622
- const optionTextWidth = ctx.measureText(optionText).width;
623
-
624
- if (optionTextWidth > maxOptionWidth) {
625
- for (let j = optionText.length; j > 0; j--) {
626
- const truncated = optionText.substring(0, j) + '...';
627
- if (ctx.measureText(truncated).width <= maxOptionWidth) {
628
- displayOptionText = truncated;
629
- break;
630
- }
631
- }
632
- }
633
-
634
- // Mettre en évidence la partie correspondante
635
- if (this.value && this.value.length > 0) {
636
- const searchLower = this.value.toLowerCase();
637
- const optionLower = optionText.toLowerCase();
638
- const matchIndex = optionLower.indexOf(searchLower);
639
-
640
- if (matchIndex >= 0 && matchIndex < displayOptionText.length) {
641
- // Partie avant la correspondance
642
- const beforeMatch = displayOptionText.substring(0, matchIndex);
643
- const matchLength = Math.min(this.value.length, displayOptionText.length - matchIndex);
644
- const matchPart = displayOptionText.substring(matchIndex, matchIndex + matchLength);
645
- const afterMatch = displayOptionText.substring(matchIndex + matchLength);
646
-
647
- // Dessiner partie avant
648
- ctx.fillStyle = '#666666';
649
- ctx.fillText(beforeMatch, textX, textY);
650
-
651
- // Dessiner partie correspondante
652
- const beforeWidth = ctx.measureText(beforeMatch).width;
653
- ctx.fillStyle = '#000000';
654
- ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
655
- ctx.fillText(matchPart, textX + beforeWidth, textY);
656
-
657
- // Dessiner partie après
658
- const matchWidth = ctx.measureText(matchPart).width;
659
- ctx.fillStyle = '#666666';
660
- ctx.fillText(afterMatch, textX + beforeWidth + matchWidth, textY);
661
- } else {
662
- // Pas de correspondance
663
- ctx.fillStyle = '#666666';
664
- ctx.fillText(displayOptionText, textX, textY);
665
- }
666
- } else {
667
- // Pas de valeur de recherche
668
- ctx.fillStyle = '#666666';
669
- ctx.fillText(displayOptionText, textX, textY);
670
- }
671
- }
672
-
673
- // Restaurer l'état du contexte
674
- ctx.restore();
675
- }
676
-
677
- /**
678
- * Dessine un rectangle avec coins arrondis
679
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
680
- * @param {number} x - Position X
681
- * @param {number} y - Position Y
682
- * @param {number} width - Largeur
683
- * @param {number} height - Hauteur
684
- * @param {number} radius - Rayon des coins
685
- * @private
686
- */
687
- roundRect(ctx, x, y, width, height, radius) {
688
- ctx.beginPath();
689
- ctx.moveTo(x + radius, y);
690
- ctx.lineTo(x + width - radius, y);
691
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
692
- ctx.lineTo(x + width, y + height - radius);
693
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
694
- ctx.lineTo(x + radius, y + height);
695
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
696
- ctx.lineTo(x, y + radius);
697
- ctx.quadraticCurveTo(x, y, x + radius, y);
698
- ctx.closePath();
699
- }
700
-
701
- /**
702
- * Nettoie les ressources
703
- */
704
- destroy() {
705
- this.destroyHiddenInput();
706
-
707
- if (this.cursorInterval) {
708
- clearInterval(this.cursorInterval);
709
- }
710
-
711
- InputDatalist.allInputs.delete(this);
712
-
713
- if (InputDatalist.allInputs.size === 0 && InputDatalist.globalClickHandler) {
714
- document.removeEventListener('click', InputDatalist.globalClickHandler, true);
715
- document.removeEventListener('touchstart', InputDatalist.globalClickHandler, true);
716
- InputDatalist.globalClickHandler = null;
717
- }
718
-
719
- super.destroy && super.destroy();
720
- }
721
- }
722
-
723
- export default InputDatalist;