canvasframework 0.5.16 → 0.5.18
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/dist/canvasframework.js +2 -0
- package/dist/canvasframework.js.LICENSE.txt +1 -0
- package/package.json +18 -17
- package/components/Accordion.js +0 -265
- package/components/AndroidDatePickerDialog.js +0 -406
- package/components/AppBar.js +0 -398
- package/components/AudioPlayer.js +0 -611
- package/components/Avatar.js +0 -202
- package/components/Banner.js +0 -342
- package/components/BottomNavigationBar.js +0 -433
- package/components/BottomSheet.js +0 -234
- package/components/Button.js +0 -360
- package/components/Camera.js +0 -644
- package/components/Card.js +0 -193
- package/components/Chart.js +0 -700
- package/components/Checkbox.js +0 -166
- package/components/Chip.js +0 -212
- package/components/CircularProgress.js +0 -327
- package/components/ContextMenu.js +0 -116
- package/components/DatePicker.js +0 -298
- package/components/Dialog.js +0 -337
- package/components/Divider.js +0 -125
- package/components/Drawer.js +0 -276
- package/components/FAB.js +0 -270
- package/components/FileUpload.js +0 -315
- package/components/FloatedCamera.js +0 -644
- package/components/IOSDatePickerWheel.js +0 -430
- package/components/ImageCarousel.js +0 -219
- package/components/ImageComponent.js +0 -223
- package/components/Input.js +0 -831
- package/components/InputDatalist.js +0 -723
- package/components/InputTags.js +0 -624
- package/components/List.js +0 -95
- package/components/ListItem.js +0 -269
- package/components/Modal.js +0 -364
- package/components/MorphingFAB.js +0 -428
- package/components/MultiSelectDialog.js +0 -206
- package/components/NumberInput.js +0 -271
- package/components/PasswordInput.js +0 -462
- package/components/ProgressBar.js +0 -88
- package/components/QRCodeReader.js +0 -539
- package/components/RadioButton.js +0 -151
- package/components/SearchInput.js +0 -315
- package/components/SegmentedControl.js +0 -357
- package/components/Select.js +0 -199
- package/components/SelectDialog.js +0 -255
- package/components/Slider.js +0 -113
- package/components/SliverAppBar.js +0 -139
- package/components/Snackbar.js +0 -243
- package/components/SpeedDialFAB.js +0 -397
- package/components/Stepper.js +0 -281
- package/components/SwipeableListItem.js +0 -327
- package/components/Switch.js +0 -147
- package/components/Table.js +0 -492
- package/components/Tabs.js +0 -423
- package/components/Text.js +0 -141
- package/components/TextField.js +0 -151
- package/components/TimePicker.js +0 -934
- package/components/Toast.js +0 -236
- package/components/TreeView.js +0 -420
- package/components/Video.js +0 -397
- package/components/View.js +0 -140
- package/components/VirtualList.js +0 -120
- package/core/CanvasFramework.js +0 -3034
- package/core/Component.js +0 -243
- package/core/ThemeManager.js +0 -358
- package/core/UIBuilder.js +0 -267
- package/core/WebGLCanvasAdapter.js +0 -782
- package/features/Column.js +0 -43
- package/features/Grid.js +0 -47
- package/features/LayoutComponent.js +0 -43
- package/features/OpenStreetMap.js +0 -310
- package/features/Positioned.js +0 -33
- package/features/PullToRefresh.js +0 -328
- package/features/Row.js +0 -40
- package/features/SignaturePad.js +0 -257
- package/features/Skeleton.js +0 -193
- package/features/Stack.js +0 -21
- package/index.js +0 -119
- package/manager/AccessibilityManager.js +0 -107
- package/manager/ErrorHandler.js +0 -59
- package/manager/FeatureFlags.js +0 -60
- package/manager/MemoryManager.js +0 -107
- package/manager/PerformanceMonitor.js +0 -84
- package/manager/SecurityManager.js +0 -54
- package/utils/AnimationEngine.js +0 -734
- package/utils/CryptoManager.js +0 -303
- package/utils/DataStore.js +0 -403
- package/utils/DevTools.js +0 -1618
- package/utils/DevToolsConsole.js +0 -201
- package/utils/EventBus.js +0 -407
- package/utils/FetchClient.js +0 -74
- package/utils/FirebaseAuth.js +0 -653
- package/utils/FirebaseCore.js +0 -246
- package/utils/FirebaseFirestore.js +0 -581
- package/utils/FirebaseFunctions.js +0 -97
- package/utils/FirebaseRealtimeDB.js +0 -498
- package/utils/FirebaseStorage.js +0 -612
- package/utils/FormValidator.js +0 -355
- package/utils/GeoLocationService.js +0 -62
- package/utils/I18n.js +0 -207
- package/utils/IndexedDBManager.js +0 -273
- package/utils/InspectionOverlay.js +0 -308
- package/utils/NotificationManager.js +0 -60
- package/utils/OfflineSyncManager.js +0 -342
- package/utils/PayPalPayment.js +0 -678
- package/utils/QueryBuilder.js +0 -478
- package/utils/SafeArea.js +0 -64
- package/utils/SecureStorage.js +0 -289
- package/utils/StateManager.js +0 -207
- package/utils/StripePayment.js +0 -552
- package/utils/WebSocketClient.js +0 -66
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
import Component from '../core/Component.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Barre de navigation inférieure (Material & Cupertino)
|
|
5
|
-
* @class
|
|
6
|
-
* @extends Component
|
|
7
|
-
*/
|
|
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
|
-
*/
|
|
21
|
-
constructor(framework, options = {}) {
|
|
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;
|
|
35
|
-
this.platform = framework.platform;
|
|
36
|
-
|
|
37
|
-
// Couleurs selon la plateforme
|
|
38
|
-
if (this.platform === 'material') {
|
|
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)';
|
|
43
|
-
} else {
|
|
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';
|
|
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
|
-
}
|
|
65
|
-
|
|
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;
|
|
75
|
-
|
|
76
|
-
let needsUpdate = false;
|
|
77
|
-
|
|
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
|
-
}
|
|
101
|
-
|
|
102
|
-
// Redessiner si nécessaire
|
|
103
|
-
if (needsUpdate) {
|
|
104
|
-
this.requestRender();
|
|
105
|
-
}
|
|
106
|
-
|
|
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
|
-
};
|
|
115
|
-
|
|
116
|
-
if (this.ripples.length > 0 && !this.animationFrame) {
|
|
117
|
-
this.animationFrame = requestAnimationFrame(animate);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Demander un redessin
|
|
123
|
-
* @private
|
|
124
|
-
*/
|
|
125
|
-
requestRender() {
|
|
126
|
-
if (this.framework && this.framework.requestRender) {
|
|
127
|
-
this.framework.requestRender();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
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
|
-
}
|
|
141
|
-
|
|
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
|
-
}
|
|
154
|
-
|
|
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();
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
requestAnimationFrame(animate);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Dessine la barre de navigation
|
|
189
|
-
*/
|
|
190
|
-
draw(ctx) {
|
|
191
|
-
ctx.save();
|
|
192
|
-
|
|
193
|
-
// Background
|
|
194
|
-
ctx.fillStyle = this.bgColor;
|
|
195
|
-
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
196
|
-
|
|
197
|
-
// Bordure/Ombre supérieure
|
|
198
|
-
if (this.platform === 'material') {
|
|
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;
|
|
208
|
-
ctx.beginPath();
|
|
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
|
-
|
|
231
|
-
ctx.beginPath();
|
|
232
|
-
this.roundRect(ctx, indicatorX, indicatorY, indicatorWidth, indicatorHeight, 16);
|
|
233
|
-
ctx.fill();
|
|
234
|
-
}
|
|
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);
|
|
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
|
-
}
|
|
257
|
-
|
|
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
|
|
267
|
-
ctx.beginPath();
|
|
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();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Restaurer le contexte
|
|
280
|
-
ctx.restore();
|
|
281
|
-
}
|
|
282
|
-
|
|
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);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Vérifie si un point est dans les limites
|
|
372
|
-
*/
|
|
373
|
-
isPointInside(x, y) {
|
|
374
|
-
return y >= this.y && y <= this.y + this.height;
|
|
375
|
-
}
|
|
376
|
-
|
|
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
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
export default BottomNavigationBar;
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import Component from '../core/Component.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* BottomSheet avec styles Material et Cupertino
|
|
5
|
-
* @class
|
|
6
|
-
* @extends Component
|
|
7
|
-
*/
|
|
8
|
-
class BottomSheet extends Component {
|
|
9
|
-
constructor(framework, options = {}) {
|
|
10
|
-
super(framework, {
|
|
11
|
-
x: 0,
|
|
12
|
-
y: framework.height,
|
|
13
|
-
width: framework.width,
|
|
14
|
-
height: options.height || framework.height * 0.6,
|
|
15
|
-
visible: false
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
this.platform = framework.platform; // material / cupertino
|
|
19
|
-
|
|
20
|
-
this.children = [];
|
|
21
|
-
this.dragHandle = options.dragHandle !== false;
|
|
22
|
-
this.closeOnOverlayClick = options.closeOnOverlayClick !== false;
|
|
23
|
-
|
|
24
|
-
// Styles plateforme
|
|
25
|
-
if (this.platform === 'material') {
|
|
26
|
-
this.bgColor = options.bgColor || '#FFFFFF';
|
|
27
|
-
this.overlayColor = 'rgba(0,0,0,0.5)';
|
|
28
|
-
this.shadowBlur = 20;
|
|
29
|
-
this.shadowOffsetY = -5;
|
|
30
|
-
this.borderRadius = 11;
|
|
31
|
-
} else { // cupertino
|
|
32
|
-
this.bgColor = options.bgColor || 'rgba(255,255,255,0.95)';
|
|
33
|
-
this.overlayColor = 'rgba(0,0,0,0.2)';
|
|
34
|
-
this.shadowBlur = 0;
|
|
35
|
-
this.shadowOffsetY = 0;
|
|
36
|
-
this.borderRadius = options.borderRadius || 20;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
this.targetY = framework.height;
|
|
40
|
-
this.isOpen = false;
|
|
41
|
-
this.animating = false;
|
|
42
|
-
this.dragging = false;
|
|
43
|
-
this.dragStartY = 0;
|
|
44
|
-
this.dragOffset = 0;
|
|
45
|
-
this.lastClickTime = 0;
|
|
46
|
-
|
|
47
|
-
this.onPress = this.handlePress.bind(this);
|
|
48
|
-
this.onMove = this.handleMove.bind(this);
|
|
49
|
-
this.onRelease = this.handleRelease.bind(this);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
add(child) {
|
|
53
|
-
this.children.push(child);
|
|
54
|
-
return child;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
open() {
|
|
58
|
-
this.visible = true;
|
|
59
|
-
this.isOpen = true;
|
|
60
|
-
this.targetY = this.framework.height - this.height;
|
|
61
|
-
this.animate();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
close() {
|
|
65
|
-
this.isOpen = false;
|
|
66
|
-
this.targetY = this.framework.height;
|
|
67
|
-
this.animate(() => {
|
|
68
|
-
this.visible = false;
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
animate(callback) {
|
|
73
|
-
if (this.animating) return;
|
|
74
|
-
this.animating = true;
|
|
75
|
-
|
|
76
|
-
const step = () => {
|
|
77
|
-
let diff = this.targetY - this.y;
|
|
78
|
-
|
|
79
|
-
if (Math.abs(diff) < 1) {
|
|
80
|
-
this.y = this.targetY;
|
|
81
|
-
this.animating = false;
|
|
82
|
-
if (callback) callback();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Animation type spring pour iOS, easing pour Material
|
|
87
|
-
if (this.platform === 'cupertino') {
|
|
88
|
-
diff *= 0.15; // spring
|
|
89
|
-
} else {
|
|
90
|
-
diff *= 0.2; // easing
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.y += diff;
|
|
94
|
-
|
|
95
|
-
requestAnimationFrame(step);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
step();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
handlePress(x, y) {
|
|
102
|
-
const now = Date.now();
|
|
103
|
-
if (now - this.lastClickTime < 300) return;
|
|
104
|
-
this.lastClickTime = now;
|
|
105
|
-
|
|
106
|
-
if (y < this.y && this.closeOnOverlayClick) {
|
|
107
|
-
this.close();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Clic sur la poignée
|
|
112
|
-
if (this.dragHandle && y >= this.y && y <= this.y + 40) {
|
|
113
|
-
// ✅ Fermer le bottom sheet immédiatement
|
|
114
|
-
this.close();
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Gestion clic enfants
|
|
119
|
-
const contentY = this.y + (this.dragHandle ? 40 : 16);
|
|
120
|
-
for (let i = this.children.length - 1; i >= 0; i--) {
|
|
121
|
-
const child = this.children[i];
|
|
122
|
-
if (!child.visible) continue;
|
|
123
|
-
|
|
124
|
-
const childAbsX = this.x + 16 + child.x;
|
|
125
|
-
const childAbsY = contentY + child.y;
|
|
126
|
-
|
|
127
|
-
if (x >= childAbsX && x <= childAbsX + child.width &&
|
|
128
|
-
y >= childAbsY && y <= childAbsY + child.height) {
|
|
129
|
-
if (child.onClick) child.onClick();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
handleMove(x, y) {
|
|
136
|
-
if (!this.dragging) return;
|
|
137
|
-
this.dragOffset = y - this.dragStartY;
|
|
138
|
-
let newY = (this.framework.height - this.height) + this.dragOffset;
|
|
139
|
-
if (newY >= this.framework.height - this.height) this.y = newY;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
handleRelease() {
|
|
143
|
-
if (!this.dragging) return;
|
|
144
|
-
this.dragging = false;
|
|
145
|
-
this.framework.activeComponent = null;
|
|
146
|
-
|
|
147
|
-
if (this.dragOffset > this.height * 0.3) this.close();
|
|
148
|
-
else {
|
|
149
|
-
this.targetY = this.framework.height - this.height;
|
|
150
|
-
this.animate();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
draw(ctx) {
|
|
155
|
-
if (!this.visible) return;
|
|
156
|
-
ctx.save();
|
|
157
|
-
|
|
158
|
-
// Overlay
|
|
159
|
-
ctx.fillStyle = this.overlayColor;
|
|
160
|
-
ctx.fillRect(0, 0, this.framework.width, this.framework.height);
|
|
161
|
-
|
|
162
|
-
// Sheet
|
|
163
|
-
ctx.fillStyle = this.bgColor;
|
|
164
|
-
ctx.shadowColor = this.platform === 'material' ? 'rgba(0,0,0,0.3)' : 'transparent';
|
|
165
|
-
ctx.shadowBlur = this.shadowBlur;
|
|
166
|
-
ctx.shadowOffsetY = this.shadowOffsetY;
|
|
167
|
-
|
|
168
|
-
ctx.beginPath();
|
|
169
|
-
this.roundRectTop(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
170
|
-
ctx.fill();
|
|
171
|
-
ctx.shadowColor = 'transparent';
|
|
172
|
-
|
|
173
|
-
// Drag handle
|
|
174
|
-
if (this.dragHandle) {
|
|
175
|
-
ctx.fillStyle = this.platform === 'material' ? '#CCCCCC' : '#E0E0E0';
|
|
176
|
-
ctx.beginPath();
|
|
177
|
-
this.roundRect(ctx, this.width / 2 - 20, this.y + 12, 40, 4, 2);
|
|
178
|
-
ctx.fill();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Enfants
|
|
182
|
-
const contentY = this.y + (this.dragHandle ? 40 : 16);
|
|
183
|
-
const contentHeight = this.height - (this.dragHandle ? 40 : 16);
|
|
184
|
-
|
|
185
|
-
ctx.save();
|
|
186
|
-
ctx.beginPath();
|
|
187
|
-
ctx.rect(this.x, contentY, this.width, contentHeight);
|
|
188
|
-
ctx.clip();
|
|
189
|
-
|
|
190
|
-
for (let child of this.children) {
|
|
191
|
-
if (!child.visible) continue;
|
|
192
|
-
const origX = child.x;
|
|
193
|
-
const origY = child.y;
|
|
194
|
-
child.x = this.x + 16 + origX;
|
|
195
|
-
child.y = contentY + origY;
|
|
196
|
-
child.draw(ctx);
|
|
197
|
-
child.x = origX;
|
|
198
|
-
child.y = origY;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
ctx.restore();
|
|
202
|
-
ctx.restore();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
roundRectTop(ctx, x, y, width, height, radius) {
|
|
206
|
-
ctx.moveTo(x + radius, y);
|
|
207
|
-
ctx.lineTo(x + width - radius, y);
|
|
208
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
209
|
-
ctx.lineTo(x + width, y + height);
|
|
210
|
-
ctx.lineTo(x, y + height);
|
|
211
|
-
ctx.lineTo(x, y + radius);
|
|
212
|
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
roundRect(ctx, x, y, width, height, radius) {
|
|
216
|
-
if (radius === 0) return ctx.rect(x, y, width, height);
|
|
217
|
-
ctx.moveTo(x + radius, y);
|
|
218
|
-
ctx.lineTo(x + width - radius, y);
|
|
219
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
220
|
-
ctx.lineTo(x + width, y + height - radius);
|
|
221
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
222
|
-
ctx.lineTo(x + radius, y + height);
|
|
223
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
224
|
-
ctx.lineTo(x, y + radius);
|
|
225
|
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
226
|
-
ctx.closePath();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
isPointInside(x, y) {
|
|
230
|
-
return this.visible;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export default BottomSheet;
|