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,193 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Carte avec ombre et contenu
4
+ * @class
5
+ * @extends Component
6
+ * @property {Component[]} children - Enfants de la carte
7
+ * @property {number} padding - Padding interne
8
+ * @property {string} bgColor - Couleur de fond
9
+ * @property {number} elevation - Élévation (ombre)
10
+ * @property {number} borderRadius - Rayon des coins
11
+ * @property {boolean} clipContent - Clip le contenu
12
+ * @property {boolean} clickableChildren - Active les clics sur les enfants
13
+ */
14
+ class Card extends Component {
15
+ /**
16
+ * Crée une instance de Card
17
+ * @param {CanvasFramework} framework - Framework parent
18
+ * @param {Object} [options={}] - Options de configuration
19
+ * @param {number} [options.padding=16] - Padding interne
20
+ * @param {string} [options.bgColor='#FFFFFF'] - Couleur de fond
21
+ * @param {number} [options.elevation=2] - Élévation (ombre)
22
+ * @param {number} [options.borderRadius] - Rayon des coins (auto selon platform)
23
+ * @param {boolean} [options.clipContent=true] - Clip le contenu
24
+ * @param {boolean} [options.clickableChildren=true] - Active les clics enfants
25
+ */
26
+ constructor(framework, options = {}) {
27
+ super(framework, options);
28
+ this.children = [];
29
+ this.padding = options.padding || 16;
30
+ this.bgColor = options.bgColor || '#FFFFFF';
31
+ this.elevation = options.elevation || 2;
32
+ this.borderRadius = options.borderRadius || (framework.platform === 'material' ? 4 : 12);
33
+ this.clipContent = options.clipContent !== false;
34
+ this.clickableChildren = options.clickableChildren !== false; // NOUVEAU: activer/désactiver les enfants cliquables
35
+ }
36
+
37
+ /**
38
+ * Ajoute un enfant à la carte
39
+ * @param {Component} child - Composant enfant
40
+ * @returns {Component} L'enfant ajouté
41
+ */
42
+ add(child) {
43
+ // Ajuster les coordonnées de l'enfant pour qu'elles soient relatives à la Card
44
+ child.x = child.x || 0;
45
+ child.y = child.y || 0;
46
+ this.children.push(child);
47
+
48
+ // Marquer l'enfant comme appartenant à cette Card
49
+ child.parentCard = this;
50
+
51
+ return child;
52
+ }
53
+
54
+ /**
55
+ * Dessine la carte et ses enfants
56
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
57
+ */
58
+ draw(ctx) {
59
+ ctx.save();
60
+
61
+ // Ombre
62
+ if (this.elevation > 0) {
63
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.15)';
64
+ ctx.shadowBlur = this.elevation * 3;
65
+ ctx.shadowOffsetY = this.elevation;
66
+ }
67
+
68
+ // Background
69
+ ctx.fillStyle = this.bgColor;
70
+ ctx.beginPath();
71
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
72
+ ctx.fill();
73
+
74
+ ctx.shadowColor = 'transparent';
75
+
76
+ // Clipping pour empêcher le contenu de déborder
77
+ if (this.clipContent) {
78
+ ctx.beginPath();
79
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
80
+ ctx.clip();
81
+ }
82
+
83
+ // Children - dessinés relativement à la Card
84
+ for (let child of this.children) {
85
+ if (child.visible) {
86
+ // Sauvegarder les coordonnées originales
87
+ const originalX = child.x;
88
+ const originalY = child.y;
89
+
90
+ // Ajuster les coordonnées pour être relatives à la Card
91
+ child.x = this.x + this.padding + originalX;
92
+ child.y = this.y + this.padding + originalY;
93
+
94
+ // Dessiner l'enfant
95
+ child.draw(ctx);
96
+
97
+ // Restaurer les coordonnées originales
98
+ child.x = originalX;
99
+ child.y = originalY;
100
+ }
101
+ }
102
+
103
+ ctx.restore();
104
+ }
105
+
106
+ /**
107
+ * Vérifie les clics sur les enfants
108
+ * @param {number} x - Coordonnée X
109
+ * @param {number} y - Coordonnée Y
110
+ * @returns {boolean} True si un enfant a été cliqué
111
+ * @private
112
+ */
113
+ checkChildClick(x, y) {
114
+ // Ajuster y avec le scrollOffset
115
+ const adjustedY = y - this.framework.scrollOffset;
116
+
117
+ // Vérifier chaque enfant
118
+ for (let i = this.children.length - 1; i >= 0; i--) {
119
+ const child = this.children[i];
120
+
121
+ // Calculer les coordonnées absolues de l'enfant
122
+ const childX = this.x + this.padding + child.x;
123
+ const childY = this.y + this.padding + child.y;
124
+
125
+ // Vérifier si le clic est dans l'enfant
126
+ if (child.visible &&
127
+ adjustedY >= childY &&
128
+ adjustedY <= childY + child.height &&
129
+ x >= childX &&
130
+ x <= childX + child.width) {
131
+
132
+ // Si l'enfant a un onClick ou onPress, le déclencher
133
+ if (child.onClick) {
134
+ child.onClick();
135
+ return true;
136
+ } else if (child.onPress) {
137
+ child.onPress(x, adjustedY);
138
+ return true;
139
+ }
140
+ }
141
+ }
142
+
143
+ return false;
144
+ }
145
+
146
+ /**
147
+ * Dessine un rectangle avec coins arrondis
148
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
149
+ * @param {number} x - Position X
150
+ * @param {number} y - Position Y
151
+ * @param {number} width - Largeur
152
+ * @param {number} height - Hauteur
153
+ * @param {number} radius - Rayon des coins
154
+ * @private
155
+ */
156
+ roundRect(ctx, x, y, width, height, radius) {
157
+ ctx.beginPath();
158
+ ctx.moveTo(x + radius, y);
159
+ ctx.lineTo(x + width - radius, y);
160
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
161
+ ctx.lineTo(x + width, y + height - radius);
162
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
163
+ ctx.lineTo(x + radius, y + height);
164
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
165
+ ctx.lineTo(x, y + radius);
166
+ ctx.quadraticCurveTo(x, y, x + radius, y);
167
+ ctx.closePath();
168
+ }
169
+
170
+ /**
171
+ * Vérifie si un point est dans les limites
172
+ * @param {number} x - Coordonnée X
173
+ * @param {number} y - Coordonnée Y
174
+ * @returns {boolean} True si le point est dans la carte
175
+ */
176
+ isPointInside(x, y) {
177
+ return x >= this.x &&
178
+ x <= this.x + this.width &&
179
+ y >= this.y &&
180
+ y <= this.y + this.height;
181
+ }
182
+
183
+ /**
184
+ * Gère le clic sur la carte
185
+ * @private
186
+ */
187
+ onClick() {
188
+ // La Card elle-même peut avoir un onClick, mais on veut aussi vérifier les enfants
189
+ // Cette logique est gérée dans le CanvasFramework modifié ci-dessous
190
+ }
191
+ }
192
+
193
+ export default Card;
@@ -0,0 +1,180 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Case à cocher
4
+ * @class
5
+ * @extends Component
6
+ * @property {boolean} checked - État cochée
7
+ * @property {string} label - Texte du label
8
+ * @property {string} platform - Plateforme
9
+ * @property {number} boxWidth - Largeur de la case
10
+ * @property {number} boxHeight - Hauteur de la case
11
+ * @property {Function} onChange - Callback au changement
12
+ */
13
+ class Checkbox extends Component {
14
+ /**
15
+ * Crée une instance de Checkbox
16
+ * @param {CanvasFramework} framework - Framework parent
17
+ * @param {Object} [options={}] - Options de configuration
18
+ * @param {boolean} [options.checked=false] - État initial
19
+ * @param {string} [options.label=''] - Texte du label
20
+ * @param {Function} [options.onChange] - Callback au changement
21
+ */
22
+ constructor(framework, options = {}) {
23
+ super(framework, options);
24
+ this.checked = options.checked || false;
25
+ this.label = options.label || '';
26
+ this.platform = framework.platform;
27
+ this.boxWidth = 24; // Taille de la case
28
+ this.boxHeight = 24; // Taille de la case
29
+ this.onChange = options.onChange;
30
+
31
+ // Calculer la largeur totale incluant le label
32
+ this.totalWidth = this.label ? this.boxWidth + 8 + this.getTextWidth(this.label) : this.boxWidth;
33
+ this.width = this.totalWidth; // Mettre à jour la largeur totale
34
+ this.height = this.boxHeight; // Garder la même hauteur
35
+
36
+ // Définir onClick
37
+ this.onClick = this.handleClick.bind(this);
38
+ }
39
+
40
+ /**
41
+ * Calcule la largeur du texte
42
+ * @param {string} text - Texte à mesurer
43
+ * @returns {number} Largeur du texte
44
+ * @private
45
+ */
46
+ getTextWidth(text) {
47
+ // Utiliser le contexte temporaire pour mesurer le texte
48
+ const ctx = this.framework.ctx;
49
+ ctx.save();
50
+ ctx.font = '16px -apple-system, sans-serif';
51
+ const width = ctx.measureText(text).width;
52
+ ctx.restore();
53
+ return width;
54
+ }
55
+
56
+ /**
57
+ * Gère le clic sur la checkbox
58
+ * @private
59
+ */
60
+ handleClick() {
61
+ this.checked = !this.checked;
62
+ if (this.onChange) this.onChange(this.checked);
63
+ }
64
+
65
+ /**
66
+ * Dessine la checkbox
67
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
68
+ */
69
+ draw(ctx) {
70
+ ctx.save();
71
+
72
+ // Position de la case
73
+ const boxX = this.x;
74
+ const boxY = this.y;
75
+ const boxCenterX = boxX + this.boxWidth / 2;
76
+ const boxCenterY = boxY + this.boxHeight / 2;
77
+
78
+ if (this.platform === 'material') {
79
+ // Material Design Checkbox
80
+ if (this.checked) {
81
+ // Case cochée
82
+ ctx.fillStyle = '#6200EE';
83
+ ctx.beginPath();
84
+ this.roundRect(ctx, boxX, boxY, this.boxWidth, this.boxHeight, 2);
85
+ ctx.fill();
86
+
87
+ // Coche
88
+ ctx.strokeStyle = '#FFFFFF';
89
+ ctx.lineWidth = 2;
90
+ ctx.beginPath();
91
+ ctx.moveTo(boxX + 6, boxY + 12);
92
+ ctx.lineTo(boxX + 10, boxY + 16);
93
+ ctx.lineTo(boxX + 18, boxY + 8);
94
+ ctx.stroke();
95
+ } else {
96
+ // Case non cochée
97
+ ctx.strokeStyle = '#666666';
98
+ ctx.lineWidth = 2;
99
+ ctx.beginPath();
100
+ this.roundRect(ctx, boxX, boxY, this.boxWidth, this.boxHeight, 2);
101
+ ctx.stroke();
102
+ }
103
+ } else {
104
+ // Cupertino (iOS) Checkbox
105
+ if (this.checked) {
106
+ // Case cochée (iOS utilise plutôt un cercle)
107
+ ctx.fillStyle = '#007AFF';
108
+ ctx.beginPath();
109
+ ctx.arc(boxCenterX, boxCenterY, this.boxWidth/2, 0, Math.PI * 2);
110
+ ctx.fill();
111
+
112
+ // Coche
113
+ ctx.strokeStyle = '#FFFFFF';
114
+ ctx.lineWidth = 2;
115
+ ctx.beginPath();
116
+ ctx.moveTo(boxX + 6, boxCenterY);
117
+ ctx.lineTo(boxX + 10, boxCenterY + 4);
118
+ ctx.lineTo(boxX + 18, boxCenterY - 4);
119
+ ctx.stroke();
120
+ } else {
121
+ // Case non cochée
122
+ ctx.strokeStyle = '#C7C7CC';
123
+ ctx.lineWidth = 2;
124
+ ctx.beginPath();
125
+ ctx.arc(boxCenterX, boxCenterY, this.boxWidth/2, 0, Math.PI * 2);
126
+ ctx.stroke();
127
+ }
128
+ }
129
+
130
+ // Label
131
+ if (this.label) {
132
+ ctx.fillStyle = '#000000';
133
+ ctx.font = '16px -apple-system, sans-serif';
134
+ ctx.textAlign = 'left';
135
+ ctx.textBaseline = 'middle';
136
+ ctx.fillText(this.label, boxX + this.boxWidth + 8, boxCenterY);
137
+ }
138
+
139
+ ctx.restore();
140
+ }
141
+
142
+ /**
143
+ * Dessine un rectangle avec coins arrondis
144
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
145
+ * @param {number} x - Position X
146
+ * @param {number} y - Position Y
147
+ * @param {number} width - Largeur
148
+ * @param {number} height - Hauteur
149
+ * @param {number} radius - Rayon des coins
150
+ * @private
151
+ */
152
+ roundRect(ctx, x, y, width, height, radius) {
153
+ ctx.beginPath();
154
+ ctx.moveTo(x + radius, y);
155
+ ctx.lineTo(x + width - radius, y);
156
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
157
+ ctx.lineTo(x + width, y + height - radius);
158
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
159
+ ctx.lineTo(x + radius, y + height);
160
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
161
+ ctx.lineTo(x, y + radius);
162
+ ctx.quadraticCurveTo(x, y, x + radius, y);
163
+ ctx.closePath();
164
+ }
165
+
166
+ /**
167
+ * Vérifie si un point est dans les limites
168
+ * @param {number} x - Coordonnée X
169
+ * @param {number} y - Coordonnée Y
170
+ * @returns {boolean} True si le point est dans la checkbox
171
+ */
172
+ isPointInside(x, y) {
173
+ return x >= this.x &&
174
+ x <= this.x + this.width &&
175
+ y >= this.y &&
176
+ y <= this.y + this.height;
177
+ }
178
+ }
179
+
180
+ export default Checkbox;
@@ -0,0 +1,212 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Chip (étiquette cliquable)
4
+ * @class
5
+ * @extends Component
6
+ * @property {string} text - Texte
7
+ * @property {string|null} icon - Icône
8
+ * @property {boolean} closable - Peut être fermé
9
+ * @property {string} platform - Plateforme
10
+ * @property {string} bgColor - Couleur de fond
11
+ * @property {string} textColor - Couleur du texte
12
+ * @property {Function} onClose - Callback à la fermeture
13
+ * @property {number} borderRadius - Rayon des coins
14
+ * @property {Object|null} closeButtonRect - Rectangle du bouton fermer
15
+ */
16
+ class Chip extends Component {
17
+ /**
18
+ * Crée une instance de Chip
19
+ * @param {CanvasFramework} framework - Framework parent
20
+ * @param {Object} [options={}] - Options de configuration
21
+ * @param {string} [options.text=''] - Texte
22
+ * @param {string} [options.icon] - Icône
23
+ * @param {boolean} [options.closable=true] - Peut être fermé
24
+ * @param {string} [options.bgColor] - Couleur de fond (auto selon platform)
25
+ * @param {string} [options.textColor='#000000'] - Couleur du texte
26
+ * @param {Function} [options.onClose] - Callback à la fermeture
27
+ * @param {number} [options.height=32] - Hauteur
28
+ */
29
+ constructor(framework, options = {}) {
30
+ super(framework, options);
31
+ this.text = options.text || '';
32
+ this.icon = options.icon || null;
33
+ this.closable = options.closable !== false;
34
+ this.platform = framework.platform;
35
+ this.bgColor = options.bgColor || (framework.platform === 'material' ? '#E0E0E0' : '#F0F0F0');
36
+ this.textColor = options.textColor || '#000000';
37
+ this.onClose = options.onClose;
38
+
39
+ // Calculer la largeur en fonction du contenu
40
+ const ctx = framework.ctx;
41
+ ctx.font = '14px -apple-system, sans-serif';
42
+ const textWidth = ctx.measureText(this.text).width;
43
+ const iconWidth = this.icon ? 24 : 0;
44
+ const closeWidth = this.closable ? 24 : 0;
45
+ this.width = iconWidth + textWidth + closeWidth + 24; // padding
46
+ this.height = options.height || 32;
47
+ this.borderRadius = this.height / 2;
48
+
49
+ this.closeButtonRect = null;
50
+ this.onPress = this.handlePress.bind(this);
51
+ }
52
+
53
+ /**
54
+ * Dessine le chip
55
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
56
+ */
57
+ draw(ctx) {
58
+ ctx.save();
59
+
60
+ // Background
61
+ ctx.fillStyle = this.pressed ? this.darkenColor(this.bgColor) : this.bgColor;
62
+ ctx.beginPath();
63
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
64
+ ctx.fill();
65
+
66
+ let currentX = this.x + 12;
67
+
68
+ // Icône
69
+ if (this.icon) {
70
+ ctx.font = '16px sans-serif';
71
+ ctx.textAlign = 'left';
72
+ ctx.textBaseline = 'middle';
73
+ ctx.fillStyle = this.textColor;
74
+ ctx.fillText(this.icon, currentX, this.y + this.height / 2);
75
+ currentX += 20;
76
+ }
77
+
78
+ // Texte
79
+ ctx.font = '14px -apple-system, sans-serif';
80
+ ctx.fillStyle = this.textColor;
81
+ ctx.textAlign = 'left';
82
+ ctx.textBaseline = 'middle';
83
+ ctx.fillText(this.text, currentX, this.y + this.height / 2);
84
+
85
+ // Bouton de fermeture
86
+ if (this.closable) {
87
+ const closeX = this.x + this.width - 20;
88
+ const closeY = this.y + this.height / 2;
89
+
90
+ this.closeButtonRect = {
91
+ x: closeX - 8,
92
+ y: closeY - 8,
93
+ width: 16,
94
+ height: 16
95
+ };
96
+
97
+ // Cercle du bouton (optionnel)
98
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
99
+ ctx.beginPath();
100
+ ctx.arc(closeX, closeY, 8, 0, Math.PI * 2);
101
+ ctx.fill();
102
+
103
+ // Croix (X)
104
+ ctx.strokeStyle = this.textColor;
105
+ ctx.lineWidth = 1.5;
106
+ ctx.lineCap = 'round';
107
+ ctx.beginPath();
108
+ ctx.moveTo(closeX - 4, closeY - 4);
109
+ ctx.lineTo(closeX + 4, closeY + 4);
110
+ ctx.stroke();
111
+
112
+ ctx.beginPath();
113
+ ctx.moveTo(closeX + 4, closeY - 4);
114
+ ctx.lineTo(closeX - 4, closeY + 4);
115
+ ctx.stroke();
116
+ }
117
+
118
+ ctx.restore();
119
+ }
120
+
121
+ /**
122
+ * Gère la pression (clic)
123
+ * @param {number} x - Coordonnée X
124
+ * @param {number} y - Coordonnée Y
125
+ * @private
126
+ */
127
+ handlePress(x, y) {
128
+ const adjustedY = y - this.framework.scrollOffset;
129
+
130
+ // Vérifier si on clique sur le bouton de fermeture
131
+ if (this.closable && this.closeButtonRect) {
132
+ if (x >= this.closeButtonRect.x &&
133
+ x <= this.closeButtonRect.x + this.closeButtonRect.width &&
134
+ adjustedY >= this.closeButtonRect.y &&
135
+ adjustedY <= this.closeButtonRect.y + this.closeButtonRect.height) {
136
+ if (this.onClose) this.onClose();
137
+ return;
138
+ }
139
+ }
140
+
141
+ // Sinon, déclencher onClick normal
142
+ if (this.onClick) this.onClick();
143
+ }
144
+
145
+ /**
146
+ * Dessine un rectangle avec coins arrondis
147
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
148
+ * @param {number} x - Position X
149
+ * @param {number} y - Position Y
150
+ * @param {number} width - Largeur
151
+ * @param {number} height - Hauteur
152
+ * @param {number} radius - Rayon des coins
153
+ * @private
154
+ */
155
+ roundRect(ctx, x, y, width, height, radius) {
156
+ ctx.beginPath();
157
+ ctx.moveTo(x + radius, y);
158
+ ctx.lineTo(x + width - radius, y);
159
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
160
+ ctx.lineTo(x + width, y + height - radius);
161
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
162
+ ctx.lineTo(x + radius, y + height);
163
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
164
+ ctx.lineTo(x, y + radius);
165
+ ctx.quadraticCurveTo(x, y, x + radius, y);
166
+ ctx.closePath();
167
+ }
168
+
169
+ /**
170
+ * Assombrit une couleur
171
+ * @param {string} color - Couleur
172
+ * @returns {string} Couleur assombrie
173
+ * @private
174
+ */
175
+ darkenColor(color) {
176
+ // Utiliser la même méthode que Button
177
+ if (color.startsWith('#')) {
178
+ const rgb = this.hexToRgb(color);
179
+ return `rgb(${Math.max(0, rgb.r - 20)}, ${Math.max(0, rgb.g - 20)}, ${Math.max(0, rgb.b - 20)})`;
180
+ }
181
+ return color;
182
+ }
183
+
184
+ /**
185
+ * Convertit une couleur hex en RGB
186
+ * @param {string} hex - Couleur hexadécimale
187
+ * @returns {{r: number, g: number, b: number}} Objet RGB
188
+ * @private
189
+ */
190
+ hexToRgb(hex) {
191
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
192
+ return result ? {
193
+ r: parseInt(result[1], 16),
194
+ g: parseInt(result[2], 16),
195
+ b: parseInt(result[3], 16)
196
+ } : { r: 0, g: 0, b: 0 };
197
+ }
198
+
199
+ /**
200
+ * Vérifie si un point est dans les limites
201
+ * @param {number} x - Coordonnée X
202
+ * @param {number} y - Coordonnée Y
203
+ * @returns {boolean} True si le point est dans le chip
204
+ */
205
+ isPointInside(x, y) {
206
+ const adjustedY = y - this.framework.scrollOffset;
207
+ return x >= this.x && x <= this.x + this.width &&
208
+ adjustedY >= this.y && adjustedY <= this.y + this.height;
209
+ }
210
+ }
211
+
212
+ export default Chip;