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,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;
|