canvasframework 0.5.41 → 0.5.42
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 +107 -249
- package/components/BottomNavigationBar.js +143 -378
- package/components/FileUpload.js +225 -248
- package/package.json +1 -1
package/components/AppBar.js
CHANGED
|
@@ -6,159 +6,71 @@ import Component from '../core/Component.js';
|
|
|
6
6
|
* @extends Component
|
|
7
7
|
*/
|
|
8
8
|
class AppBar extends Component {
|
|
9
|
-
/**
|
|
10
|
-
* Crée une instance de AppBar
|
|
11
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
12
|
-
* @param {Object} [options={}] - Options de configuration
|
|
13
|
-
* @param {string} [options.title=''] - Titre
|
|
14
|
-
* @param {string} [options.leftIcon] - Icône gauche ('menu' ou 'back')
|
|
15
|
-
* @param {string} [options.rightIcon] - Icône droite ('search' ou 'more')
|
|
16
|
-
* @param {Function} [options.onLeftClick] - Callback gauche
|
|
17
|
-
* @param {Function} [options.onRightClick] - Callback droit
|
|
18
|
-
* @param {number} [options.height] - Hauteur (auto selon platform)
|
|
19
|
-
* @param {string} [options.bgColor] - Couleur de fond
|
|
20
|
-
* @param {string} [options.textColor] - Couleur du texte
|
|
21
|
-
* @param {number} [options.elevation=4] - Élévation (Material)
|
|
22
|
-
*/
|
|
23
9
|
constructor(framework, options = {}) {
|
|
10
|
+
// Détection automatique Material ou Cupertino
|
|
11
|
+
const platform = options.platform || (() => {
|
|
12
|
+
if (framework.platform) return framework.platform;
|
|
13
|
+
return /iPad|iPhone|iPod/.test(navigator.userAgent) ? 'cupertino' : 'material';
|
|
14
|
+
})();
|
|
15
|
+
|
|
24
16
|
super(framework, {
|
|
25
17
|
x: 0,
|
|
26
18
|
y: 0,
|
|
27
19
|
width: framework.width,
|
|
28
|
-
height: options.height || (
|
|
20
|
+
height: options.height || (platform === 'material' ? 56 : 44),
|
|
29
21
|
...options
|
|
30
22
|
});
|
|
31
|
-
|
|
23
|
+
|
|
32
24
|
this.title = options.title || '';
|
|
33
25
|
this.leftIcon = options.leftIcon || null;
|
|
34
26
|
this.rightIcon = options.rightIcon || null;
|
|
35
27
|
this.onLeftClick = options.onLeftClick;
|
|
36
28
|
this.onRightClick = options.onRightClick;
|
|
37
|
-
this.platform =
|
|
38
|
-
|
|
39
|
-
// Couleurs
|
|
29
|
+
this.platform = platform;
|
|
30
|
+
|
|
31
|
+
// Couleurs et styles par plateforme
|
|
40
32
|
if (this.platform === 'material') {
|
|
41
33
|
this.bgColor = options.bgColor || '#6200EE';
|
|
42
34
|
this.textColor = options.textColor || '#FFFFFF';
|
|
43
35
|
this.elevation = options.elevation !== undefined ? options.elevation : 4;
|
|
44
36
|
} else {
|
|
45
|
-
//
|
|
37
|
+
// Cupertino (iOS)
|
|
46
38
|
this.bgColor = options.bgColor || 'rgba(248, 248, 248, 0.95)';
|
|
47
39
|
this.textColor = options.textColor || '#000000';
|
|
48
40
|
this.elevation = 0;
|
|
49
41
|
}
|
|
50
|
-
|
|
51
|
-
//
|
|
42
|
+
|
|
43
|
+
// Ripples (Material)
|
|
52
44
|
this.ripples = [];
|
|
53
45
|
this.animationFrame = null;
|
|
54
46
|
this.lastAnimationTime = 0;
|
|
55
|
-
|
|
56
|
-
// États
|
|
47
|
+
|
|
48
|
+
// États press (iOS)
|
|
57
49
|
this.leftPressed = false;
|
|
58
50
|
this.rightPressed = false;
|
|
59
|
-
|
|
60
|
-
this.onPress = this.handlePress.bind(this);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Démarrer l'animation des ripples
|
|
65
|
-
* @private
|
|
66
|
-
*/
|
|
67
|
-
startRippleAnimation() {
|
|
68
|
-
const animate = (timestamp) => {
|
|
69
|
-
if (!this.lastAnimationTime) this.lastAnimationTime = timestamp;
|
|
70
|
-
const deltaTime = timestamp - this.lastAnimationTime;
|
|
71
|
-
this.lastAnimationTime = timestamp;
|
|
72
|
-
|
|
73
|
-
let needsUpdate = false;
|
|
74
|
-
|
|
75
|
-
// Mettre à jour chaque ripple
|
|
76
|
-
for (let i = this.ripples.length - 1; i >= 0; i--) {
|
|
77
|
-
const ripple = this.ripples[i];
|
|
78
|
-
|
|
79
|
-
// Animer le rayon (expansion)
|
|
80
|
-
if (ripple.radius < ripple.maxRadius) {
|
|
81
|
-
ripple.radius += (ripple.maxRadius / 250) * deltaTime;
|
|
82
|
-
needsUpdate = true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Animer l'opacité (fade out) - commencer plus tôt
|
|
86
|
-
if (ripple.radius >= ripple.maxRadius * 0.4) {
|
|
87
|
-
ripple.opacity -= (0.003 * deltaTime);
|
|
88
|
-
if (ripple.opacity < 0) ripple.opacity = 0;
|
|
89
|
-
needsUpdate = true;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Supprimer les ripples terminés
|
|
93
|
-
if (ripple.opacity <= 0 && ripple.radius >= ripple.maxRadius * 0.95) {
|
|
94
|
-
this.ripples.splice(i, 1);
|
|
95
|
-
needsUpdate = true;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Redessiner si nécessaire
|
|
100
|
-
if (needsUpdate) {
|
|
101
|
-
this.requestRender();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Continuer l'animation
|
|
105
|
-
if (this.ripples.length > 0) {
|
|
106
|
-
this.animationFrame = requestAnimationFrame(animate);
|
|
107
|
-
} else {
|
|
108
|
-
this.animationFrame = null;
|
|
109
|
-
this.lastAnimationTime = 0;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
if (this.ripples.length > 0 && !this.animationFrame) {
|
|
114
|
-
this.animationFrame = requestAnimationFrame(animate);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
51
|
|
|
118
|
-
|
|
119
|
-
* Demander un redessin
|
|
120
|
-
* @private
|
|
121
|
-
*/
|
|
122
|
-
requestRender() {
|
|
123
|
-
if (this.framework && this.framework.requestRender) {
|
|
124
|
-
this.framework.requestRender();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Nettoyer l'animation lors de la destruction
|
|
130
|
-
*/
|
|
131
|
-
destroy() {
|
|
132
|
-
if (this.animationFrame) {
|
|
133
|
-
cancelAnimationFrame(this.animationFrame);
|
|
134
|
-
this.animationFrame = null;
|
|
135
|
-
}
|
|
136
|
-
super.destroy();
|
|
52
|
+
this.onPress = this.handlePress.bind(this);
|
|
137
53
|
}
|
|
138
54
|
|
|
139
|
-
/**
|
|
140
|
-
* Dessine l'AppBar
|
|
141
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
142
|
-
*/
|
|
143
55
|
draw(ctx) {
|
|
144
56
|
ctx.save();
|
|
145
|
-
|
|
146
|
-
// Ombre
|
|
57
|
+
|
|
58
|
+
// Ombre Material
|
|
147
59
|
if (this.platform === 'material' && this.elevation > 0) {
|
|
148
60
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
|
|
149
61
|
ctx.shadowBlur = this.elevation * 2;
|
|
150
62
|
ctx.shadowOffsetY = this.elevation / 2;
|
|
151
63
|
}
|
|
152
|
-
|
|
64
|
+
|
|
153
65
|
// Background
|
|
154
66
|
ctx.fillStyle = this.bgColor;
|
|
155
67
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
156
|
-
|
|
68
|
+
|
|
157
69
|
ctx.shadowColor = 'transparent';
|
|
158
70
|
ctx.shadowBlur = 0;
|
|
159
71
|
ctx.shadowOffsetY = 0;
|
|
160
|
-
|
|
161
|
-
// Bordure
|
|
72
|
+
|
|
73
|
+
// Bordure iOS
|
|
162
74
|
if (this.platform === 'cupertino') {
|
|
163
75
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
|
164
76
|
ctx.lineWidth = 0.5;
|
|
@@ -167,28 +79,26 @@ class AppBar extends Component {
|
|
|
167
79
|
ctx.lineTo(this.x + this.width, this.y + this.height - 0.5);
|
|
168
80
|
ctx.stroke();
|
|
169
81
|
}
|
|
170
|
-
|
|
171
|
-
// Ripples pour
|
|
172
|
-
if (this.platform === 'material')
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// Overlay pressed pour iOS
|
|
82
|
+
|
|
83
|
+
// Ripples pour Material
|
|
84
|
+
if (this.platform === 'material') this.drawRipples(ctx);
|
|
85
|
+
|
|
86
|
+
// Overlay pressed iOS
|
|
177
87
|
if (this.platform === 'cupertino') {
|
|
178
88
|
if (this.leftPressed && this.leftIcon) {
|
|
179
|
-
ctx.fillStyle = 'rgba(0,
|
|
89
|
+
ctx.fillStyle = 'rgba(0,0,0,0.1)';
|
|
180
90
|
ctx.beginPath();
|
|
181
91
|
ctx.arc(this.x + 28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
182
92
|
ctx.fill();
|
|
183
93
|
}
|
|
184
94
|
if (this.rightPressed && this.rightIcon) {
|
|
185
|
-
ctx.fillStyle = 'rgba(0,
|
|
95
|
+
ctx.fillStyle = 'rgba(0,0,0,0.1)';
|
|
186
96
|
ctx.beginPath();
|
|
187
97
|
ctx.arc(this.x + this.width - 28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
188
98
|
ctx.fill();
|
|
189
99
|
}
|
|
190
100
|
}
|
|
191
|
-
|
|
101
|
+
|
|
192
102
|
// Titre
|
|
193
103
|
ctx.fillStyle = this.textColor;
|
|
194
104
|
const titleAlign = this.platform === 'material' && this.leftIcon ? 'left' : 'center';
|
|
@@ -197,34 +107,14 @@ class AppBar extends Component {
|
|
|
197
107
|
ctx.textAlign = titleAlign;
|
|
198
108
|
ctx.textBaseline = 'middle';
|
|
199
109
|
ctx.fillText(this.title, titleX, this.y + this.height / 2);
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
if (this.leftIcon)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.drawMenuIcon(ctx, this.x + 16, this.y + this.height / 2, iconColor);
|
|
206
|
-
} else if (this.leftIcon === 'back') {
|
|
207
|
-
this.drawBackIcon(ctx, this.x + 16, this.y + this.height / 2, iconColor);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Icône droite
|
|
212
|
-
if (this.rightIcon) {
|
|
213
|
-
const iconColor = this.platform === 'cupertino' ? '#007AFF' : this.textColor;
|
|
214
|
-
if (this.rightIcon === 'search') {
|
|
215
|
-
this.drawSearchIcon(ctx, this.x + this.width - 36, this.y + this.height / 2, iconColor);
|
|
216
|
-
} else if (this.rightIcon === 'more') {
|
|
217
|
-
this.drawMoreIcon(ctx, this.x + this.width - 36, this.y + this.height / 2, iconColor);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
110
|
+
|
|
111
|
+
// Icônes
|
|
112
|
+
if (this.leftIcon) this.drawLeftIcon(ctx);
|
|
113
|
+
if (this.rightIcon) this.drawRightIcon(ctx);
|
|
114
|
+
|
|
221
115
|
ctx.restore();
|
|
222
116
|
}
|
|
223
117
|
|
|
224
|
-
/**
|
|
225
|
-
* Dessine les ripples
|
|
226
|
-
* @private
|
|
227
|
-
*/
|
|
228
118
|
drawRipples(ctx) {
|
|
229
119
|
for (let ripple of this.ripples) {
|
|
230
120
|
ctx.save();
|
|
@@ -237,10 +127,18 @@ class AppBar extends Component {
|
|
|
237
127
|
}
|
|
238
128
|
}
|
|
239
129
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
130
|
+
drawLeftIcon(ctx) {
|
|
131
|
+
const color = this.platform === 'cupertino' ? this.textColor : this.textColor;
|
|
132
|
+
if (this.leftIcon === 'menu') this.drawMenuIcon(ctx, this.x + 16, this.y + this.height / 2, color);
|
|
133
|
+
if (this.leftIcon === 'back') this.drawBackIcon(ctx, this.x + 16, this.y + this.height / 2, color);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
drawRightIcon(ctx) {
|
|
137
|
+
const color = this.platform === 'cupertino' ? '#007AFF' : this.textColor;
|
|
138
|
+
if (this.rightIcon === 'search') this.drawSearchIcon(ctx, this.x + this.width - 36, this.y + this.height / 2, color);
|
|
139
|
+
if (this.rightIcon === 'more') this.drawMoreIcon(ctx, this.x + this.width - 36, this.y + this.height / 2, color);
|
|
140
|
+
}
|
|
141
|
+
|
|
244
142
|
drawMenuIcon(ctx, x, y, color) {
|
|
245
143
|
ctx.strokeStyle = color;
|
|
246
144
|
ctx.lineWidth = 2;
|
|
@@ -253,10 +151,6 @@ class AppBar extends Component {
|
|
|
253
151
|
}
|
|
254
152
|
}
|
|
255
153
|
|
|
256
|
-
/**
|
|
257
|
-
* Dessine l'icône retour
|
|
258
|
-
* @private
|
|
259
|
-
*/
|
|
260
154
|
drawBackIcon(ctx, x, y, color) {
|
|
261
155
|
ctx.strokeStyle = color;
|
|
262
156
|
ctx.lineWidth = 2;
|
|
@@ -269,10 +163,6 @@ class AppBar extends Component {
|
|
|
269
163
|
ctx.stroke();
|
|
270
164
|
}
|
|
271
165
|
|
|
272
|
-
/**
|
|
273
|
-
* Dessine l'icône recherche
|
|
274
|
-
* @private
|
|
275
|
-
*/
|
|
276
166
|
drawSearchIcon(ctx, x, y, color) {
|
|
277
167
|
ctx.strokeStyle = color;
|
|
278
168
|
ctx.lineWidth = 2;
|
|
@@ -286,10 +176,6 @@ class AppBar extends Component {
|
|
|
286
176
|
ctx.stroke();
|
|
287
177
|
}
|
|
288
178
|
|
|
289
|
-
/**
|
|
290
|
-
* Dessine l'icône plus (3 dots)
|
|
291
|
-
* @private
|
|
292
|
-
*/
|
|
293
179
|
drawMoreIcon(ctx, x, y, color) {
|
|
294
180
|
ctx.fillStyle = color;
|
|
295
181
|
const spacing = this.platform === 'material' ? 10 : 8;
|
|
@@ -300,99 +186,71 @@ class AppBar extends Component {
|
|
|
300
186
|
}
|
|
301
187
|
}
|
|
302
188
|
|
|
303
|
-
|
|
304
|
-
* Vérifie si un point est dans les zones cliquables
|
|
305
|
-
*/
|
|
306
|
-
isPointInside(x, y) {
|
|
307
|
-
// Les coordonnées x, y sont absolues, on les compare avec nos coordonnées absolues
|
|
189
|
+
handlePress(x, y) {
|
|
308
190
|
const inY = y >= this.y && y <= this.y + this.height;
|
|
309
|
-
|
|
191
|
+
|
|
310
192
|
if (!inY) return false;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (this.
|
|
314
|
-
|
|
193
|
+
|
|
194
|
+
// Bouton gauche
|
|
195
|
+
if (this.leftIcon && x >= this.x && x <= this.x + 56) {
|
|
196
|
+
if (this.platform === 'material') this.addRipple(this.x + 28, this.y + this.height / 2);
|
|
197
|
+
else this.leftPressed = true, setTimeout(() => this.leftPressed = false, 150);
|
|
198
|
+
if (this.onLeftClick) this.onLeftClick();
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Bouton droit
|
|
203
|
+
if (this.rightIcon && x >= this.x + this.width - 56 && x <= this.x + this.width) {
|
|
204
|
+
if (this.platform === 'material') this.addRipple(this.x + this.width - 28, this.y + this.height / 2);
|
|
205
|
+
else this.rightPressed = true, setTimeout(() => this.rightPressed = false, 150);
|
|
206
|
+
if (this.onRightClick) this.onRightClick();
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
315
210
|
return false;
|
|
316
211
|
}
|
|
317
212
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
this.startRippleAnimation();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Forcer un redessin
|
|
345
|
-
this.requestRender();
|
|
346
|
-
} else {
|
|
347
|
-
// iOS pressed state
|
|
348
|
-
this.leftPressed = true;
|
|
349
|
-
setTimeout(() => {
|
|
350
|
-
this.leftPressed = false;
|
|
351
|
-
this.requestRender();
|
|
352
|
-
}, 150);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (this.onLeftClick) this.onLeftClick();
|
|
356
|
-
this.requestRender();
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Bouton droit
|
|
361
|
-
if (this.rightIcon && x >= this.x + this.width - 56 && x <= this.x + this.width) {
|
|
362
|
-
// Ripple effect (Material)
|
|
363
|
-
if (this.platform === 'material') {
|
|
364
|
-
this.ripples.push({
|
|
365
|
-
x: this.x + this.width - 28,
|
|
366
|
-
y: this.y + this.height / 2,
|
|
367
|
-
radius: 0,
|
|
368
|
-
maxRadius: 28,
|
|
369
|
-
opacity: 1,
|
|
370
|
-
createdAt: performance.now()
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// Démarrer l'animation si elle n'est pas en cours
|
|
374
|
-
if (!this.animationFrame) {
|
|
375
|
-
this.startRippleAnimation();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Forcer un redessin
|
|
379
|
-
this.requestRender();
|
|
380
|
-
} else {
|
|
381
|
-
// iOS pressed state
|
|
382
|
-
this.rightPressed = true;
|
|
383
|
-
setTimeout(() => {
|
|
384
|
-
this.rightPressed = false;
|
|
385
|
-
this.requestRender();
|
|
386
|
-
}, 150);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (this.onRightClick) this.onRightClick();
|
|
390
|
-
this.requestRender();
|
|
391
|
-
return true;
|
|
213
|
+
addRipple(x, y) {
|
|
214
|
+
this.ripples.push({
|
|
215
|
+
x, y,
|
|
216
|
+
radius: 0,
|
|
217
|
+
maxRadius: 28,
|
|
218
|
+
opacity: 1,
|
|
219
|
+
createdAt: performance.now()
|
|
220
|
+
});
|
|
221
|
+
if (!this.animationFrame) this.startRippleAnimation();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
startRippleAnimation() {
|
|
225
|
+
const animate = (timestamp) => {
|
|
226
|
+
if (!this.lastAnimationTime) this.lastAnimationTime = timestamp;
|
|
227
|
+
const deltaTime = timestamp - this.lastAnimationTime;
|
|
228
|
+
this.lastAnimationTime = timestamp;
|
|
229
|
+
let needsUpdate = false;
|
|
230
|
+
|
|
231
|
+
for (let i = this.ripples.length - 1; i >= 0; i--) {
|
|
232
|
+
const ripple = this.ripples[i];
|
|
233
|
+
if (ripple.radius < ripple.maxRadius) ripple.radius += (ripple.maxRadius / 250) * deltaTime, needsUpdate = true;
|
|
234
|
+
if (ripple.radius >= ripple.maxRadius * 0.4) ripple.opacity -= (0.003 * deltaTime), ripple.opacity < 0 && (ripple.opacity = 0), needsUpdate = true;
|
|
235
|
+
if (ripple.opacity <= 0 && ripple.radius >= ripple.maxRadius * 0.95) this.ripples.splice(i, 1), needsUpdate = true;
|
|
392
236
|
}
|
|
393
|
-
|
|
394
|
-
|
|
237
|
+
|
|
238
|
+
if (needsUpdate) this.requestRender();
|
|
239
|
+
if (this.ripples.length > 0) this.animationFrame = requestAnimationFrame(animate);
|
|
240
|
+
else this.animationFrame = null, this.lastAnimationTime = 0;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (this.ripples.length > 0 && !this.animationFrame) this.animationFrame = requestAnimationFrame(animate);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
requestRender() {
|
|
247
|
+
if (this.framework && this.framework.requestRender) this.framework.requestRender();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
destroy() {
|
|
251
|
+
if (this.animationFrame) cancelAnimationFrame(this.animationFrame), this.animationFrame = null;
|
|
252
|
+
super.destroy();
|
|
395
253
|
}
|
|
396
254
|
}
|
|
397
255
|
|
|
398
|
-
export default AppBar;
|
|
256
|
+
export default AppBar;
|