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,143 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Spinner de chargement circulaire
4
+ * @class
5
+ * @extends Component
6
+ * @property {number} size - Taille du spinner
7
+ * @property {boolean} indeterminate - Mode indéterminé
8
+ * @property {number} progress - Progression (0-100)
9
+ * @property {string} platform - Plateforme
10
+ * @property {string} color - Couleur
11
+ * @property {number} lineWidth - Épaisseur de la ligne
12
+ * @property {number} rotation - Rotation actuelle
13
+ * @property {number} animationSpeed - Vitesse d'animation
14
+ */
15
+ class CircularProgress extends Component {
16
+ /**
17
+ * Crée une instance de CircularProgress
18
+ * @param {CanvasFramework} framework - Framework parent
19
+ * @param {Object} [options={}] - Options de configuration
20
+ * @param {number} [options.size=40] - Taille
21
+ * @param {boolean} [options.indeterminate=true] - Mode indéterminé
22
+ * @param {number} [options.progress=0] - Progression (0-100)
23
+ * @param {string} [options.color] - Couleur (auto selon platform)
24
+ * @param {number} [options.lineWidth=4] - Épaisseur
25
+ * @param {number} [options.animationSpeed=0.05] - Vitesse d'animation
26
+ */
27
+ constructor(framework, options = {}) {
28
+ super(framework, options);
29
+ this.size = options.size || 40;
30
+ this.indeterminate = options.indeterminate !== false;
31
+ this.progress = options.progress || 0; // 0-100
32
+ this.platform = framework.platform;
33
+ this.color = options.color || (framework.platform === 'material' ? '#6200EE' : '#007AFF');
34
+ this.lineWidth = options.lineWidth || 4;
35
+ this.rotation = 0;
36
+ this.animationSpeed = options.animationSpeed || 0.05;
37
+
38
+ this.width = this.size;
39
+ this.height = this.size;
40
+
41
+ // Démarrer l'animation pour indeterminate
42
+ if (this.indeterminate) {
43
+ this.startAnimation();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Démarre l'animation du spinner
49
+ * @private
50
+ */
51
+ startAnimation() {
52
+ const animate = () => {
53
+ if (!this.visible || !this.indeterminate) return;
54
+
55
+ this.rotation += this.animationSpeed;
56
+ if (this.rotation > Math.PI * 2) {
57
+ this.rotation = 0;
58
+ }
59
+
60
+ requestAnimationFrame(animate);
61
+ };
62
+ animate();
63
+ }
64
+
65
+ /**
66
+ * Dessine le spinner
67
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
68
+ */
69
+ draw(ctx) {
70
+ ctx.save();
71
+
72
+ const centerX = this.x + this.size / 2;
73
+ const centerY = this.y + this.size / 2;
74
+ const radius = (this.size - this.lineWidth) / 2;
75
+
76
+ if (this.indeterminate) {
77
+ // Spinner qui tourne
78
+ ctx.translate(centerX, centerY);
79
+ ctx.rotate(this.rotation);
80
+ ctx.translate(-centerX, -centerY);
81
+
82
+ // Cercle de base (track)
83
+ ctx.strokeStyle = this.platform === 'material' ? '#E0E0E0' : '#E5E5EA';
84
+ ctx.lineWidth = this.lineWidth;
85
+ ctx.lineCap = 'round';
86
+ ctx.beginPath();
87
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
88
+ ctx.stroke();
89
+
90
+ // Arc animé
91
+ ctx.strokeStyle = this.color;
92
+ ctx.beginPath();
93
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 1.5);
94
+ ctx.stroke();
95
+
96
+ } else {
97
+ // Progress circulaire déterminé
98
+ // Track
99
+ ctx.strokeStyle = this.platform === 'material' ? '#E0E0E0' : '#E5E5EA';
100
+ ctx.lineWidth = this.lineWidth;
101
+ ctx.beginPath();
102
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
103
+ ctx.stroke();
104
+
105
+ // Progress
106
+ const angle = (this.progress / 100) * Math.PI * 2;
107
+ ctx.strokeStyle = this.color;
108
+ ctx.lineCap = 'round';
109
+ ctx.beginPath();
110
+ ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + angle);
111
+ ctx.stroke();
112
+
113
+ // Pourcentage au centre (optionnel)
114
+ if (this.progress > 0) {
115
+ ctx.fillStyle = this.color;
116
+ ctx.font = `bold ${this.size / 3}px -apple-system, sans-serif`;
117
+ ctx.textAlign = 'center';
118
+ ctx.textBaseline = 'middle';
119
+ ctx.fillText(`${Math.round(this.progress)}%`, centerX, centerY);
120
+ }
121
+ }
122
+
123
+ ctx.restore();
124
+ }
125
+
126
+ /**
127
+ * Définit la progression
128
+ * @param {number} value - Valeur de progression (0-100)
129
+ */
130
+ setProgress(value) {
131
+ this.progress = Math.max(0, Math.min(100, value));
132
+ }
133
+
134
+ /**
135
+ * Vérifie si un point est dans les limites
136
+ * @returns {boolean} False (non cliquable)
137
+ */
138
+ isPointInside() {
139
+ return false; // Non cliquable
140
+ }
141
+ }
142
+
143
+ export default CircularProgress;
@@ -0,0 +1,116 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Menu contextuel
4
+ * @class
5
+ * @extends Component
6
+ * @property {string[]} options - Options du menu
7
+ * @property {Function} onSelect - Callback à la sélection
8
+ * @property {number} itemHeight - Hauteur d'un item
9
+ * @property {boolean} isOpen - État ouvert
10
+ * @property {number} hoveredIndex - Index survolé
11
+ */
12
+ class ContextMenu extends Component {
13
+ /**
14
+ * Crée une instance de ContextMenu
15
+ * @param {CanvasFramework} framework - Framework parent
16
+ * @param {Object} [options={}] - Options de configuration
17
+ * @param {string[]} [options.options=[]] - Options du menu
18
+ * @param {Function} [options.onSelect] - Callback à la sélection
19
+ */
20
+ constructor(framework, options = {}) {
21
+ super(framework, options);
22
+ this.options = options.options || [];
23
+ this.onSelect = options.onSelect;
24
+ this.itemHeight = 48;
25
+ this.height = this.options.length * this.itemHeight;
26
+ this.hoveredIndex = -1;
27
+ this.isOpen = true;
28
+
29
+ // Définir onClick pour le menu
30
+ this.onClick = this.handleClick.bind(this);
31
+ }
32
+
33
+ /**
34
+ * Dessine le menu contextuel
35
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
36
+ */
37
+ draw(ctx) {
38
+ if (!this.isOpen) return;
39
+
40
+ ctx.save();
41
+
42
+ // Background
43
+ ctx.fillStyle = '#FFFFFF';
44
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
45
+ ctx.shadowBlur = 10;
46
+ ctx.fillRect(this.x, this.y, this.width, this.height);
47
+
48
+ ctx.shadowColor = 'transparent';
49
+
50
+ // Options
51
+ for (let i = 0; i < this.options.length; i++) {
52
+ const itemY = this.y + i * this.itemHeight;
53
+
54
+ if (this.hoveredIndex === i) {
55
+ ctx.fillStyle = '#F5F5F5';
56
+ ctx.fillRect(this.x, itemY, this.width, this.itemHeight);
57
+ }
58
+
59
+ ctx.fillStyle = '#000000';
60
+ ctx.font = '16px -apple-system, sans-serif';
61
+ ctx.textAlign = 'left';
62
+ ctx.textBaseline = 'middle';
63
+ ctx.fillText(this.options[i], this.x + 16, itemY + this.itemHeight / 2);
64
+
65
+ // Divider
66
+ if (i < this.options.length - 1) {
67
+ ctx.strokeStyle = '#E0E0E0';
68
+ ctx.lineWidth = 1;
69
+ ctx.beginPath();
70
+ ctx.moveTo(this.x, itemY + this.itemHeight);
71
+ ctx.lineTo(this.x + this.width, itemY + this.itemHeight);
72
+ ctx.stroke();
73
+ }
74
+ }
75
+
76
+ ctx.restore();
77
+ }
78
+
79
+ /**
80
+ * Vérifie si un point est dans les limites
81
+ * @param {number} x - Coordonnée X
82
+ * @param {number} y - Coordonnée Y
83
+ * @returns {boolean} True si le point est dans le menu
84
+ */
85
+ isPointInside(x, y) {
86
+ const adjustedY = y - this.framework.scrollOffset;
87
+ const inBounds = super.isPointInside(x, adjustedY);
88
+ if (inBounds) {
89
+ this.hoveredIndex = Math.floor((adjustedY - this.y) / this.itemHeight);
90
+ } else {
91
+ this.hoveredIndex = -1;
92
+ }
93
+ return inBounds;
94
+ }
95
+
96
+ /**
97
+ * Gère le clic sur le menu
98
+ * @private
99
+ */
100
+ handleClick() {
101
+ if (this.hoveredIndex >= 0 && this.onSelect) {
102
+ this.onSelect(this.hoveredIndex);
103
+ this.close();
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Ferme le menu contextuel
109
+ */
110
+ close() {
111
+ this.isOpen = false;
112
+ this.framework.remove(this);
113
+ }
114
+ }
115
+
116
+ export default ContextMenu;
@@ -0,0 +1,257 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Sélecteur de date (wrapper)
4
+ * @class
5
+ * @extends Component
6
+ * @property {Date} selectedDate - Date sélectionnée
7
+ * @property {Date|null} minDate - Date minimum
8
+ * @property {Date|null} maxDate - Date maximum
9
+ * @property {Function} onChange - Callback au changement
10
+ * @property {string} platform - Plateforme
11
+ * @property {string} label - Label
12
+ * @property {Modal|null} pickerModal - Modal iOS
13
+ */
14
+ class DatePicker extends Component {
15
+ /**
16
+ * Crée une instance de DatePicker
17
+ * @param {CanvasFramework} framework - Framework parent
18
+ * @param {Object} [options={}] - Options de configuration
19
+ * @param {Date} [options.selectedDate=new Date()] - Date initiale
20
+ * @param {Date} [options.minDate] - Date minimum
21
+ * @param {Date} [options.maxDate] - Date maximum
22
+ * @param {Function} [options.onChange] - Callback au changement
23
+ * @param {string} [options.label='Sélectionner une date'] - Label
24
+ */
25
+ constructor(framework, options = {}) {
26
+ super(framework, options);
27
+ this.selectedDate = options.selectedDate || new Date();
28
+ this.minDate = options.minDate || null;
29
+ this.maxDate = options.maxDate || null;
30
+ this.onChange = options.onChange;
31
+ this.platform = framework.platform;
32
+ this.label = options.label || 'Sélectionner une date';
33
+
34
+ // Pour iOS: créer un bouton qui ouvre le picker
35
+ if (this.platform === 'cupertino') {
36
+ this.width = options.width || framework.width - 40;
37
+ this.height = 50;
38
+ this.pickerModal = null;
39
+ this.onClick = this.openPicker.bind(this);
40
+ } else {
41
+ // Pour Android: afficher directement le modal
42
+ this.width = options.width || Math.min(320, framework.width - 40);
43
+ this.height = 50;
44
+ this.onClick = this.openPicker.bind(this);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Ouvre le sélecteur de date
50
+ * @private
51
+ */
52
+ openPicker() {
53
+ if (this.platform === 'cupertino') {
54
+ this.openIOSPicker();
55
+ } else {
56
+ this.openAndroidDialog();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Ouvre le sélecteur iOS
62
+ * @private
63
+ */
64
+ openIOSPicker() {
65
+ // Créer un modal avec le picker iOS
66
+ const modal = new Modal(this.framework, {
67
+ title: '',
68
+ width: this.framework.width,
69
+ height: 320,
70
+ showCloseButton: false,
71
+ closeOnOverlayClick: true,
72
+ bgColor: '#F9F9F9'
73
+ });
74
+
75
+ // Créer le picker de date iOS style
76
+ const picker = new IOSDatePickerWheel(this.framework, {
77
+ x: 0,
78
+ y: 20,
79
+ width: this.framework.width - 40,
80
+ selectedDate: this.selectedDate,
81
+ onChange: (date) => {
82
+ this.selectedDate = date;
83
+ if (this.onChange) this.onChange(date);
84
+ }
85
+ });
86
+ modal.add(picker);
87
+
88
+ // Bouton Valider
89
+ const btnOK = new Button(this.framework, {
90
+ x: (this.framework.width - 200) / 2,
91
+ y: 230,
92
+ width: 200,
93
+ height: 44,
94
+ text: 'Valider',
95
+ onClick: () => {
96
+ modal.hide();
97
+ }
98
+ });
99
+ modal.add(btnOK);
100
+
101
+ this.framework.add(modal);
102
+ modal.show();
103
+ this.pickerModal = modal;
104
+ }
105
+
106
+ /**
107
+ * Ouvre le dialog Android
108
+ * @private
109
+ */
110
+ openAndroidDialog() {
111
+ // Créer un dialog Material Design avec calendrier
112
+ const dialog = new AndroidDatePickerDialog(this.framework, {
113
+ selectedDate: this.selectedDate,
114
+ onChange: (date) => {
115
+ this.selectedDate = date;
116
+ if (this.onChange) this.onChange(date);
117
+ }
118
+ });
119
+
120
+ this.framework.add(dialog);
121
+ dialog.show();
122
+ }
123
+
124
+ /**
125
+ * Dessine le sélecteur de date
126
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
127
+ */
128
+ draw(ctx) {
129
+ if (this.platform === 'cupertino') {
130
+ // Dessiner un bouton iOS
131
+ ctx.save();
132
+
133
+ // Background
134
+ ctx.fillStyle = '#FFFFFF';
135
+ ctx.beginPath();
136
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, 10);
137
+ ctx.fill();
138
+
139
+ // Bordure
140
+ ctx.strokeStyle = '#C7C7CC';
141
+ ctx.lineWidth = 1;
142
+ ctx.stroke();
143
+
144
+ // Label
145
+ ctx.fillStyle = '#8E8E93';
146
+ ctx.font = '14px -apple-system, sans-serif';
147
+ ctx.textAlign = 'left';
148
+ ctx.textBaseline = 'middle';
149
+ ctx.fillText(this.label, this.x + 15, this.y + this.height / 2 - 10);
150
+
151
+ // Date sélectionnée
152
+ ctx.fillStyle = '#000000';
153
+ ctx.font = '16px -apple-system, sans-serif';
154
+ ctx.fillText(this.formatDate(this.selectedDate), this.x + 15, this.y + this.height / 2 + 10);
155
+
156
+ // Chevron
157
+ ctx.strokeStyle = '#C7C7CC';
158
+ ctx.lineWidth = 2;
159
+ ctx.lineCap = 'round';
160
+ ctx.beginPath();
161
+ ctx.moveTo(this.x + this.width - 25, this.y + this.height / 2 - 5);
162
+ ctx.lineTo(this.x + this.width - 20, this.y + this.height / 2);
163
+ ctx.lineTo(this.x + this.width - 15, this.y + this.height / 2 - 5);
164
+ ctx.stroke();
165
+
166
+ ctx.restore();
167
+ } else {
168
+ // Dessiner un bouton Material
169
+ ctx.save();
170
+
171
+ // Background
172
+ ctx.fillStyle = this.pressed ? '#F5F5F5' : '#FFFFFF';
173
+ ctx.fillRect(this.x, this.y, this.width, this.height);
174
+
175
+ // Bordure
176
+ ctx.strokeStyle = '#E0E0E0';
177
+ ctx.lineWidth = 1;
178
+ ctx.strokeRect(this.x, this.y, this.width, this.height);
179
+
180
+ // Icône calendrier
181
+ ctx.strokeStyle = '#666666';
182
+ ctx.lineWidth = 2;
183
+ ctx.strokeRect(this.x + 15, this.y + 15, 20, 20);
184
+ ctx.beginPath();
185
+ ctx.moveTo(this.x + 18, this.y + 12);
186
+ ctx.lineTo(this.x + 18, this.y + 18);
187
+ ctx.moveTo(this.x + 32, this.y + 12);
188
+ ctx.lineTo(this.x + 32, this.y + 18);
189
+ ctx.stroke();
190
+
191
+ // Label
192
+ ctx.fillStyle = '#666666';
193
+ ctx.font = '12px Roboto, sans-serif';
194
+ ctx.textAlign = 'left';
195
+ ctx.textBaseline = 'top';
196
+ ctx.fillText(this.label, this.x + 45, this.y + 8);
197
+
198
+ // Date
199
+ ctx.fillStyle = '#000000';
200
+ ctx.font = '16px Roboto, sans-serif';
201
+ ctx.textBaseline = 'bottom';
202
+ ctx.fillText(this.formatDate(this.selectedDate), this.x + 45, this.y + this.height - 8);
203
+
204
+ ctx.restore();
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Formate une date
210
+ * @param {Date} date - Date à formater
211
+ * @returns {string} Date formatée
212
+ * @private
213
+ */
214
+ formatDate(date) {
215
+ const day = date.getDate().toString().padStart(2, '0');
216
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
217
+ const year = date.getFullYear();
218
+ return `${day}/${month}/${year}`;
219
+ }
220
+
221
+ /**
222
+ * Dessine un rectangle avec coins arrondis
223
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
224
+ * @param {number} x - Position X
225
+ * @param {number} y - Position Y
226
+ * @param {number} width - Largeur
227
+ * @param {number} height - Hauteur
228
+ * @param {number} radius - Rayon des coins
229
+ * @private
230
+ */
231
+ roundRect(ctx, x, y, width, height, radius) {
232
+ ctx.beginPath();
233
+ ctx.moveTo(x + radius, y);
234
+ ctx.lineTo(x + width - radius, y);
235
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
236
+ ctx.lineTo(x + width, y + height - radius);
237
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
238
+ ctx.lineTo(x + radius, y + height);
239
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
240
+ ctx.lineTo(x, y + radius);
241
+ ctx.quadraticCurveTo(x, y, x + radius, y);
242
+ ctx.closePath();
243
+ }
244
+
245
+ /**
246
+ * Vérifie si un point est dans les limites
247
+ * @param {number} x - Coordonnée X
248
+ * @param {number} y - Coordonnée Y
249
+ * @returns {boolean} True si le point est dans le sélecteur
250
+ */
251
+ isPointInside(x, y) {
252
+ return x >= this.x && x <= this.x + this.width &&
253
+ y >= this.y && y <= this.y + this.height;
254
+ }
255
+ }
256
+
257
+ export default DatePicker;