canvasframework 0.5.64 → 0.6.0
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/BottomNavigationBar.js +378 -143
- package/components/PDFViewer.js +1000 -1161
- package/core/CanvasFramework.js +1 -0
- package/package.json +1 -1
|
@@ -1,198 +1,433 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Barre de navigation inférieure (Material & Cupertino)
|
|
5
5
|
* @class
|
|
6
6
|
* @extends Component
|
|
7
7
|
*/
|
|
8
|
-
class
|
|
8
|
+
class BottomNavigationBar extends Component {
|
|
9
|
+
/**
|
|
10
|
+
* Crée une instance de BottomNavigationBar
|
|
11
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
12
|
+
* @param {Object} [options={}] - Options de configuration
|
|
13
|
+
* @param {Array} [options.items=[]] - Items [{icon, label}]
|
|
14
|
+
* @param {number} [options.selectedIndex=0] - Index sélectionné
|
|
15
|
+
* @param {Function} [options.onChange] - Callback au changement
|
|
16
|
+
* @param {number} [options.height] - Hauteur
|
|
17
|
+
* @param {string} [options.bgColor] - Couleur de fond
|
|
18
|
+
* @param {string} [options.selectedColor] - Couleur sélectionnée
|
|
19
|
+
* @param {string} [options.unselectedColor] - Couleur non sélectionnée
|
|
20
|
+
*/
|
|
9
21
|
constructor(framework, options = {}) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
const height = options.height || (framework.platform === 'material' ? 56 : 50);
|
|
23
|
+
|
|
24
|
+
super(framework, {
|
|
25
|
+
x: 0,
|
|
26
|
+
y: framework.height - height,
|
|
27
|
+
width: framework.width,
|
|
28
|
+
height: height,
|
|
29
|
+
...options
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.items = options.items || [];
|
|
33
|
+
this.selectedIndex = options.selectedIndex || 0;
|
|
34
|
+
this.onChange = options.onChange;
|
|
18
35
|
this.platform = framework.platform;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Couleurs selon plateforme
|
|
36
|
+
|
|
37
|
+
// Couleurs selon la plateforme
|
|
23
38
|
if (this.platform === 'material') {
|
|
24
|
-
this.bgColor = options.bgColor || '#
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
this.ripples = [];
|
|
29
|
-
this.animationFrame = null;
|
|
39
|
+
this.bgColor = options.bgColor || '#FFFFFF';
|
|
40
|
+
this.selectedColor = options.selectedColor || '#6200EE';
|
|
41
|
+
this.unselectedColor = options.unselectedColor || '#757575';
|
|
42
|
+
this.rippleColor = 'rgba(98, 0, 238, 0.2)';
|
|
30
43
|
} else {
|
|
31
|
-
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
44
|
+
// iOS : background transparent avec blur
|
|
45
|
+
this.bgColor = options.bgColor || 'rgba(248, 248, 248, 0.95)';
|
|
46
|
+
this.selectedColor = options.selectedColor || '#007AFF';
|
|
47
|
+
this.unselectedColor = options.unselectedColor || '#8E8E93';
|
|
35
48
|
}
|
|
49
|
+
|
|
50
|
+
// Ripple effect (Material)
|
|
51
|
+
this.ripples = [];
|
|
52
|
+
this.animationFrame = null;
|
|
53
|
+
this.lastAnimationTime = 0;
|
|
54
|
+
|
|
55
|
+
// Animation de l'indicateur (iOS)
|
|
56
|
+
this.indicatorX = 0;
|
|
57
|
+
this.targetIndicatorX = 0;
|
|
58
|
+
this.animatingIndicator = false;
|
|
59
|
+
|
|
60
|
+
this.onPress = this.handlePress.bind(this);
|
|
61
|
+
|
|
62
|
+
// Initialiser la position de l'indicateur
|
|
63
|
+
this.updateIndicatorPosition();
|
|
64
|
+
}
|
|
36
65
|
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Démarrer l'animation des ripples
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
startRippleAnimation() {
|
|
71
|
+
const animate = (timestamp) => {
|
|
72
|
+
if (!this.lastAnimationTime) this.lastAnimationTime = timestamp;
|
|
73
|
+
const deltaTime = timestamp - this.lastAnimationTime;
|
|
74
|
+
this.lastAnimationTime = timestamp;
|
|
39
75
|
|
|
40
|
-
|
|
41
|
-
this.createFileInput();
|
|
42
|
-
}
|
|
76
|
+
let needsUpdate = false;
|
|
43
77
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
78
|
+
// Mettre à jour chaque ripple
|
|
79
|
+
for (let i = this.ripples.length - 1; i >= 0; i--) {
|
|
80
|
+
const ripple = this.ripples[i];
|
|
81
|
+
|
|
82
|
+
// Animer le rayon (expansion)
|
|
83
|
+
if (ripple.radius < ripple.maxRadius) {
|
|
84
|
+
ripple.radius += (ripple.maxRadius / 300) * deltaTime;
|
|
85
|
+
needsUpdate = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Animer l'opacité (fade out)
|
|
89
|
+
if (ripple.radius >= ripple.maxRadius * 0.4) {
|
|
90
|
+
ripple.opacity -= (0.003 * deltaTime);
|
|
91
|
+
if (ripple.opacity < 0) ripple.opacity = 0;
|
|
92
|
+
needsUpdate = true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Supprimer les ripples terminés
|
|
96
|
+
if (ripple.opacity <= 0 && ripple.radius >= ripple.maxRadius * 0.95) {
|
|
97
|
+
this.ripples.splice(i, 1);
|
|
98
|
+
needsUpdate = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
51
101
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
102
|
+
// Redessiner si nécessaire
|
|
103
|
+
if (needsUpdate) {
|
|
104
|
+
this.requestRender();
|
|
105
|
+
}
|
|
56
106
|
|
|
57
|
-
|
|
58
|
-
|
|
107
|
+
// Continuer l'animation
|
|
108
|
+
if (this.ripples.length > 0) {
|
|
109
|
+
this.animationFrame = requestAnimationFrame(animate);
|
|
110
|
+
} else {
|
|
111
|
+
this.animationFrame = null;
|
|
112
|
+
this.lastAnimationTime = 0;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
59
115
|
|
|
60
|
-
|
|
61
|
-
|
|
116
|
+
if (this.ripples.length > 0 && !this.animationFrame) {
|
|
117
|
+
this.animationFrame = requestAnimationFrame(animate);
|
|
62
118
|
}
|
|
119
|
+
}
|
|
63
120
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Demander un redessin
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
requestRender() {
|
|
126
|
+
if (this.framework && this.framework.requestRender) {
|
|
127
|
+
this.framework.requestRender();
|
|
67
128
|
}
|
|
68
|
-
|
|
69
|
-
this.fileInput.value = '';
|
|
70
|
-
this.requestRender();
|
|
71
129
|
}
|
|
72
130
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Nettoyer l'animation lors de la destruction
|
|
133
|
+
*/
|
|
134
|
+
destroy() {
|
|
135
|
+
if (this.animationFrame) {
|
|
136
|
+
cancelAnimationFrame(this.animationFrame);
|
|
137
|
+
this.animationFrame = null;
|
|
138
|
+
}
|
|
139
|
+
super.destroy();
|
|
140
|
+
}
|
|
76
141
|
|
|
77
|
-
|
|
78
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Met à jour la position de l'indicateur iOS
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
updateIndicatorPosition() {
|
|
147
|
+
const itemWidth = this.width / this.items.length;
|
|
148
|
+
this.targetIndicatorX = this.selectedIndex * itemWidth;
|
|
149
|
+
|
|
150
|
+
if (!this.animatingIndicator) {
|
|
151
|
+
this.indicatorX = this.targetIndicatorX;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
79
154
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Anime l'indicateur iOS
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
animateIndicator() {
|
|
160
|
+
this.animatingIndicator = true;
|
|
161
|
+
const startTime = performance.now();
|
|
162
|
+
const duration = 300; // 300ms d'animation
|
|
163
|
+
const startX = this.indicatorX;
|
|
164
|
+
const endX = this.targetIndicatorX;
|
|
165
|
+
|
|
166
|
+
const animate = (currentTime) => {
|
|
167
|
+
const elapsed = currentTime - startTime;
|
|
168
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
169
|
+
|
|
170
|
+
// Easing function (easeOutCubic)
|
|
171
|
+
const easeProgress = 1 - Math.pow(1 - progress, 3);
|
|
172
|
+
this.indicatorX = startX + (endX - startX) * easeProgress;
|
|
173
|
+
|
|
174
|
+
if (progress < 1) {
|
|
175
|
+
requestAnimationFrame(animate);
|
|
176
|
+
this.requestRender();
|
|
177
|
+
} else {
|
|
178
|
+
this.indicatorX = endX;
|
|
179
|
+
this.animatingIndicator = false;
|
|
180
|
+
this.requestRender();
|
|
88
181
|
}
|
|
89
|
-
|
|
90
|
-
this.requestRender();
|
|
91
|
-
|
|
92
|
-
if (needsUpdate) requestAnimationFrame(animate);
|
|
93
182
|
};
|
|
94
183
|
|
|
95
184
|
requestAnimationFrame(animate);
|
|
96
185
|
}
|
|
97
186
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (this.isPointInside(globalX, globalY)) {
|
|
103
|
-
if (this.platform === 'material') this.startRipple(localX, localY);
|
|
104
|
-
this.fileInput.click();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Dessine la barre de navigation
|
|
189
|
+
*/
|
|
108
190
|
draw(ctx) {
|
|
109
191
|
ctx.save();
|
|
110
|
-
|
|
192
|
+
|
|
111
193
|
// Background
|
|
112
194
|
ctx.fillStyle = this.bgColor;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Border
|
|
117
|
-
ctx.strokeStyle = this.borderColor;
|
|
118
|
-
ctx.lineWidth = 2;
|
|
119
|
-
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
120
|
-
ctx.stroke();
|
|
121
|
-
|
|
122
|
-
// Ripple (Material)
|
|
195
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
196
|
+
|
|
197
|
+
// Bordure/Ombre supérieure
|
|
123
198
|
if (this.platform === 'material') {
|
|
124
|
-
ctx.
|
|
199
|
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.1)';
|
|
200
|
+
ctx.shadowBlur = 8;
|
|
201
|
+
ctx.shadowOffsetY = -2;
|
|
202
|
+
ctx.fillRect(this.x, this.y, this.width, 1);
|
|
203
|
+
ctx.shadowColor = 'transparent';
|
|
204
|
+
} else {
|
|
205
|
+
// iOS : fine ligne de séparation
|
|
206
|
+
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
|
207
|
+
ctx.lineWidth = 0.5;
|
|
125
208
|
ctx.beginPath();
|
|
126
|
-
ctx.
|
|
127
|
-
ctx.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
209
|
+
ctx.moveTo(this.x, this.y);
|
|
210
|
+
ctx.lineTo(this.x + this.width, this.y);
|
|
211
|
+
ctx.stroke();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Items
|
|
215
|
+
const itemWidth = this.width / this.items.length;
|
|
216
|
+
|
|
217
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
218
|
+
const item = this.items[i];
|
|
219
|
+
const itemX = this.x + i * itemWidth;
|
|
220
|
+
const isSelected = i === this.selectedIndex;
|
|
221
|
+
const color = isSelected ? this.selectedColor : this.unselectedColor;
|
|
222
|
+
|
|
223
|
+
// iOS : Indicateur de sélection (fond arrondi)
|
|
224
|
+
if (this.platform === 'cupertino' && isSelected) {
|
|
225
|
+
ctx.fillStyle = `${this.selectedColor}15`;
|
|
226
|
+
const indicatorWidth = 60;
|
|
227
|
+
const indicatorHeight = 32;
|
|
228
|
+
const indicatorX = itemX + itemWidth / 2 - indicatorWidth / 2;
|
|
229
|
+
const indicatorY = this.y + 6;
|
|
230
|
+
|
|
131
231
|
ctx.beginPath();
|
|
132
|
-
|
|
232
|
+
this.roundRect(ctx, indicatorX, indicatorY, indicatorWidth, indicatorHeight, 16);
|
|
133
233
|
ctx.fill();
|
|
134
234
|
}
|
|
135
|
-
|
|
235
|
+
|
|
236
|
+
// Icône
|
|
237
|
+
const iconY = this.platform === 'material' ? this.y + 12 : this.y + 8;
|
|
238
|
+
this.drawIcon(ctx, item.icon, itemX + itemWidth / 2, iconY, color, isSelected);
|
|
239
|
+
|
|
240
|
+
// Label
|
|
241
|
+
ctx.fillStyle = color;
|
|
242
|
+
const fontSize = this.platform === 'material' ? 12 : 10;
|
|
243
|
+
ctx.font = `${isSelected && this.platform === 'material' ? 'bold ' : ''}${fontSize}px -apple-system, Roboto, sans-serif`;
|
|
244
|
+
ctx.textAlign = 'center';
|
|
245
|
+
ctx.textBaseline = 'top';
|
|
246
|
+
const labelY = this.platform === 'material' ? this.y + 34 : this.y + 30;
|
|
247
|
+
ctx.fillText(item.label, itemX + itemWidth / 2, labelY);
|
|
136
248
|
}
|
|
249
|
+
|
|
250
|
+
// Ripples (Material) - DESSINER APRÈS LES ÉLÉMENTS
|
|
251
|
+
if (this.platform === 'material') {
|
|
252
|
+
this.drawRipples(ctx);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
ctx.restore();
|
|
256
|
+
}
|
|
137
257
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
ctx.
|
|
258
|
+
/**
|
|
259
|
+
* Dessine les ripples (Material)
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
drawRipples(ctx) {
|
|
263
|
+
// Sauvegarder le contexte
|
|
264
|
+
ctx.save();
|
|
265
|
+
|
|
266
|
+
// Créer un masque de clipping pour limiter les ripples à la barre
|
|
145
267
|
ctx.beginPath();
|
|
146
|
-
ctx.
|
|
147
|
-
ctx.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
ctx.textAlign = 'center';
|
|
156
|
-
ctx.textBaseline = 'top';
|
|
157
|
-
ctx.fillText(this.label, this.x + this.width / 2, iconY + iconSize + 10);
|
|
158
|
-
|
|
159
|
-
// Fichiers sélectionnés
|
|
160
|
-
if (this.files.length > 0) {
|
|
161
|
-
ctx.fillStyle = this.borderColor;
|
|
162
|
-
ctx.font = '12px -apple-system, Roboto, sans-serif';
|
|
163
|
-
const fileText = this.files.length === 1 ? this.files[0].name : `${this.files.length} files selected`;
|
|
164
|
-
ctx.fillText(fileText, this.x + this.width / 2, this.y + this.height - 20);
|
|
268
|
+
ctx.rect(this.x, this.y, this.width, this.height);
|
|
269
|
+
ctx.clip();
|
|
270
|
+
|
|
271
|
+
for (let ripple of this.ripples) {
|
|
272
|
+
ctx.globalAlpha = ripple.opacity;
|
|
273
|
+
ctx.fillStyle = this.rippleColor;
|
|
274
|
+
ctx.beginPath();
|
|
275
|
+
ctx.arc(ripple.x, ripple.y, ripple.radius, 0, Math.PI * 2);
|
|
276
|
+
ctx.fill();
|
|
165
277
|
}
|
|
166
|
-
|
|
278
|
+
|
|
279
|
+
// Restaurer le contexte
|
|
167
280
|
ctx.restore();
|
|
168
281
|
}
|
|
169
282
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
ctx.
|
|
176
|
-
ctx.
|
|
177
|
-
ctx.
|
|
178
|
-
ctx.
|
|
179
|
-
ctx.
|
|
283
|
+
/**
|
|
284
|
+
* Dessine une icône
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
drawIcon(ctx, icon, x, y, color, isSelected) {
|
|
288
|
+
ctx.strokeStyle = color;
|
|
289
|
+
ctx.fillStyle = color;
|
|
290
|
+
ctx.lineWidth = isSelected ? 2.5 : 2;
|
|
291
|
+
ctx.lineCap = 'round';
|
|
292
|
+
ctx.lineJoin = 'round';
|
|
293
|
+
|
|
294
|
+
switch(icon) {
|
|
295
|
+
case 'home':
|
|
296
|
+
ctx.beginPath();
|
|
297
|
+
ctx.moveTo(x, y + 2);
|
|
298
|
+
ctx.lineTo(x - 10, y + 10);
|
|
299
|
+
ctx.lineTo(x - 10, y + 18);
|
|
300
|
+
ctx.lineTo(x + 10, y + 18);
|
|
301
|
+
ctx.lineTo(x + 10, y + 10);
|
|
302
|
+
ctx.closePath();
|
|
303
|
+
if (isSelected) ctx.fill();
|
|
304
|
+
else ctx.stroke();
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
case 'search':
|
|
308
|
+
ctx.beginPath();
|
|
309
|
+
ctx.arc(x - 2, y + 6, 7, 0, Math.PI * 2);
|
|
310
|
+
ctx.stroke();
|
|
311
|
+
ctx.beginPath();
|
|
312
|
+
ctx.moveTo(x + 4, y + 11);
|
|
313
|
+
ctx.lineTo(x + 9, y + 16);
|
|
314
|
+
ctx.stroke();
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case 'favorite':
|
|
318
|
+
ctx.beginPath();
|
|
319
|
+
ctx.moveTo(x, y + 3);
|
|
320
|
+
for (let i = 0; i < 5; i++) {
|
|
321
|
+
const angle = (i * 4 * Math.PI / 5) - Math.PI / 2;
|
|
322
|
+
const radius = i % 2 === 0 ? 9 : 4;
|
|
323
|
+
ctx.lineTo(x + Math.cos(angle) * radius, y + 10 + Math.sin(angle) * radius);
|
|
324
|
+
}
|
|
325
|
+
ctx.closePath();
|
|
326
|
+
if (isSelected) ctx.fill();
|
|
327
|
+
else ctx.stroke();
|
|
328
|
+
break;
|
|
329
|
+
|
|
330
|
+
case 'person':
|
|
331
|
+
ctx.beginPath();
|
|
332
|
+
ctx.arc(x, y + 6, 5, 0, Math.PI * 2);
|
|
333
|
+
ctx.stroke();
|
|
334
|
+
ctx.beginPath();
|
|
335
|
+
ctx.arc(x, y + 20, 9, Math.PI, 0, true);
|
|
336
|
+
ctx.stroke();
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case 'settings':
|
|
340
|
+
ctx.beginPath();
|
|
341
|
+
ctx.arc(x, y + 10, 5, 0, Math.PI * 2);
|
|
342
|
+
ctx.stroke();
|
|
343
|
+
for (let i = 0; i < 4; i++) {
|
|
344
|
+
const angle = (i * Math.PI / 2) - Math.PI / 4;
|
|
345
|
+
ctx.beginPath();
|
|
346
|
+
ctx.moveTo(x + Math.cos(angle) * 7, y + 10 + Math.sin(angle) * 7);
|
|
347
|
+
ctx.lineTo(x + Math.cos(angle) * 11, y + 10 + Math.sin(angle) * 11);
|
|
348
|
+
ctx.stroke();
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Dessine un rectangle arrondi
|
|
356
|
+
* @private
|
|
357
|
+
*/
|
|
358
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
359
|
+
ctx.moveTo(x + radius, y);
|
|
360
|
+
ctx.lineTo(x + width - radius, y);
|
|
361
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
362
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
363
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
364
|
+
ctx.lineTo(x + radius, y + height);
|
|
365
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
366
|
+
ctx.lineTo(x, y + radius);
|
|
367
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
180
368
|
}
|
|
181
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Vérifie si un point est dans les limites
|
|
372
|
+
*/
|
|
182
373
|
isPointInside(x, y) {
|
|
183
|
-
return
|
|
184
|
-
y >= this.y && y <= this.y + this.height;
|
|
374
|
+
return y >= this.y && y <= this.y + this.height;
|
|
185
375
|
}
|
|
186
376
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Gère la pression (clic)
|
|
379
|
+
* @private
|
|
380
|
+
*/
|
|
381
|
+
handlePress(x, y) {
|
|
382
|
+
// Convertir les coordonnées absolues en coordonnées relatives à la barre
|
|
383
|
+
const relativeX = x - this.x;
|
|
384
|
+
const relativeY = y - this.y;
|
|
385
|
+
|
|
386
|
+
// Vérifier si on est dans la barre
|
|
387
|
+
if (relativeY >= 0 && relativeY <= this.height) {
|
|
388
|
+
const itemWidth = this.width / this.items.length;
|
|
389
|
+
const index = Math.floor(relativeX / itemWidth);
|
|
390
|
+
|
|
391
|
+
if (index >= 0 && index < this.items.length && index !== this.selectedIndex) {
|
|
392
|
+
// Ripple effect (Material)
|
|
393
|
+
if (this.platform === 'material') {
|
|
394
|
+
// Calculer la taille maximale du ripple (ne pas dépasser la hauteur de la barre)
|
|
395
|
+
const maxRippleRadius = Math.min(itemWidth * 0.6, this.height * 0.8);
|
|
396
|
+
|
|
397
|
+
this.ripples.push({
|
|
398
|
+
x: this.x + (index + 0.5) * itemWidth, // Coordonnée absolue
|
|
399
|
+
y: this.y + this.height / 2, // Coordonnée absolue
|
|
400
|
+
radius: 0,
|
|
401
|
+
maxRadius: maxRippleRadius,
|
|
402
|
+
opacity: 1,
|
|
403
|
+
createdAt: performance.now()
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Démarrer l'animation si elle n'est pas en cours
|
|
407
|
+
if (!this.animationFrame) {
|
|
408
|
+
this.startRippleAnimation();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Forcer un redessin
|
|
412
|
+
this.requestRender();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this.selectedIndex = index;
|
|
416
|
+
this.updateIndicatorPosition();
|
|
417
|
+
|
|
418
|
+
// Animer l'indicateur (iOS)
|
|
419
|
+
if (this.platform === 'cupertino') {
|
|
420
|
+
this.animateIndicator();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (this.onChange) {
|
|
424
|
+
this.onChange(index, this.items[index]);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.requestRender();
|
|
428
|
+
}
|
|
190
429
|
}
|
|
191
430
|
}
|
|
192
|
-
|
|
193
|
-
requestRender() {
|
|
194
|
-
if (this.framework && this.framework.requestRender) this.framework.requestRender();
|
|
195
|
-
}
|
|
196
431
|
}
|
|
197
432
|
|
|
198
|
-
export default
|
|
433
|
+
export default BottomNavigationBar;
|