canvasframework 0.5.18 → 0.5.20

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 (113) hide show
  1. package/README.md +30 -0
  2. package/components/Accordion.js +265 -0
  3. package/components/AndroidDatePickerDialog.js +406 -0
  4. package/components/AppBar.js +398 -0
  5. package/components/AudioPlayer.js +611 -0
  6. package/components/Avatar.js +202 -0
  7. package/components/Banner.js +342 -0
  8. package/components/BottomNavigationBar.js +433 -0
  9. package/components/BottomSheet.js +234 -0
  10. package/components/Button.js +358 -0
  11. package/components/Camera.js +644 -0
  12. package/components/Card.js +193 -0
  13. package/components/Chart.js +700 -0
  14. package/components/Checkbox.js +166 -0
  15. package/components/Chip.js +212 -0
  16. package/components/CircularProgress.js +327 -0
  17. package/components/ContextMenu.js +116 -0
  18. package/components/DatePicker.js +298 -0
  19. package/components/Dialog.js +337 -0
  20. package/components/Divider.js +125 -0
  21. package/components/Drawer.js +276 -0
  22. package/components/FAB.js +270 -0
  23. package/components/FileUpload.js +315 -0
  24. package/components/FloatedCamera.js +644 -0
  25. package/components/IOSDatePickerWheel.js +430 -0
  26. package/components/ImageCarousel.js +219 -0
  27. package/components/ImageComponent.js +223 -0
  28. package/components/Input.js +831 -0
  29. package/components/InputDatalist.js +723 -0
  30. package/components/InputTags.js +624 -0
  31. package/components/List.js +95 -0
  32. package/components/ListItem.js +269 -0
  33. package/components/Modal.js +364 -0
  34. package/components/MorphingFAB.js +428 -0
  35. package/components/MultiSelectDialog.js +206 -0
  36. package/components/NumberInput.js +271 -0
  37. package/components/PasswordInput.js +462 -0
  38. package/components/ProgressBar.js +88 -0
  39. package/components/QRCodeReader.js +539 -0
  40. package/components/RadioButton.js +151 -0
  41. package/components/SearchInput.js +315 -0
  42. package/components/SegmentedControl.js +357 -0
  43. package/components/Select.js +199 -0
  44. package/components/SelectDialog.js +255 -0
  45. package/components/Slider.js +113 -0
  46. package/components/SliverAppBar.js +139 -0
  47. package/components/Snackbar.js +243 -0
  48. package/components/SpeedDialFAB.js +397 -0
  49. package/components/Stepper.js +281 -0
  50. package/components/SwipeableListItem.js +327 -0
  51. package/components/Switch.js +147 -0
  52. package/components/Table.js +492 -0
  53. package/components/Tabs.js +423 -0
  54. package/components/Text.js +141 -0
  55. package/components/TextField.js +151 -0
  56. package/components/TimePicker.js +934 -0
  57. package/components/Toast.js +236 -0
  58. package/components/TreeView.js +420 -0
  59. package/components/Video.js +397 -0
  60. package/components/View.js +140 -0
  61. package/components/VirtualList.js +120 -0
  62. package/core/CanvasFramework.js +3045 -0
  63. package/core/Component.js +243 -0
  64. package/core/ThemeManager.js +358 -0
  65. package/core/UIBuilder.js +267 -0
  66. package/core/WebGLCanvasAdapter.js +782 -0
  67. package/features/Column.js +43 -0
  68. package/features/Grid.js +47 -0
  69. package/features/LayoutComponent.js +43 -0
  70. package/features/OpenStreetMap.js +310 -0
  71. package/features/Positioned.js +33 -0
  72. package/features/PullToRefresh.js +328 -0
  73. package/features/Row.js +40 -0
  74. package/features/SignaturePad.js +257 -0
  75. package/features/Skeleton.js +193 -0
  76. package/features/Stack.js +21 -0
  77. package/index.js +119 -0
  78. package/manager/AccessibilityManager.js +107 -0
  79. package/manager/ErrorHandler.js +59 -0
  80. package/manager/FeatureFlags.js +60 -0
  81. package/manager/MemoryManager.js +107 -0
  82. package/manager/PerformanceMonitor.js +84 -0
  83. package/manager/SecurityManager.js +54 -0
  84. package/package.json +22 -16
  85. package/utils/AnimationEngine.js +734 -0
  86. package/utils/CryptoManager.js +303 -0
  87. package/utils/DataStore.js +403 -0
  88. package/utils/DevTools.js +1618 -0
  89. package/utils/DevToolsConsole.js +201 -0
  90. package/utils/EventBus.js +407 -0
  91. package/utils/FetchClient.js +74 -0
  92. package/utils/FirebaseAuth.js +653 -0
  93. package/utils/FirebaseCore.js +246 -0
  94. package/utils/FirebaseFirestore.js +581 -0
  95. package/utils/FirebaseFunctions.js +97 -0
  96. package/utils/FirebaseRealtimeDB.js +498 -0
  97. package/utils/FirebaseStorage.js +612 -0
  98. package/utils/FormValidator.js +355 -0
  99. package/utils/GeoLocationService.js +62 -0
  100. package/utils/I18n.js +207 -0
  101. package/utils/IndexedDBManager.js +273 -0
  102. package/utils/InspectionOverlay.js +308 -0
  103. package/utils/NotificationManager.js +60 -0
  104. package/utils/OfflineSyncManager.js +342 -0
  105. package/utils/PayPalPayment.js +678 -0
  106. package/utils/QueryBuilder.js +478 -0
  107. package/utils/SafeArea.js +64 -0
  108. package/utils/SecureStorage.js +289 -0
  109. package/utils/StateManager.js +207 -0
  110. package/utils/StripePayment.js +552 -0
  111. package/utils/WebSocketClient.js +66 -0
  112. package/dist/canvasframework.js +0 -2
  113. package/dist/canvasframework.js.LICENSE.txt +0 -1
@@ -0,0 +1,358 @@
1
+ import Component from '../core/Component.js';
2
+
3
+ /**
4
+ * Bouton cliquable avec variantes Material et Cupertino
5
+ * @class
6
+ * @extends Component
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)
11
+ */
12
+ class Button extends Component {
13
+ /**
14
+ * Crée une instance de Button
15
+ * @param {CanvasFramework} framework - Framework parent
16
+ * @param {Object} [options={}] - Options de configuration
17
+ * @param {string} [options.text='Button'] - Texte
18
+ * @param {number} [options.fontSize=16] - Taille de police
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)
24
+ */
25
+ constructor(framework, options = {}) {
26
+ super(framework, options);
27
+ this.text = options.text || 'Button';
28
+ this.fontSize = options.fontSize || 16;
29
+ this.platform = framework.platform;
30
+ this.shape = options.shape || 'rounded';
31
+
32
+ // Définir le type de bouton selon la plateforme
33
+ if (this.platform === 'material') {
34
+ this.type = options.type || 'filled';
35
+ this.setupMaterialStyle(options);
36
+ } else {
37
+ this.type = options.type || 'filled';
38
+ this.setupCupertinoStyle(options);
39
+ }
40
+
41
+ // Effets ripple (Material uniquement)
42
+ this.ripples = [];
43
+
44
+ // Bind
45
+ this.onPress = this.handlePress.bind(this);
46
+ }
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
+ default :
98
+ this.bgColor = options.bgColor || '#FFFFFF';
99
+ this.textColor = options.textColor || baseColor;
100
+ this.borderWidth = 0;
101
+ this.rippleColor = this.hexToRgba(baseColor, 0.2);
102
+ this.elevation = options.elevation || 4;
103
+ break;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Configure le style Cupertino (iOS)
109
+ * @private
110
+ */
111
+ setupCupertinoStyle(options) {
112
+ const baseColor = options.bgColor || '#007AFF';
113
+
114
+ switch (this.type) {
115
+ case 'filled':
116
+ this.bgColor = baseColor;
117
+ this.textColor = options.textColor || '#FFFFFF';
118
+ this.borderWidth = 0;
119
+ break;
120
+
121
+ case 'gray':
122
+ this.bgColor = 'rgba(120, 120, 128, 0.16)';
123
+ this.textColor = options.textColor || baseColor;
124
+ this.borderWidth = 0;
125
+ break;
126
+
127
+ case 'tinted':
128
+ this.bgColor = this.hexToRgba(baseColor, 0.2);
129
+ this.textColor = options.textColor || baseColor;
130
+ this.borderWidth = 0;
131
+ break;
132
+
133
+ case 'plain':
134
+ this.bgColor = 'transparent';
135
+ this.textColor = options.textColor || baseColor;
136
+ this.borderWidth = 0;
137
+ break;
138
+
139
+ default :
140
+ this.bgColor = 'transparent';
141
+ this.textColor = options.textColor || baseColor;
142
+ this.borderWidth = 0;
143
+ break;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Retourne le rayon des coins selon la forme
149
+ * @returns {number} Rayon en pixels
150
+ * @private
151
+ */
152
+ getBorderRadius() {
153
+ switch (this.shape) {
154
+ case 'square':
155
+ return 0;
156
+ case 'rounded':
157
+ default:
158
+ return this.platform === 'material' ? 4 : 10;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Gère la pression sur le bouton
164
+ * @param {number} x - Coordonnée X
165
+ * @param {number} y - Coordonnée Y
166
+ * @private
167
+ */
168
+ handlePress(x, y) {
169
+ if (this.platform === 'material') {
170
+ const adjustedY = y - this.framework.scrollOffset;
171
+ this.ripples.push({
172
+ x: x - this.x,
173
+ y: adjustedY - this.y,
174
+ radius: 0,
175
+ maxRadius: Math.max(this.width, this.height) * 1.5,
176
+ opacity: 1
177
+ });
178
+ this.animateRipple();
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Anime les effets ripple
184
+ * @private
185
+ */
186
+ animateRipple() {
187
+ const animate = () => {
188
+ for (let ripple of this.ripples) {
189
+ ripple.radius += ripple.maxRadius / 15;
190
+ ripple.opacity -= 0.05; // diminue progressivement
191
+ }
192
+
193
+ // Supprime les ripples complètement invisibles
194
+ this.ripples = this.ripples.filter(r => r.opacity > 0);
195
+
196
+ // Redessine le bouton après mise à jour (important!)
197
+ if (this.framework && this.framework.redraw) {
198
+ this.framework.redraw();
199
+ }
200
+
201
+ // Tant qu'il reste des ripples visibles, continue l'animation
202
+ if (this.ripples.length > 0) {
203
+ requestAnimationFrame(animate);
204
+ }
205
+ };
206
+
207
+ animate();
208
+ }
209
+
210
+ /**
211
+ * Dessine le bouton
212
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
213
+ */
214
+ draw(ctx) {
215
+ ctx.save();
216
+
217
+ const radius = this.getBorderRadius();
218
+
219
+ // Ombre Material (elevated/filled)
220
+ if (this.platform === 'material' && this.elevation > 0 && !this.pressed) {
221
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
222
+ ctx.shadowBlur = this.elevation * 2;
223
+ ctx.shadowOffsetY = this.elevation;
224
+ }
225
+
226
+ // Background
227
+ if (this.bgColor !== 'transparent') {
228
+ ctx.fillStyle = this.pressed ? this.darkenColor(this.bgColor) : this.bgColor;
229
+ ctx.beginPath();
230
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
231
+ ctx.fill();
232
+ }
233
+
234
+ // Réinitialiser l'ombre
235
+ ctx.shadowColor = 'transparent';
236
+ ctx.shadowBlur = 0;
237
+ ctx.shadowOffsetY = 0;
238
+
239
+ // Bordure
240
+ if (this.borderWidth > 0) {
241
+ ctx.strokeStyle = this.borderColor;
242
+ ctx.lineWidth = this.borderWidth;
243
+ ctx.beginPath();
244
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
245
+ ctx.stroke();
246
+ }
247
+
248
+ // Ripple effect (Material)
249
+ if (this.platform === 'material') {
250
+ ctx.save();
251
+ ctx.beginPath();
252
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
253
+ ctx.clip();
254
+
255
+ for (let ripple of this.ripples) {
256
+ ctx.globalAlpha = ripple.opacity;
257
+ ctx.fillStyle = this.rippleColor;
258
+ ctx.beginPath();
259
+ ctx.arc(this.x + ripple.x, this.y + ripple.y, ripple.radius, 0, Math.PI * 2);
260
+ ctx.fill();
261
+ }
262
+
263
+ ctx.restore();
264
+ }
265
+
266
+ // Effet pressed pour iOS (overlay sombre)
267
+ if (this.platform === 'cupertino' && this.pressed && this.bgColor !== 'transparent') {
268
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
269
+ ctx.beginPath();
270
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
271
+ ctx.fill();
272
+ }
273
+
274
+ // Texte
275
+ ctx.fillStyle = this.pressed && this.platform === 'cupertino'
276
+ ? this.darkenColor(this.textColor)
277
+ : this.textColor;
278
+ ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
279
+ ctx.textAlign = 'center';
280
+ ctx.textBaseline = 'middle';
281
+ ctx.fillText(this.text, this.x + this.width / 2, this.y + this.height / 2);
282
+
283
+ ctx.restore();
284
+ }
285
+
286
+ /**
287
+ * Dessine un rectangle avec coins arrondis
288
+ * @private
289
+ */
290
+ roundRect(ctx, x, y, width, height, radius) {
291
+ if (radius === 0) {
292
+ ctx.rect(x, y, width, height);
293
+ return;
294
+ }
295
+
296
+ ctx.moveTo(x + radius, y);
297
+ ctx.lineTo(x + width - radius, y);
298
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
299
+ ctx.lineTo(x + width, y + height - radius);
300
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
301
+ ctx.lineTo(x + radius, y + height);
302
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
303
+ ctx.lineTo(x, y + radius);
304
+ ctx.quadraticCurveTo(x, y, x + radius, y);
305
+ }
306
+
307
+ /**
308
+ * Assombrit une couleur
309
+ * @private
310
+ */
311
+ darkenColor(color) {
312
+ if (color === 'transparent') return 'rgba(0, 0, 0, 0.1)';
313
+
314
+ if (color.startsWith('rgba') || color.startsWith('rgb')) {
315
+ return color.replace(/[\d.]+\)$/g, match => {
316
+ const val = parseFloat(match);
317
+ return `${Math.max(0, val - 0.1)})`;
318
+ });
319
+ }
320
+
321
+ const rgb = this.hexToRgb(color);
322
+ return `rgb(${Math.max(0, rgb.r - 30)}, ${Math.max(0, rgb.g - 30)}, ${Math.max(0, rgb.b - 30)})`;
323
+ }
324
+
325
+ /**
326
+ * Convertit hex en RGB
327
+ * @private
328
+ */
329
+ hexToRgb(hex) {
330
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
331
+ return result ? {
332
+ r: parseInt(result[1], 16),
333
+ g: parseInt(result[2], 16),
334
+ b: parseInt(result[3], 16)
335
+ } : { r: 0, g: 0, b: 0 };
336
+ }
337
+
338
+ /**
339
+ * Convertit hex en RGBA avec alpha
340
+ * @private
341
+ */
342
+ hexToRgba(hex, alpha) {
343
+ const rgb = this.hexToRgb(hex);
344
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
345
+ }
346
+
347
+ /**
348
+ * Vérifie si un point est dans les limites
349
+ */
350
+ isPointInside(x, y) {
351
+ return x >= this.x &&
352
+ x <= this.x + this.width &&
353
+ y >= this.y &&
354
+ y <= this.y + this.height;
355
+ }
356
+ }
357
+
358
+ export default Button;