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.
Files changed (112) hide show
  1. package/dist/canvasframework.js +2 -0
  2. package/dist/canvasframework.js.LICENSE.txt +1 -0
  3. package/package.json +18 -17
  4. package/components/Accordion.js +0 -265
  5. package/components/AndroidDatePickerDialog.js +0 -406
  6. package/components/AppBar.js +0 -398
  7. package/components/AudioPlayer.js +0 -611
  8. package/components/Avatar.js +0 -202
  9. package/components/Banner.js +0 -342
  10. package/components/BottomNavigationBar.js +0 -433
  11. package/components/BottomSheet.js +0 -234
  12. package/components/Button.js +0 -360
  13. package/components/Camera.js +0 -644
  14. package/components/Card.js +0 -193
  15. package/components/Chart.js +0 -700
  16. package/components/Checkbox.js +0 -166
  17. package/components/Chip.js +0 -212
  18. package/components/CircularProgress.js +0 -327
  19. package/components/ContextMenu.js +0 -116
  20. package/components/DatePicker.js +0 -298
  21. package/components/Dialog.js +0 -337
  22. package/components/Divider.js +0 -125
  23. package/components/Drawer.js +0 -276
  24. package/components/FAB.js +0 -270
  25. package/components/FileUpload.js +0 -315
  26. package/components/FloatedCamera.js +0 -644
  27. package/components/IOSDatePickerWheel.js +0 -430
  28. package/components/ImageCarousel.js +0 -219
  29. package/components/ImageComponent.js +0 -223
  30. package/components/Input.js +0 -831
  31. package/components/InputDatalist.js +0 -723
  32. package/components/InputTags.js +0 -624
  33. package/components/List.js +0 -95
  34. package/components/ListItem.js +0 -269
  35. package/components/Modal.js +0 -364
  36. package/components/MorphingFAB.js +0 -428
  37. package/components/MultiSelectDialog.js +0 -206
  38. package/components/NumberInput.js +0 -271
  39. package/components/PasswordInput.js +0 -462
  40. package/components/ProgressBar.js +0 -88
  41. package/components/QRCodeReader.js +0 -539
  42. package/components/RadioButton.js +0 -151
  43. package/components/SearchInput.js +0 -315
  44. package/components/SegmentedControl.js +0 -357
  45. package/components/Select.js +0 -199
  46. package/components/SelectDialog.js +0 -255
  47. package/components/Slider.js +0 -113
  48. package/components/SliverAppBar.js +0 -139
  49. package/components/Snackbar.js +0 -243
  50. package/components/SpeedDialFAB.js +0 -397
  51. package/components/Stepper.js +0 -281
  52. package/components/SwipeableListItem.js +0 -327
  53. package/components/Switch.js +0 -147
  54. package/components/Table.js +0 -492
  55. package/components/Tabs.js +0 -423
  56. package/components/Text.js +0 -141
  57. package/components/TextField.js +0 -151
  58. package/components/TimePicker.js +0 -934
  59. package/components/Toast.js +0 -236
  60. package/components/TreeView.js +0 -420
  61. package/components/Video.js +0 -397
  62. package/components/View.js +0 -140
  63. package/components/VirtualList.js +0 -120
  64. package/core/CanvasFramework.js +0 -3034
  65. package/core/Component.js +0 -243
  66. package/core/ThemeManager.js +0 -358
  67. package/core/UIBuilder.js +0 -267
  68. package/core/WebGLCanvasAdapter.js +0 -782
  69. package/features/Column.js +0 -43
  70. package/features/Grid.js +0 -47
  71. package/features/LayoutComponent.js +0 -43
  72. package/features/OpenStreetMap.js +0 -310
  73. package/features/Positioned.js +0 -33
  74. package/features/PullToRefresh.js +0 -328
  75. package/features/Row.js +0 -40
  76. package/features/SignaturePad.js +0 -257
  77. package/features/Skeleton.js +0 -193
  78. package/features/Stack.js +0 -21
  79. package/index.js +0 -119
  80. package/manager/AccessibilityManager.js +0 -107
  81. package/manager/ErrorHandler.js +0 -59
  82. package/manager/FeatureFlags.js +0 -60
  83. package/manager/MemoryManager.js +0 -107
  84. package/manager/PerformanceMonitor.js +0 -84
  85. package/manager/SecurityManager.js +0 -54
  86. package/utils/AnimationEngine.js +0 -734
  87. package/utils/CryptoManager.js +0 -303
  88. package/utils/DataStore.js +0 -403
  89. package/utils/DevTools.js +0 -1618
  90. package/utils/DevToolsConsole.js +0 -201
  91. package/utils/EventBus.js +0 -407
  92. package/utils/FetchClient.js +0 -74
  93. package/utils/FirebaseAuth.js +0 -653
  94. package/utils/FirebaseCore.js +0 -246
  95. package/utils/FirebaseFirestore.js +0 -581
  96. package/utils/FirebaseFunctions.js +0 -97
  97. package/utils/FirebaseRealtimeDB.js +0 -498
  98. package/utils/FirebaseStorage.js +0 -612
  99. package/utils/FormValidator.js +0 -355
  100. package/utils/GeoLocationService.js +0 -62
  101. package/utils/I18n.js +0 -207
  102. package/utils/IndexedDBManager.js +0 -273
  103. package/utils/InspectionOverlay.js +0 -308
  104. package/utils/NotificationManager.js +0 -60
  105. package/utils/OfflineSyncManager.js +0 -342
  106. package/utils/PayPalPayment.js +0 -678
  107. package/utils/QueryBuilder.js +0 -478
  108. package/utils/SafeArea.js +0 -64
  109. package/utils/SecureStorage.js +0 -289
  110. package/utils/StateManager.js +0 -207
  111. package/utils/StripePayment.js +0 -552
  112. package/utils/WebSocketClient.js +0 -66
@@ -1,243 +0,0 @@
1
- import Component from '../core/Component.js';
2
- /**
3
- * Snackbar (notification avec action)
4
- * @class
5
- * @extends Component
6
- * @property {string} message - Message
7
- * @property {string|null} actionText - Texte de l'action
8
- * @property {Function} onAction - Callback de l'action
9
- * @property {number} duration - Durée d'affichage
10
- * @property {string} platform - Plateforme
11
- * @property {number} padding - Padding interne
12
- * @property {number} minWidth - Largeur minimale
13
- * @property {number} maxWidth - Largeur maximale
14
- * @property {number} targetY - Position Y cible
15
- * @property {number} opacity - Opacité
16
- * @property {boolean} isVisible - Visibilité
17
- * @property {Object|null} actionRect - Rectangle de l'action
18
- * @property {boolean} actionHovered - Action survolée
19
- */
20
- class Snackbar extends Component {
21
- /**
22
- * Crée une instance de Snackbar
23
- * @param {CanvasFramework} framework - Framework parent
24
- * @param {Object} [options={}] - Options de configuration
25
- * @param {string} [options.message=''] - Message
26
- * @param {string} [options.actionText] - Texte de l'action
27
- * @param {Function} [options.onAction] - Callback de l'action
28
- * @param {number} [options.duration=4000] - Durée en ms
29
- */
30
- constructor(framework, options = {}) {
31
- super(framework, options);
32
- this.message = options.message || '';
33
- this.actionText = options.actionText || null;
34
- this.onAction = options.onAction;
35
- this.duration = options.duration || 4000;
36
- this.platform = framework.platform;
37
-
38
- // Dimensions
39
- this.height = 48;
40
- this.padding = 16;
41
- this.minWidth = 344;
42
- this.maxWidth = Math.min(672, framework.width - 32);
43
-
44
- // Calculer la largeur
45
- const ctx = framework.ctx;
46
- ctx.font = '14px -apple-system, Roboto, sans-serif';
47
- const messageWidth = ctx.measureText(this.message).width;
48
- const actionWidth = this.actionText ? ctx.measureText(this.actionText).width + 40 : 0;
49
- this.width = Math.min(this.maxWidth, Math.max(this.minWidth, messageWidth + actionWidth + this.padding * 3));
50
-
51
- // Position (centré en bas)
52
- this.x = (framework.width - this.width) / 2;
53
- this.y = framework.height; // Commence hors écran
54
- this.targetY = framework.height - this.height - 37;
55
-
56
- this.opacity = 0;
57
- this.isVisible = false;
58
-
59
- // Zone du bouton d'action
60
- this.actionRect = null;
61
-
62
- this.onPress = this.handlePress.bind(this);
63
- }
64
-
65
- /**
66
- * Affiche la snackbar
67
- */
68
- show() {
69
- this.isVisible = true;
70
- this.visible = true;
71
- this.animateIn();
72
-
73
- // Auto-hide après duration
74
- setTimeout(() => {
75
- if (this.isVisible) {
76
- this.hide();
77
- }
78
- }, this.duration);
79
- }
80
-
81
- /**
82
- * Cache la snackbar
83
- */
84
- hide() {
85
- this.animateOut();
86
- }
87
-
88
- /**
89
- * Anime l'entrée
90
- * @private
91
- */
92
- animateIn() {
93
- const animate = () => {
94
- if (this.y > this.targetY) {
95
- this.y -= (this.y - this.targetY) * 0.2;
96
- this.opacity = Math.min(1, this.opacity + 0.1);
97
- requestAnimationFrame(animate);
98
- } else {
99
- this.y = this.targetY;
100
- this.opacity = 1;
101
- }
102
- };
103
- animate();
104
- }
105
-
106
- /**
107
- * Anime la sortie
108
- * @private
109
- */
110
- animateOut() {
111
- const animate = () => {
112
- if (this.opacity > 0) {
113
- this.y += 5;
114
- this.opacity -= 0.1;
115
- requestAnimationFrame(animate);
116
- } else {
117
- this.isVisible = false;
118
- this.visible = false;
119
- this.framework.remove(this);
120
- }
121
- };
122
- animate();
123
- }
124
-
125
- /**
126
- * Dessine la snackbar
127
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
128
- */
129
- draw(ctx) {
130
- if (!this.isVisible || this.opacity <= 0) return;
131
-
132
- ctx.save();
133
- ctx.globalAlpha = this.opacity;
134
-
135
- // Background
136
- ctx.fillStyle = this.platform === 'material' ? '#323232' : 'rgba(0, 0, 0, 0.9)';
137
- ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
138
- ctx.shadowBlur = 8;
139
- ctx.shadowOffsetY = 4;
140
-
141
- ctx.beginPath();
142
- this.roundRect(ctx, this.x, this.y, this.width, this.height, 4);
143
- ctx.fill();
144
-
145
- ctx.shadowColor = 'transparent';
146
-
147
- // Message
148
- ctx.fillStyle = '#FFFFFF';
149
- ctx.font = '14px -apple-system, Roboto, sans-serif';
150
- ctx.textAlign = 'left';
151
- ctx.textBaseline = 'middle';
152
- ctx.fillText(this.message, this.x + this.padding, this.y + this.height / 2);
153
-
154
- // Bouton d'action
155
- if (this.actionText) {
156
- const ctx2 = this.framework.ctx;
157
- ctx2.font = '14px -apple-system, Roboto, sans-serif';
158
- const actionWidth = ctx2.measureText(this.actionText).width;
159
- const actionX = this.x + this.width - actionWidth - this.padding * 2;
160
- const actionY = this.y;
161
-
162
- this.actionRect = {
163
- x: actionX,
164
- y: actionY,
165
- width: actionWidth + this.padding * 2,
166
- height: this.height
167
- };
168
-
169
- // Highlight si hover
170
- if (this.actionHovered) {
171
- ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
172
- ctx.fillRect(actionX, actionY, actionWidth + this.padding * 2, this.height);
173
- }
174
-
175
- // Texte du bouton
176
- const actionColor = this.platform === 'material' ? '#BB86FC' : '#0A84FF';
177
- ctx.fillStyle = actionColor;
178
- ctx.font = 'bold 14px -apple-system, Roboto, sans-serif';
179
- ctx.textAlign = 'center';
180
- ctx.fillText(this.actionText, actionX + (actionWidth + this.padding * 2) / 2, this.y + this.height / 2);
181
- }
182
-
183
- ctx.restore();
184
- }
185
-
186
- /**
187
- * Gère la pression (clic)
188
- * @param {number} x - Coordonnée X
189
- * @param {number} y - Coordonnée Y
190
- * @private
191
- */
192
- handlePress(x, y) {
193
- if (this.actionRect && this.actionText) {
194
- if (x >= this.actionRect.x &&
195
- x <= this.actionRect.x + this.actionRect.width &&
196
- y >= this.actionRect.y &&
197
- y <= this.actionRect.y + this.actionRect.height) {
198
- if (this.onAction) {
199
- this.onAction();
200
- }
201
- this.hide();
202
- }
203
- }
204
- }
205
-
206
- /**
207
- * Dessine un rectangle avec coins arrondis
208
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
209
- * @param {number} x - Position X
210
- * @param {number} y - Position Y
211
- * @param {number} width - Largeur
212
- * @param {number} height - Hauteur
213
- * @param {number} radius - Rayon des coins
214
- * @private
215
- */
216
- roundRect(ctx, x, y, width, height, radius) {
217
- ctx.beginPath();
218
- ctx.moveTo(x + radius, y);
219
- ctx.lineTo(x + width - radius, y);
220
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
221
- ctx.lineTo(x + width, y + height - radius);
222
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
223
- ctx.lineTo(x + radius, y + height);
224
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
225
- ctx.lineTo(x, y + radius);
226
- ctx.quadraticCurveTo(x, y, x + radius, y);
227
- ctx.closePath();
228
- }
229
-
230
- /**
231
- * Vérifie si un point est dans les limites
232
- * @param {number} x - Coordonnée X
233
- * @param {number} y - Coordonnée Y
234
- * @returns {boolean} True si le point est dans la snackbar
235
- */
236
- isPointInside(x, y) {
237
- return this.isVisible &&
238
- x >= this.x && x <= this.x + this.width &&
239
- y >= this.y && y <= this.y + this.height;
240
- }
241
- }
242
-
243
- export default Snackbar;
@@ -1,397 +0,0 @@
1
- import FAB from './FAB.js';
2
-
3
- /**
4
- * Speed Dial FAB - FAB qui ouvre un menu d'actions
5
- * @class
6
- * @extends FAB
7
- * @property {Array} actions - Liste des actions du menu
8
- * @property {boolean} isOpen - État ouvert/fermé
9
- * @property {number} animProgress - Progression de l'animation (0-1)
10
- */
11
- class SpeedDialFAB extends FAB {
12
- /**
13
- * Crée une instance de SpeedDialFAB
14
- * @param {CanvasFramework} framework - Framework parent
15
- * @param {Object} [options={}] - Options de configuration
16
- * @param {Array} [options.actions=[]] - Actions du menu
17
- * @example
18
- * actions: [
19
- * { icon: '✉', label: 'Email', bgColor: '#4CAF50', action: () => {...} },
20
- * { icon: '📞', label: 'Call', bgColor: '#2196F3', action: () => {...} },
21
- * { icon: '📍', label: Map', bgColor: '#FF9800', action: () => {...} }
22
- * ]
23
- */
24
- constructor(framework, options = {}) {
25
- super(framework, {
26
- ...options,
27
- icon: options.icon || '+'
28
- });
29
-
30
- this.actions = options.actions || [];
31
- this.isOpen = false;
32
- this.animProgress = 0;
33
- this.actionSpacing = 72; // Espacement entre les mini FABs
34
-
35
- // AJOUT: Flags pour gérer l'interaction
36
- this.justClicked = false; // Pour éviter la fermeture immédiate
37
- this.clickStartTime = 0;
38
- this.clickStartY = 0;
39
- this.isScrolling = false;
40
- this.scrollThreshold = 5; // Seuil de mouvement pour détecter un scroll
41
-
42
- // Initialiser les mini FABs
43
- this.miniFabs = this.actions.map((action, index) => ({
44
- ...action,
45
- size: 48,
46
- x: this.x,
47
- y: this.y,
48
- targetY: this.y - (index + 1) * this.actionSpacing,
49
- currentY: this.y,
50
- alpha: 0,
51
- pressed: false
52
- }));
53
-
54
- // Overlay pour fermer le menu
55
- this.showOverlay = false;
56
-
57
- // Bind methods
58
- this.onPress = this.handlePress.bind(this);
59
- this.onMove = this.handleMove.bind(this);
60
- }
61
-
62
- /**
63
- * Gère le début de la pression
64
- * @param {number} x - Coordonnée X
65
- * @param {number} y - Coordonnée Y
66
- */
67
- handlePress(x, y) {
68
- this.justClicked = true;
69
- this.clickStartTime = Date.now();
70
- this.clickStartY = y;
71
- this.isScrolling = false;
72
-
73
- const adjustedY = y - this.framework.scrollOffset;
74
-
75
- // 1. Vérifier si c'est le FAB principal
76
- const isMainFabClick = x >= this.x && x <= this.x + this.width &&
77
- adjustedY >= this.y && adjustedY <= this.y + this.height;
78
-
79
- if (isMainFabClick) {
80
- // Le FAB principal peut toujours être cliqué (ouvrir/fermer)
81
- this.toggle();
82
- super.handlePress(x, y);
83
-
84
- // Empêcher la fermeture immédiate
85
- setTimeout(() => {
86
- this.justClicked = false;
87
- }, 300);
88
- return;
89
- }
90
-
91
- // 2. Si le menu est ouvert, vérifier les mini FABs
92
- if (this.isOpen) {
93
- for (let i = 0; i < this.miniFabs.length; i++) {
94
- const fab = this.miniFabs[i];
95
- if (fab.alpha < 0.5) continue; // Ignorer les FABs pas encore visibles
96
-
97
- const fabX = this.x + (this.width - fab.size) / 2;
98
- const fabY = fab.currentY;
99
-
100
- const distance = Math.sqrt(
101
- Math.pow(x - (fabX + fab.size / 2), 2) +
102
- Math.pow(adjustedY - (fabY + fab.size / 2), 2)
103
- );
104
-
105
- if (distance <= fab.size / 2) {
106
- // Action cliquée
107
- fab.pressed = true;
108
- setTimeout(() => {
109
- fab.pressed = false;
110
- if (fab.action) fab.action();
111
- this.close();
112
- }, 150);
113
-
114
- // Empêcher la fermeture par handleClickOutside
115
- this.justClicked = true;
116
- setTimeout(() => {
117
- this.justClicked = false;
118
- }, 300);
119
- return;
120
- }
121
- }
122
-
123
- // 3. Clic sur overlay (n'importe où ailleurs)
124
- // Ne pas fermer immédiatement, attendre la fin du mouvement
125
- }
126
-
127
- // Pas de return ici, la fermeture sera gérée par handleClickOutside
128
- // avec vérification du mouvement
129
- }
130
-
131
- /**
132
- * Gère le mouvement
133
- * @param {number} x - Coordonnée X
134
- * @param {number} y - Coordonnée Y
135
- */
136
- handleMove(x, y) {
137
- // Détecter si c'est un scroll
138
- const deltaY = Math.abs(y - this.clickStartY);
139
- if (deltaY > this.scrollThreshold) {
140
- this.isScrolling = true;
141
- }
142
- }
143
-
144
- /**
145
- * Ouvre le menu
146
- */
147
- open() {
148
- if (this.isOpen) return;
149
- this.isOpen = true;
150
- this.showOverlay = true;
151
- this.animate();
152
- }
153
-
154
- /**
155
- * Ferme le menu
156
- */
157
- close() {
158
- if (!this.isOpen) return;
159
- this.isOpen = false;
160
- this.animate();
161
- }
162
-
163
- /**
164
- * Toggle l'état ouvert/fermé
165
- */
166
- toggle() {
167
- this.isOpen ? this.close() : this.open();
168
- }
169
-
170
- /**
171
- * Anime l'ouverture/fermeture
172
- * @private
173
- */
174
- animate() {
175
- const startTime = Date.now();
176
- const duration = 300;
177
-
178
- const step = () => {
179
- const elapsed = Date.now() - startTime;
180
- const progress = Math.min(elapsed / duration, 1);
181
-
182
- // Easing out cubic
183
- const eased = 1 - Math.pow(1 - progress, 3);
184
-
185
- this.animProgress = this.isOpen ? eased : 1 - eased;
186
-
187
- // Mettre à jour les positions des mini FABs
188
- this.miniFabs.forEach((fab, index) => {
189
- const delay = index * 0.05;
190
- const fabProgress = Math.max(0, Math.min(1, (this.animProgress - delay) / (1 - delay)));
191
-
192
- fab.currentY = this.y - (fabProgress * (index + 1) * this.actionSpacing);
193
- fab.alpha = fabProgress;
194
- });
195
-
196
- if (progress < 1) {
197
- requestAnimationFrame(step);
198
- } else {
199
- if (!this.isOpen) {
200
- this.showOverlay = false;
201
- }
202
- }
203
- };
204
-
205
- step();
206
- }
207
-
208
- /**
209
- * Dessine le Speed Dial FAB
210
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
211
- */
212
- draw(ctx) {
213
- // Overlay semi-transparent avec gestion du clic
214
- if (this.showOverlay) {
215
- ctx.save();
216
- ctx.fillStyle = `rgba(0, 0, 0, ${this.animProgress * 0.5})`;
217
- ctx.fillRect(0, 0, this.framework.width, this.framework.height);
218
- ctx.restore();
219
-
220
- // Dessiner une zone invisible pour détecter les clics sur l'overlay
221
- // On enregistre cette zone pour la détection
222
- this.overlayActive = true;
223
- } else {
224
- this.overlayActive = false;
225
- }
226
-
227
- // Dessiner les mini FABs (de bas en haut)
228
- if (this.animProgress > 0) {
229
- for (let i = this.miniFabs.length - 1; i >= 0; i--) {
230
- const fab = this.miniFabs[i];
231
- if (fab.alpha > 0.01) {
232
- this.drawMiniFab(ctx, fab);
233
- }
234
- }
235
- }
236
-
237
- // FAB principal
238
- ctx.save();
239
-
240
- // Rotation de l'icône + quand ouvert
241
- if (this.icon === '+') {
242
- ctx.save();
243
- ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
244
- ctx.rotate((this.animProgress * 45) * Math.PI / 180);
245
- ctx.translate(-(this.x + this.width / 2), -(this.y + this.height / 2));
246
- }
247
-
248
- super.draw(ctx);
249
-
250
- if (this.icon === '+') {
251
- ctx.restore();
252
- }
253
-
254
- ctx.restore();
255
- }
256
-
257
- /**
258
- * Dessine un mini FAB
259
- * @private
260
- */
261
- drawMiniFab(ctx, fab) {
262
- ctx.save();
263
- ctx.globalAlpha = fab.alpha;
264
-
265
- const fabX = this.x + (this.width - fab.size) / 2;
266
- const fabY = fab.currentY;
267
-
268
- // Ombre
269
- if (!fab.pressed) {
270
- ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
271
- ctx.shadowBlur = 6;
272
- ctx.shadowOffsetY = 3;
273
- }
274
-
275
- // Background
276
- const bgColor = fab.bgColor || this.bgColor;
277
- ctx.fillStyle = fab.pressed ? this.darkenColor(bgColor) : bgColor;
278
- ctx.beginPath();
279
- ctx.arc(fabX + fab.size / 2, fabY + fab.size / 2, fab.size / 2, 0, Math.PI * 2);
280
- ctx.fill();
281
-
282
- ctx.shadowColor = 'transparent';
283
- ctx.shadowBlur = 0;
284
- ctx.shadowOffsetY = 0;
285
-
286
- // Icône
287
- ctx.fillStyle = fab.iconColor || '#FFFFFF';
288
- ctx.font = 'bold 20px sans-serif';
289
- ctx.textAlign = 'center';
290
- ctx.textBaseline = 'middle';
291
- ctx.fillText(fab.icon, fabX + fab.size / 2, fabY + fab.size / 2);
292
-
293
- // Label à gauche
294
- if (fab.label && fab.alpha > 0.5) {
295
- ctx.fillStyle = '#FFFFFF';
296
- ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
297
- ctx.shadowBlur = 4;
298
-
299
- const labelPadding = 12;
300
- const labelHeight = 32;
301
- ctx.font = '14px -apple-system, sans-serif';
302
- const labelWidth = ctx.measureText(fab.label).width + labelPadding * 2;
303
-
304
- // Fond du label
305
- ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
306
- this.roundRect(
307
- ctx,
308
- fabX - labelWidth - 8,
309
- fabY + fab.size / 2 - labelHeight / 2,
310
- labelWidth,
311
- labelHeight,
312
- 4
313
- );
314
- ctx.fill();
315
-
316
- ctx.shadowColor = 'transparent';
317
- ctx.shadowBlur = 0;
318
-
319
- // Texte du label
320
- ctx.fillStyle = '#FFFFFF';
321
- ctx.textAlign = 'right';
322
- ctx.fillText(fab.label, fabX - 16, fabY + fab.size / 2);
323
- }
324
-
325
- ctx.restore();
326
- }
327
-
328
- /**
329
- * Dessine un rectangle arrondi
330
- * @private
331
- */
332
- roundRect(ctx, x, y, width, height, radius) {
333
- ctx.beginPath();
334
- ctx.moveTo(x + radius, y);
335
- ctx.lineTo(x + width - radius, y);
336
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
337
- ctx.lineTo(x + width, y + height - radius);
338
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
339
- ctx.lineTo(x + radius, y + height);
340
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
341
- ctx.lineTo(x, y + radius);
342
- ctx.quadraticCurveTo(x, y, x + radius, y);
343
- }
344
-
345
- /**
346
- * Vérifie si un point est dans le Speed Dial
347
- */
348
- /**
349
- * Vérifie si un point est dans le Speed Dial
350
- */
351
- isPointInside(x, y) {
352
- const adjustedY = y - this.framework.scrollOffset;
353
-
354
- // Vérifier le FAB principal
355
- if (x >= this.x && x <= this.x + this.width &&
356
- adjustedY >= this.y && adjustedY <= this.y + this.height) {
357
- return true;
358
- }
359
-
360
- // Si ouvert, vérifier les mini FABs
361
- if (this.isOpen) {
362
- for (let fab of this.miniFabs) {
363
- if (fab.alpha < 0.01) continue;
364
-
365
- const fabX = this.x + (this.width - fab.size) / 2;
366
- const fabY = fab.currentY;
367
-
368
- const distance = Math.sqrt(
369
- Math.pow(x - (fabX + fab.size / 2), 2) +
370
- Math.pow(adjustedY - (fabY + fab.size / 2), 2)
371
- );
372
-
373
- if (distance <= fab.size / 2) {
374
- return true;
375
- }
376
- }
377
-
378
- // LA CLÉ : Quand le menu est ouvert, TOUT L'ÉCRAN compte comme "inside"
379
- // Cela empêche le framework d'appeler handleClickOutside()
380
- return true;
381
- }
382
-
383
- return false;
384
- }
385
-
386
- /**
387
- * Gère le clic en dehors (appelé par le framework)
388
- */
389
- handleClickOutside() {
390
- // Ne pas fermer automatiquement
391
- // La fermeture se fera par handleClick() seulement
392
- // si on a vraiment cliqué en dehors
393
- }
394
-
395
-
396
- }
397
- export default SpeedDialFAB;