canvasframework 0.3.6

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 (85) hide show
  1. package/README.md +554 -0
  2. package/components/Accordion.js +252 -0
  3. package/components/AndroidDatePickerDialog.js +398 -0
  4. package/components/AppBar.js +225 -0
  5. package/components/Avatar.js +202 -0
  6. package/components/BottomNavigationBar.js +205 -0
  7. package/components/BottomSheet.js +374 -0
  8. package/components/Button.js +225 -0
  9. package/components/Card.js +193 -0
  10. package/components/Checkbox.js +180 -0
  11. package/components/Chip.js +212 -0
  12. package/components/CircularProgress.js +143 -0
  13. package/components/ContextMenu.js +116 -0
  14. package/components/DatePicker.js +257 -0
  15. package/components/Dialog.js +367 -0
  16. package/components/Divider.js +125 -0
  17. package/components/Drawer.js +261 -0
  18. package/components/FAB.js +270 -0
  19. package/components/FileUpload.js +315 -0
  20. package/components/IOSDatePickerWheel.js +268 -0
  21. package/components/ImageCarousel.js +193 -0
  22. package/components/ImageComponent.js +223 -0
  23. package/components/Input.js +309 -0
  24. package/components/List.js +94 -0
  25. package/components/ListItem.js +223 -0
  26. package/components/Modal.js +364 -0
  27. package/components/MultiSelectDialog.js +206 -0
  28. package/components/NumberInput.js +271 -0
  29. package/components/ProgressBar.js +88 -0
  30. package/components/RadioButton.js +142 -0
  31. package/components/SearchInput.js +315 -0
  32. package/components/SegmentedControl.js +202 -0
  33. package/components/Select.js +199 -0
  34. package/components/SelectDialog.js +255 -0
  35. package/components/Slider.js +113 -0
  36. package/components/Snackbar.js +243 -0
  37. package/components/Stepper.js +281 -0
  38. package/components/SwipeableListItem.js +179 -0
  39. package/components/Switch.js +147 -0
  40. package/components/Table.js +492 -0
  41. package/components/Tabs.js +125 -0
  42. package/components/Text.js +141 -0
  43. package/components/TextField.js +331 -0
  44. package/components/Toast.js +236 -0
  45. package/components/TreeView.js +420 -0
  46. package/components/Video.js +397 -0
  47. package/components/View.js +140 -0
  48. package/components/VirtualList.js +120 -0
  49. package/core/CanvasFramework.js +1271 -0
  50. package/core/CanvasWork.js +32 -0
  51. package/core/Component.js +153 -0
  52. package/core/LogicWorker.js +25 -0
  53. package/core/WebGLCanvasAdapter.js +1369 -0
  54. package/features/Column.js +43 -0
  55. package/features/Grid.js +47 -0
  56. package/features/LayoutComponent.js +43 -0
  57. package/features/OpenStreetMap.js +310 -0
  58. package/features/Positioned.js +33 -0
  59. package/features/PullToRefresh.js +328 -0
  60. package/features/Row.js +40 -0
  61. package/features/SignaturePad.js +257 -0
  62. package/features/Skeleton.js +84 -0
  63. package/features/Stack.js +21 -0
  64. package/index.js +101 -0
  65. package/manager/AccessibilityManager.js +107 -0
  66. package/manager/ErrorHandler.js +59 -0
  67. package/manager/FeatureFlags.js +60 -0
  68. package/manager/MemoryManager.js +107 -0
  69. package/manager/PerformanceMonitor.js +84 -0
  70. package/manager/SecurityManager.js +54 -0
  71. package/package.json +28 -0
  72. package/utils/AnimationEngine.js +428 -0
  73. package/utils/DataStore.js +403 -0
  74. package/utils/EventBus.js +407 -0
  75. package/utils/FetchClient.js +74 -0
  76. package/utils/FormValidator.js +355 -0
  77. package/utils/GeoLocationService.js +62 -0
  78. package/utils/I18n.js +207 -0
  79. package/utils/IndexedDBManager.js +273 -0
  80. package/utils/OfflineSyncManager.js +342 -0
  81. package/utils/QueryBuilder.js +478 -0
  82. package/utils/SafeArea.js +64 -0
  83. package/utils/SecureStorage.js +289 -0
  84. package/utils/StateManager.js +207 -0
  85. package/utils/WebSocketClient.js +66 -0
@@ -0,0 +1,367 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Boîte de dialogue modale
4
+ * @class
5
+ * @extends Component
6
+ * @property {string} title - Titre du dialog
7
+ * @property {string} message - Message du dialog
8
+ * @property {string[]} buttons - Liste des boutons
9
+ * @property {Function} onButtonClick - Callback au clic sur un bouton
10
+ * @property {number} dialogWidth - Largeur du dialog
11
+ * @property {number} dialogHeight - Hauteur du dialog
12
+ * @property {number} opacity - Opacité pour l'animation
13
+ * @property {Array} buttonRects - Positions des boutons
14
+ * @property {boolean} isVisible - Visibilité
15
+ * @property {number} pressedButtonIndex - Index du bouton pressé
16
+ */
17
+ class Dialog extends Component {
18
+ /**
19
+ * Crée une instance de Dialog
20
+ * @param {CanvasFramework} framework - Framework parent
21
+ * @param {Object} [options={}] - Options de configuration
22
+ * @param {string} [options.title=''] - Titre
23
+ * @param {string} [options.message=''] - Message
24
+ * @param {string[]} [options.buttons=['OK']] - Boutons
25
+ * @param {Function} [options.onButtonClick] - Callback au clic sur bouton
26
+ */
27
+ constructor(framework, options = {}) {
28
+ super(framework, {
29
+ x: 0,
30
+ y: 0,
31
+ width: framework.width,
32
+ height: framework.height,
33
+ visible: false
34
+ });
35
+ this.title = options.title || '';
36
+ this.message = options.message || '';
37
+ this.buttons = options.buttons || ['OK'];
38
+ this.onButtonClick = options.onButtonClick;
39
+ this.dialogWidth = Math.min(320, framework.width - 40);
40
+ this.dialogHeight = 200;
41
+ this.opacity = 0;
42
+ this.buttonRects = [];
43
+ this.isVisible = false;
44
+ this.pressedButtonIndex = -1;
45
+
46
+ // Définir onPress
47
+ this.onPress = this.handlePress.bind(this);
48
+
49
+ // Pour diviser le message en plusieurs lignes
50
+ this.messageLines = this.wrapText(this.message, this.dialogWidth - 40, '16px -apple-system, sans-serif');
51
+
52
+ // Ajuster la hauteur du dialog en fonction du message
53
+ if (this.messageLines.length > 2) {
54
+ this.dialogHeight = 150 + (this.messageLines.length - 2) * 20;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Divise le texte en plusieurs lignes
60
+ * @param {string} text - Texte à diviser
61
+ * @param {number} maxWidth - Largeur maximale
62
+ * @param {string} font - Police de caractères
63
+ * @returns {string[]} Tableau de lignes
64
+ * @private
65
+ */
66
+ wrapText(text, maxWidth, font) {
67
+ const ctx = this.framework.ctx;
68
+ ctx.save();
69
+ ctx.font = font;
70
+
71
+ const words = text.split(' ');
72
+ const lines = [];
73
+ let currentLine = words[0];
74
+
75
+ for (let i = 1; i < words.length; i++) {
76
+ const word = words[i];
77
+ const width = ctx.measureText(currentLine + " " + word).width;
78
+ if (width < maxWidth) {
79
+ currentLine += " " + word;
80
+ } else {
81
+ lines.push(currentLine);
82
+ currentLine = word;
83
+ }
84
+ }
85
+ lines.push(currentLine);
86
+
87
+ ctx.restore();
88
+ return lines;
89
+ }
90
+
91
+ /**
92
+ * Dessine le dialog
93
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
94
+ */
95
+ draw(ctx) {
96
+ if (this.opacity <= 0 || !this.isVisible) return;
97
+
98
+ ctx.save();
99
+ ctx.globalAlpha = this.opacity;
100
+
101
+ // Overlay sombre
102
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
103
+ ctx.fillRect(0, 0, this.framework.width, this.framework.height);
104
+
105
+ // Dialog box
106
+ const dialogX = (this.framework.width - this.dialogWidth) / 2;
107
+ const dialogY = (this.framework.height - this.dialogHeight) / 2;
108
+
109
+ ctx.fillStyle = '#FFFFFF';
110
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
111
+ ctx.shadowBlur = 20;
112
+ ctx.beginPath();
113
+ this.roundRect(ctx, dialogX, dialogY, this.dialogWidth, this.dialogHeight, 12);
114
+ ctx.fill();
115
+
116
+ ctx.shadowColor = 'transparent';
117
+
118
+ // Titre
119
+ ctx.fillStyle = '#000000';
120
+ ctx.font = 'bold 18px -apple-system, sans-serif';
121
+ ctx.textAlign = 'center';
122
+ ctx.textBaseline = 'middle';
123
+ ctx.fillText(this.title, dialogX + this.dialogWidth / 2, dialogY + 40);
124
+
125
+ // Message (lignes multiples)
126
+ ctx.fillStyle = '#666666';
127
+ ctx.font = '16px -apple-system, sans-serif';
128
+ ctx.textAlign = 'center';
129
+
130
+ for (let i = 0; i < this.messageLines.length; i++) {
131
+ ctx.fillText(
132
+ this.messageLines[i],
133
+ dialogX + this.dialogWidth / 2,
134
+ dialogY + 80 + (i * 24)
135
+ );
136
+ }
137
+
138
+ // Divider au-dessus des boutons
139
+ ctx.strokeStyle = '#E0E0E0';
140
+ ctx.lineWidth = 1;
141
+ ctx.beginPath();
142
+ ctx.moveTo(dialogX, dialogY + this.dialogHeight - 60);
143
+ ctx.lineTo(dialogX + this.dialogWidth, dialogY + this.dialogHeight - 60);
144
+ ctx.stroke();
145
+
146
+ // Boutons
147
+ this.buttonRects = [];
148
+ const buttonHeight = 50;
149
+ const buttonWidth = this.dialogWidth / this.buttons.length;
150
+
151
+ for (let i = 0; i < this.buttons.length; i++) {
152
+ const btnX = dialogX + i * buttonWidth;
153
+ const btnY = dialogY + this.dialogHeight - buttonHeight;
154
+
155
+ // Stocker la position du bouton
156
+ this.buttonRects.push({
157
+ x: btnX,
158
+ y: btnY,
159
+ width: buttonWidth,
160
+ height: buttonHeight
161
+ });
162
+
163
+ // Style du bouton
164
+ const isPrimary = i === this.buttons.length - 1; // Dernier bouton = primaire
165
+ ctx.fillStyle = isPrimary ? '#007AFF' : '#8E8E93';
166
+ ctx.font = 'bold 17px -apple-system, sans-serif';
167
+ ctx.textAlign = 'center';
168
+ ctx.textBaseline = 'middle';
169
+
170
+ // Effet hover
171
+ if (this.framework.hoveredComponent === this && this.isPointInButton(i, this.lastX, this.lastY)) {
172
+ ctx.globalAlpha = this.opacity * 0.8;
173
+ }
174
+
175
+ // Effet pressé
176
+ if (this.pressedButtonIndex === i) {
177
+ ctx.globalAlpha = this.opacity * 0.6;
178
+ }
179
+
180
+ ctx.fillText(this.buttons[i], btnX + buttonWidth / 2, btnY + buttonHeight / 2);
181
+ ctx.globalAlpha = this.opacity;
182
+
183
+ // Divider entre boutons (sauf pour le dernier)
184
+ if (i < this.buttons.length - 1) {
185
+ ctx.strokeStyle = '#E0E0E0';
186
+ ctx.lineWidth = 1;
187
+ ctx.beginPath();
188
+ ctx.moveTo(btnX + buttonWidth, btnY);
189
+ ctx.lineTo(btnX + buttonWidth, btnY + buttonHeight);
190
+ ctx.stroke();
191
+ }
192
+ }
193
+
194
+ ctx.restore();
195
+ }
196
+
197
+ /**
198
+ * Vérifie si un point est dans un rectangle
199
+ * @param {number} x - Coordonnée X
200
+ * @param {number} y - Coordonnée Y
201
+ * @param {Object} rect - Rectangle {x, y, width, height}
202
+ * @returns {boolean} True si le point est dans le rectangle
203
+ * @private
204
+ */
205
+ isPointInRect(x, y, rect) {
206
+ return x >= rect.x &&
207
+ x <= rect.x + rect.width &&
208
+ y >= rect.y &&
209
+ y <= rect.y + rect.height;
210
+ }
211
+
212
+ /**
213
+ * Vérifie si un point est dans un bouton spécifique
214
+ * @param {number} index - Index du bouton
215
+ * @param {number} x - Coordonnée X
216
+ * @param {number} y - Coordonnée Y
217
+ * @returns {boolean} True si le point est dans le bouton
218
+ * @private
219
+ */
220
+ isPointInButton(index, x, y) {
221
+ if (index < 0 || index >= this.buttonRects.length) return false;
222
+ return this.isPointInRect(x, y, this.buttonRects[index]);
223
+ }
224
+
225
+ /**
226
+ * Vérifie si un point est dans le dialog
227
+ * @param {number} x - Coordonnée X
228
+ * @param {number} y - Coordonnée Y
229
+ * @returns {boolean} True si le point est dans le dialog
230
+ * @private
231
+ */
232
+ isPointInDialog(x, y) {
233
+ const dialogX = (this.framework.width - this.dialogWidth) / 2;
234
+ const dialogY = (this.framework.height - this.dialogHeight) / 2;
235
+
236
+ return x >= dialogX &&
237
+ x <= dialogX + this.dialogWidth &&
238
+ y >= dialogY &&
239
+ y <= dialogY + this.dialogHeight;
240
+ }
241
+
242
+ /**
243
+ * Affiche le dialog
244
+ */
245
+ show() {
246
+ this.isVisible = true;
247
+ this.visible = true;
248
+
249
+ // Réajuster la taille si nécessaire
250
+ this.messageLines = this.wrapText(this.message, this.dialogWidth - 40, '16px -apple-system, sans-serif');
251
+ if (this.messageLines.length > 2) {
252
+ this.dialogHeight = 150 + (this.messageLines.length - 2) * 20;
253
+ }
254
+
255
+ // Animation d'apparition
256
+ const fadeIn = () => {
257
+ this.opacity += 0.1;
258
+ if (this.opacity < 1) {
259
+ requestAnimationFrame(fadeIn);
260
+ }
261
+ };
262
+ fadeIn();
263
+ }
264
+
265
+ /**
266
+ * Cache le dialog
267
+ */
268
+ hide() {
269
+ const fadeOut = () => {
270
+ this.opacity -= 0.1;
271
+ if (this.opacity > 0) {
272
+ requestAnimationFrame(fadeOut);
273
+ } else {
274
+ this.isVisible = false;
275
+ this.visible = false;
276
+ this.framework.remove(this);
277
+ }
278
+ };
279
+ fadeOut();
280
+ }
281
+
282
+ /**
283
+ * Gère la pression (clic)
284
+ * @param {number} x - Coordonnée X
285
+ * @param {number} y - Coordonnée Y
286
+ * @private
287
+ */
288
+ handlePress(x, y) {
289
+ // Stocker les dernières coordonnées pour le hover
290
+ this.lastX = x;
291
+ this.lastY = y;
292
+
293
+ // Vérifier si un bouton a été cliqué
294
+ for (let i = 0; i < this.buttonRects.length; i++) {
295
+ if (this.isPointInButton(i, x, y)) {
296
+ // Effet visuel du bouton pressé
297
+ this.pressedButtonIndex = i;
298
+
299
+ // Appeler le callback après un court délai pour l'effet visuel
300
+ setTimeout(() => {
301
+ if (this.onButtonClick) {
302
+ this.onButtonClick(i, this.buttons[i]);
303
+ }
304
+ this.hide();
305
+ }, 150);
306
+ return;
307
+ }
308
+ }
309
+
310
+ // Si on clique en dehors de la boîte de dialogue, fermer
311
+ if (!this.isPointInDialog(x, y)) {
312
+ this.hide();
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Gère le mouvement (hover)
318
+ * @param {number} x - Coordonnée X
319
+ * @param {number} y - Coordonnée Y
320
+ * @private
321
+ */
322
+ handleMove(x, y) {
323
+ // Stocker pour le hover effect
324
+ this.lastX = x;
325
+ this.lastY = y;
326
+
327
+ // Mettre à jour le hoveredComponent
328
+ if (this.isPointInDialog(x, y)) {
329
+ this.framework.hoveredComponent = this;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Dessine un rectangle avec coins arrondis
335
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
336
+ * @param {number} x - Position X
337
+ * @param {number} y - Position Y
338
+ * @param {number} width - Largeur
339
+ * @param {number} height - Hauteur
340
+ * @param {number} radius - Rayon des coins
341
+ * @private
342
+ */
343
+ roundRect(ctx, x, y, width, height, radius) {
344
+ ctx.beginPath();
345
+ ctx.moveTo(x + radius, y);
346
+ ctx.lineTo(x + width - radius, y);
347
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
348
+ ctx.lineTo(x + width, y + height - radius);
349
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
350
+ ctx.lineTo(x + radius, y + height);
351
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
352
+ ctx.lineTo(x, y + radius);
353
+ ctx.quadraticCurveTo(x, y, x + radius, y);
354
+ ctx.closePath();
355
+ }
356
+
357
+ /**
358
+ * Vérifie si un point est dans les limites
359
+ * @returns {boolean} True si visible (capture tous les clics)
360
+ */
361
+ isPointInside() {
362
+ // Le dialog capture tous les clics quand il est visible
363
+ return this.isVisible;
364
+ }
365
+ }
366
+
367
+ export default Dialog;
@@ -0,0 +1,125 @@
1
+ import Component from '../core/Component.js';
2
+
3
+ /**
4
+ * Séparateur visuel horizontal ou vertical
5
+ * @class
6
+ * @extends Component
7
+ * @property {string} orientation - 'horizontal' ou 'vertical'
8
+ * @property {number} thickness - Épaisseur du divider
9
+ * @property {string} color - Couleur du divider
10
+ * @property {number} margin - Marge autour du divider
11
+ * @property {string} style - 'solid', 'dashed', ou 'dotted'
12
+ * @property {boolean} hasInset - Si true, ajoute un inset pour les listes
13
+ * @property {number} insetStart - Début de l'inset (px)
14
+ * @property {string} variant - 'full', 'inset', 'middle'
15
+ */
16
+ class Divider extends Component {
17
+ /**
18
+ * Crée une instance de Divider
19
+ * @param {CanvasFramework} framework - Framework parent
20
+ * @param {Object} [options={}] - Options de configuration
21
+ * @param {string} [options.orientation='horizontal'] - Orientation
22
+ * @param {number} [options.thickness] - Épaisseur (auto selon platform)
23
+ * @param {string} [options.color] - Couleur (auto selon platform)
24
+ * @param {number} [options.margin=0] - Marge
25
+ * @param {string} [options.style='solid'] - Style de ligne
26
+ * @param {string} [options.variant='full'] - Variante
27
+ * @param {number} [options.insetStart=16] - Début de l'inset
28
+ */
29
+ constructor(framework, options = {}) {
30
+ super(framework, options);
31
+
32
+ this.orientation = options.orientation || 'horizontal';
33
+ this.margin = options.margin || 0;
34
+ this.style = options.style || 'solid';
35
+ this.variant = options.variant || 'full';
36
+ this.insetStart = options.insetStart || 16;
37
+
38
+ const platform = framework.platform;
39
+
40
+ // Styles selon la plateforme
41
+ if (platform === 'material') {
42
+ this.thickness = options.thickness || 1;
43
+ this.color = options.color || 'rgba(0, 0, 0, 0.12)';
44
+ } else {
45
+ this.thickness = options.thickness || 0.5;
46
+ this.color = options.color || 'rgba(60, 60, 67, 0.29)';
47
+ }
48
+
49
+ // Ajuster les dimensions selon l'orientation
50
+ if (this.orientation === 'horizontal') {
51
+ this.height = this.thickness + (this.margin * 2);
52
+ } else {
53
+ this.width = this.thickness + (this.margin * 2);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Dessine le divider
59
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
60
+ */
61
+ draw(ctx) {
62
+ ctx.save();
63
+
64
+ let startX = this.x;
65
+ let startY = this.y;
66
+ let endX = this.x;
67
+ let endY = this.y;
68
+
69
+ if (this.orientation === 'horizontal') {
70
+ startX = this.x;
71
+ endX = this.x + this.width;
72
+ startY = endY = this.y + this.margin + this.thickness / 2;
73
+
74
+ // Variantes
75
+ if (this.variant === 'inset') {
76
+ startX += this.insetStart;
77
+ } else if (this.variant === 'middle') {
78
+ const inset = this.width * 0.1;
79
+ startX += inset;
80
+ endX -= inset;
81
+ }
82
+ } else {
83
+ startY = this.y;
84
+ endY = this.y + this.height;
85
+ startX = endX = this.x + this.margin + this.thickness / 2;
86
+
87
+ // Variantes
88
+ if (this.variant === 'inset') {
89
+ startY += this.insetStart;
90
+ } else if (this.variant === 'middle') {
91
+ const inset = this.height * 0.1;
92
+ startY += inset;
93
+ endY -= inset;
94
+ }
95
+ }
96
+
97
+ ctx.strokeStyle = this.color;
98
+ ctx.lineWidth = this.thickness;
99
+
100
+ // Styles de ligne
101
+ if (this.style === 'dashed') {
102
+ ctx.setLineDash([8, 4]);
103
+ } else if (this.style === 'dotted') {
104
+ ctx.setLineDash([2, 4]);
105
+ }
106
+
107
+ ctx.beginPath();
108
+ ctx.moveTo(startX, startY);
109
+ ctx.lineTo(endX, endY);
110
+ ctx.stroke();
111
+
112
+ ctx.setLineDash([]);
113
+ ctx.restore();
114
+ }
115
+
116
+ /**
117
+ * Vérifie si un point est dans les limites (non cliquable)
118
+ * @returns {boolean} Toujours false
119
+ */
120
+ isPointInside() {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ export default Divider;