canvasframework 0.3.8 → 0.3.10
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/components/AppBar.js +164 -70
- package/components/BottomNavigationBar.js +206 -69
- package/components/Button.js +182 -62
- package/components/InputTags.js +586 -0
- package/components/MorphingFAB.js +428 -0
- package/components/PasswordInput.js +462 -0
- package/components/SpeedDialFAB.js +397 -0
- package/components/TimePicker.js +443 -0
- package/core/CanvasFramework.js +8 -4
- package/index.js +5 -1
- package/package.json +1 -1
package/components/Button.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Bouton cliquable
|
|
4
|
+
* Bouton cliquable avec variantes Material et Cupertino
|
|
5
5
|
* @class
|
|
6
6
|
* @extends Component
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @property {string} textColor - Couleur du texte
|
|
12
|
-
* @property {string} rippleColor - Couleur du ripple
|
|
13
|
-
* @property {number} elevation - Élévation (ombre)
|
|
14
|
-
* @property {Array} ripples - Effets ripple
|
|
7
|
+
*
|
|
8
|
+
* Types Material: 'filled', 'outlined', 'text', 'elevated', 'tonal'
|
|
9
|
+
* Types Cupertino: 'filled', 'gray', 'tinted', 'bordered', 'plain'
|
|
10
|
+
* Shapes: 'rounded', 'square', 'pill' (très arrondi)
|
|
15
11
|
*/
|
|
16
12
|
class Button extends Component {
|
|
17
13
|
/**
|
|
@@ -20,35 +16,135 @@ class Button extends Component {
|
|
|
20
16
|
* @param {Object} [options={}] - Options de configuration
|
|
21
17
|
* @param {string} [options.text='Button'] - Texte
|
|
22
18
|
* @param {number} [options.fontSize=16] - Taille de police
|
|
23
|
-
* @param {string} [options.
|
|
24
|
-
* @param {string} [options.
|
|
25
|
-
* @param {
|
|
19
|
+
* @param {string} [options.type] - Type de bouton (auto selon platform)
|
|
20
|
+
* @param {string} [options.shape='rounded'] - Forme: 'rounded', 'square', 'pill'
|
|
21
|
+
* @param {string} [options.bgColor] - Couleur personnalisée
|
|
22
|
+
* @param {string} [options.textColor] - Couleur du texte personnalisée
|
|
23
|
+
* @param {number} [options.elevation=2] - Élévation (Material elevated)
|
|
26
24
|
*/
|
|
27
25
|
constructor(framework, options = {}) {
|
|
28
26
|
super(framework, options);
|
|
29
27
|
this.text = options.text || 'Button';
|
|
30
28
|
this.fontSize = options.fontSize || 16;
|
|
31
29
|
this.platform = framework.platform;
|
|
30
|
+
this.shape = options.shape || 'rounded';
|
|
32
31
|
|
|
33
|
-
//
|
|
32
|
+
// Définir le type de bouton selon la plateforme
|
|
34
33
|
if (this.platform === 'material') {
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.rippleColor = 'rgba(255, 255, 255, 0.3)';
|
|
38
|
-
this.elevation = options.elevation || 2;
|
|
34
|
+
this.type = options.type || 'filled';
|
|
35
|
+
this.setupMaterialStyle(options);
|
|
39
36
|
} else {
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
this.borderRadius = 10;
|
|
37
|
+
this.type = options.type || 'filled';
|
|
38
|
+
this.setupCupertinoStyle(options);
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
//
|
|
41
|
+
// Effets ripple (Material uniquement)
|
|
46
42
|
this.ripples = [];
|
|
47
43
|
|
|
48
|
-
// Bind
|
|
44
|
+
// Bind
|
|
49
45
|
this.onPress = this.handlePress.bind(this);
|
|
50
46
|
}
|
|
51
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Configure le style Material Design
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
setupMaterialStyle(options) {
|
|
53
|
+
const baseColor = options.bgColor || '#6200EE';
|
|
54
|
+
this.elevation = options.elevation || 2;
|
|
55
|
+
|
|
56
|
+
switch (this.type) {
|
|
57
|
+
case 'filled':
|
|
58
|
+
this.bgColor = baseColor;
|
|
59
|
+
this.textColor = options.textColor || '#FFFFFF';
|
|
60
|
+
this.borderWidth = 0;
|
|
61
|
+
this.rippleColor = 'rgba(255, 255, 255, 0.3)';
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'outlined':
|
|
65
|
+
this.bgColor = 'transparent';
|
|
66
|
+
this.textColor = options.textColor || baseColor;
|
|
67
|
+
this.borderColor = baseColor;
|
|
68
|
+
this.borderWidth = 1;
|
|
69
|
+
this.rippleColor = this.hexToRgba(baseColor, 0.2);
|
|
70
|
+
this.elevation = 0;
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'text':
|
|
74
|
+
this.bgColor = 'transparent';
|
|
75
|
+
this.textColor = options.textColor || baseColor;
|
|
76
|
+
this.borderWidth = 0;
|
|
77
|
+
this.rippleColor = this.hexToRgba(baseColor, 0.2);
|
|
78
|
+
this.elevation = 0;
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case 'elevated':
|
|
82
|
+
this.bgColor = options.bgColor || '#FFFFFF';
|
|
83
|
+
this.textColor = options.textColor || baseColor;
|
|
84
|
+
this.borderWidth = 0;
|
|
85
|
+
this.rippleColor = this.hexToRgba(baseColor, 0.2);
|
|
86
|
+
this.elevation = options.elevation || 4;
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case 'tonal':
|
|
90
|
+
this.bgColor = this.hexToRgba(baseColor, 0.3);
|
|
91
|
+
this.textColor = options.textColor || baseColor;
|
|
92
|
+
this.borderWidth = 0;
|
|
93
|
+
this.rippleColor = this.hexToRgba(baseColor, 0.3);
|
|
94
|
+
this.elevation = 0;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Configure le style Cupertino (iOS)
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
setupCupertinoStyle(options) {
|
|
104
|
+
const baseColor = options.bgColor || '#007AFF';
|
|
105
|
+
|
|
106
|
+
switch (this.type) {
|
|
107
|
+
case 'filled':
|
|
108
|
+
this.bgColor = baseColor;
|
|
109
|
+
this.textColor = options.textColor || '#FFFFFF';
|
|
110
|
+
this.borderWidth = 0;
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case 'gray':
|
|
114
|
+
this.bgColor = 'rgba(120, 120, 128, 0.16)';
|
|
115
|
+
this.textColor = options.textColor || baseColor;
|
|
116
|
+
this.borderWidth = 0;
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'tinted':
|
|
120
|
+
this.bgColor = this.hexToRgba(baseColor, 0.2);
|
|
121
|
+
this.textColor = options.textColor || baseColor;
|
|
122
|
+
this.borderWidth = 0;
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'plain':
|
|
126
|
+
this.bgColor = 'transparent';
|
|
127
|
+
this.textColor = options.textColor || baseColor;
|
|
128
|
+
this.borderWidth = 0;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Retourne le rayon des coins selon la forme
|
|
135
|
+
* @returns {number} Rayon en pixels
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
getBorderRadius() {
|
|
139
|
+
switch (this.shape) {
|
|
140
|
+
case 'square':
|
|
141
|
+
return 0;
|
|
142
|
+
case 'rounded':
|
|
143
|
+
default:
|
|
144
|
+
return this.platform === 'material' ? 4 : 10;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
52
148
|
/**
|
|
53
149
|
* Gère la pression sur le bouton
|
|
54
150
|
* @param {number} x - Coordonnée X
|
|
@@ -56,7 +152,6 @@ class Button extends Component {
|
|
|
56
152
|
* @private
|
|
57
153
|
*/
|
|
58
154
|
handlePress(x, y) {
|
|
59
|
-
// Créer un ripple au point de clic
|
|
60
155
|
if (this.platform === 'material') {
|
|
61
156
|
const adjustedY = y - this.framework.scrollOffset;
|
|
62
157
|
this.ripples.push({
|
|
@@ -89,7 +184,6 @@ class Button extends Component {
|
|
|
89
184
|
}
|
|
90
185
|
}
|
|
91
186
|
|
|
92
|
-
// Nettoyer les ripples terminés
|
|
93
187
|
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
94
188
|
|
|
95
189
|
if (hasActiveRipples) {
|
|
@@ -107,31 +201,44 @@ class Button extends Component {
|
|
|
107
201
|
draw(ctx) {
|
|
108
202
|
ctx.save();
|
|
109
203
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
204
|
+
const radius = this.getBorderRadius();
|
|
205
|
+
|
|
206
|
+
// Ombre Material (elevated/filled)
|
|
207
|
+
if (this.platform === 'material' && this.elevation > 0 && !this.pressed) {
|
|
208
|
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
|
209
|
+
ctx.shadowBlur = this.elevation * 2;
|
|
210
|
+
ctx.shadowOffsetY = this.elevation;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Background
|
|
214
|
+
if (this.bgColor !== 'transparent') {
|
|
119
215
|
ctx.fillStyle = this.pressed ? this.darkenColor(this.bgColor) : this.bgColor;
|
|
120
216
|
ctx.beginPath();
|
|
121
|
-
this.roundRect(ctx, this.x, this.y, this.width, this.height,
|
|
217
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
|
|
122
218
|
ctx.fill();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Réinitialiser l'ombre
|
|
222
|
+
ctx.shadowColor = 'transparent';
|
|
223
|
+
ctx.shadowBlur = 0;
|
|
224
|
+
ctx.shadowOffsetY = 0;
|
|
225
|
+
|
|
226
|
+
// Bordure
|
|
227
|
+
if (this.borderWidth > 0) {
|
|
228
|
+
ctx.strokeStyle = this.borderColor;
|
|
229
|
+
ctx.lineWidth = this.borderWidth;
|
|
230
|
+
ctx.beginPath();
|
|
231
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
|
|
232
|
+
ctx.stroke();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Ripple effect (Material)
|
|
236
|
+
if (this.platform === 'material') {
|
|
129
237
|
ctx.save();
|
|
130
238
|
ctx.beginPath();
|
|
131
|
-
this.roundRect(ctx, this.x, this.y, this.width, this.height,
|
|
239
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
|
|
132
240
|
ctx.clip();
|
|
133
241
|
|
|
134
|
-
// Dessiner les ripples
|
|
135
242
|
for (let ripple of this.ripples) {
|
|
136
243
|
ctx.globalAlpha = ripple.opacity;
|
|
137
244
|
ctx.fillStyle = this.rippleColor;
|
|
@@ -141,17 +248,20 @@ class Button extends Component {
|
|
|
141
248
|
}
|
|
142
249
|
|
|
143
250
|
ctx.restore();
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Effet pressed pour iOS (overlay sombre)
|
|
254
|
+
if (this.platform === 'cupertino' && this.pressed && this.bgColor !== 'transparent') {
|
|
255
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
148
256
|
ctx.beginPath();
|
|
149
|
-
this.roundRect(ctx, this.x, this.y, this.width, this.height,
|
|
257
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
|
|
150
258
|
ctx.fill();
|
|
151
259
|
}
|
|
152
260
|
|
|
153
261
|
// Texte
|
|
154
|
-
ctx.fillStyle = this.
|
|
262
|
+
ctx.fillStyle = this.pressed && this.platform === 'cupertino'
|
|
263
|
+
? this.darkenColor(this.textColor)
|
|
264
|
+
: this.textColor;
|
|
155
265
|
ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
|
|
156
266
|
ctx.textAlign = 'center';
|
|
157
267
|
ctx.textBaseline = 'middle';
|
|
@@ -162,15 +272,14 @@ class Button extends Component {
|
|
|
162
272
|
|
|
163
273
|
/**
|
|
164
274
|
* Dessine un rectangle avec coins arrondis
|
|
165
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
166
|
-
* @param {number} x - Position X
|
|
167
|
-
* @param {number} y - Position Y
|
|
168
|
-
* @param {number} width - Largeur
|
|
169
|
-
* @param {number} height - Hauteur
|
|
170
|
-
* @param {number} radius - Rayon des coins
|
|
171
275
|
* @private
|
|
172
276
|
*/
|
|
173
277
|
roundRect(ctx, x, y, width, height, radius) {
|
|
278
|
+
if (radius === 0) {
|
|
279
|
+
ctx.rect(x, y, width, height);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
174
283
|
ctx.moveTo(x + radius, y);
|
|
175
284
|
ctx.lineTo(x + width - radius, y);
|
|
176
285
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
@@ -184,19 +293,24 @@ class Button extends Component {
|
|
|
184
293
|
|
|
185
294
|
/**
|
|
186
295
|
* Assombrit une couleur
|
|
187
|
-
* @param {string} color - Couleur hexadécimale
|
|
188
|
-
* @returns {string} Couleur assombrie
|
|
189
296
|
* @private
|
|
190
297
|
*/
|
|
191
298
|
darkenColor(color) {
|
|
299
|
+
if (color === 'transparent') return 'rgba(0, 0, 0, 0.1)';
|
|
300
|
+
|
|
301
|
+
if (color.startsWith('rgba') || color.startsWith('rgb')) {
|
|
302
|
+
return color.replace(/[\d.]+\)$/g, match => {
|
|
303
|
+
const val = parseFloat(match);
|
|
304
|
+
return `${Math.max(0, val - 0.1)})`;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
192
308
|
const rgb = this.hexToRgb(color);
|
|
193
309
|
return `rgb(${Math.max(0, rgb.r - 30)}, ${Math.max(0, rgb.g - 30)}, ${Math.max(0, rgb.b - 30)})`;
|
|
194
310
|
}
|
|
195
311
|
|
|
196
312
|
/**
|
|
197
|
-
* Convertit
|
|
198
|
-
* @param {string} hex - Couleur hexadécimale
|
|
199
|
-
* @returns {{r: number, g: number, b: number}} Objet RGB
|
|
313
|
+
* Convertit hex en RGB
|
|
200
314
|
* @private
|
|
201
315
|
*/
|
|
202
316
|
hexToRgb(hex) {
|
|
@@ -208,11 +322,17 @@ class Button extends Component {
|
|
|
208
322
|
} : { r: 0, g: 0, b: 0 };
|
|
209
323
|
}
|
|
210
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Convertit hex en RGBA avec alpha
|
|
327
|
+
* @private
|
|
328
|
+
*/
|
|
329
|
+
hexToRgba(hex, alpha) {
|
|
330
|
+
const rgb = this.hexToRgb(hex);
|
|
331
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
|
|
332
|
+
}
|
|
333
|
+
|
|
211
334
|
/**
|
|
212
335
|
* Vérifie si un point est dans les limites
|
|
213
|
-
* @param {number} x - Coordonnée X
|
|
214
|
-
* @param {number} y - Coordonnée Y
|
|
215
|
-
* @returns {boolean} True si le point est dans le bouton
|
|
216
336
|
*/
|
|
217
337
|
isPointInside(x, y) {
|
|
218
338
|
return x >= this.x &&
|