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,193 @@
1
+ import Component from '../core/Component.js';
2
+
3
+ /**
4
+ * Carousel / Slider d'images avec swipe horizontal et lazy load
5
+ * Compatible Material et Cupertino
6
+ * @class
7
+ * @extends Component
8
+ */
9
+ class ImageCarousel extends Component {
10
+ /**
11
+ * @param {CanvasFramework} framework - Framework parent
12
+ * @param {Object} [options={}]
13
+ * @param {Array<string>} [options.images=[]] - URLs des images
14
+ * @param {number} [options.height=200] - Hauteur du carousel
15
+ * @param {number} [options.spacing=16] - Espacement entre images
16
+ * @param {number} [options.borderRadius=8] - Coins arrondis
17
+ * @param {number} [options.pageIndicatorSize=8] - Taille des dots
18
+ * @param {string} [options.pageIndicatorColor='#6200EE'] - Couleur dot actif
19
+ * @param {Function} [options.onSwipeEnd] - Callback quand la page change
20
+ * @param {Function} [options.onImageClick] - Callback clic sur image
21
+ */
22
+ constructor(framework, options = {}) {
23
+ super(framework, options);
24
+
25
+ this.images = options.images || [];
26
+ this.currentIndex = 0;
27
+ this.scrollX = 0;
28
+ this.height = options.height || 200;
29
+ this.spacing = options.spacing || 16;
30
+ this.borderRadius = options.borderRadius || 8;
31
+
32
+ this.pageIndicatorSize = options.pageIndicatorSize || 8;
33
+ this.pageIndicatorColor = options.pageIndicatorColor || '#6200EE';
34
+
35
+ this.platform = framework.platform;
36
+ this.startX = 0;
37
+ this.isDragging = false;
38
+ this.velocity = 0;
39
+
40
+ this.onSwipeEnd = options.onSwipeEnd || null;
41
+ this.onImageClick = options.onImageClick || null;
42
+
43
+ this.loadedImages = Array(this.images.length).fill(null);
44
+
45
+ // Bind swipe
46
+ this.framework.addEventListener('touchstart', this.onTouchStart.bind(this));
47
+ this.framework.addEventListener('touchmove', this.onTouchMove.bind(this));
48
+ this.framework.addEventListener('touchend', this.onTouchEnd.bind(this));
49
+
50
+ this.animateScroll();
51
+ }
52
+
53
+ onTouchStart(e) {
54
+ const touch = e.touches[0];
55
+ this.startX = touch.clientX;
56
+ this.isDragging = true;
57
+ this.velocity = 0;
58
+ this.lastTime = performance.now();
59
+ this.lastX = touch.clientX;
60
+ }
61
+
62
+ onTouchMove(e) {
63
+ if (!this.isDragging) return;
64
+ const touch = e.touches[0];
65
+ const delta = touch.clientX - this.startX;
66
+ this.scrollX = -this.currentIndex * (this.width + this.spacing) + delta;
67
+
68
+ // calculer velocity
69
+ const now = performance.now();
70
+ const dt = now - this.lastTime;
71
+ this.velocity = (touch.clientX - this.lastX) / dt * 16; // approximation
72
+ this.lastTime = now;
73
+ this.lastX = touch.clientX;
74
+ }
75
+
76
+ onTouchEnd() {
77
+ if (!this.isDragging) return;
78
+
79
+ // momentum scroll
80
+ const momentumThreshold = this.width / 3;
81
+ let targetIndex = this.currentIndex;
82
+
83
+ if (this.velocity < -0.5) targetIndex = Math.min(this.currentIndex + 1, this.images.length - 1);
84
+ else if (this.velocity > 0.5) targetIndex = Math.max(this.currentIndex - 1, 0);
85
+ else {
86
+ const deltaIndex = Math.round(-this.scrollX / (this.width + this.spacing)) - this.currentIndex;
87
+ if (deltaIndex > 0) targetIndex = Math.min(this.currentIndex + 1, this.images.length - 1);
88
+ else if (deltaIndex < 0) targetIndex = Math.max(this.currentIndex - 1, 0);
89
+ }
90
+
91
+ this.currentIndex = targetIndex;
92
+ this.scrollX = -this.currentIndex * (this.width + this.spacing);
93
+
94
+ this.isDragging = false;
95
+ this.velocity = 0;
96
+
97
+ if (this.onSwipeEnd) this.onSwipeEnd(this.currentIndex);
98
+ }
99
+
100
+ animateScroll() {
101
+ const animate = () => {
102
+ if (!this.isDragging) {
103
+ // inertia effect
104
+ if (Math.abs(this.velocity) > 0.1) {
105
+ this.scrollX += this.velocity;
106
+ this.velocity *= 0.95; // friction
107
+
108
+ // clamp
109
+ if (this.scrollX > 0) this.scrollX = 0;
110
+ const maxScroll = -(this.images.length - 1) * (this.width + this.spacing);
111
+ if (this.scrollX < maxScroll) this.scrollX = maxScroll;
112
+ } else {
113
+ // snap to nearest
114
+ const target = -this.currentIndex * (this.width + this.spacing);
115
+ this.scrollX += (target - this.scrollX) * 0.2;
116
+ }
117
+ }
118
+
119
+ requestAnimationFrame(animate);
120
+ };
121
+ animate();
122
+ }
123
+
124
+ draw(ctx) {
125
+ ctx.save();
126
+
127
+ const startX = this.x + this.scrollX + this.spacing / 2;
128
+
129
+ for (let i = 0; i < this.images.length; i++) {
130
+ const imgX = startX + i * (this.width + this.spacing);
131
+
132
+ // charger lazy image
133
+ if (!this.loadedImages[i]) {
134
+ const img = new Image();
135
+ img.src = this.images[i];
136
+ img.onload = () => { this.loadedImages[i] = img; };
137
+ }
138
+
139
+ ctx.save();
140
+ ctx.beginPath();
141
+ this.roundRect(ctx, imgX, this.y, this.width, this.height, this.borderRadius);
142
+ ctx.clip();
143
+
144
+ if (this.loadedImages[i]) {
145
+ ctx.drawImage(this.loadedImages[i], imgX, this.y, this.width, this.height);
146
+ } else {
147
+ ctx.fillStyle = '#E0E0E0';
148
+ ctx.fillRect(imgX, this.y, this.width, this.height);
149
+ ctx.fillStyle = '#BDBDBD';
150
+ ctx.font = '20px sans-serif';
151
+ ctx.textAlign = 'center';
152
+ ctx.textBaseline = 'middle';
153
+ ctx.fillText('🖼', imgX + this.width / 2, this.y + this.height / 2);
154
+ }
155
+ ctx.restore();
156
+ }
157
+
158
+ // Pagination Material
159
+ if (this.platform === 'material') {
160
+ const dotY = this.y + this.height + 12;
161
+ const totalWidth = this.images.length * this.pageIndicatorSize * 2;
162
+ const startDotX = this.x + (this.width - totalWidth) / 2;
163
+
164
+ for (let i = 0; i < this.images.length; i++) {
165
+ ctx.beginPath();
166
+ ctx.arc(startDotX + i * this.pageIndicatorSize * 2, dotY, this.pageIndicatorSize / 2, 0, Math.PI * 2);
167
+ ctx.fillStyle = i === this.currentIndex ? this.pageIndicatorColor : '#E0E0E0';
168
+ ctx.fill();
169
+ }
170
+ }
171
+
172
+ ctx.restore();
173
+ }
174
+
175
+ roundRect(ctx, x, y, width, height, radius) {
176
+ ctx.moveTo(x + radius, y);
177
+ ctx.lineTo(x + width - radius, y);
178
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
179
+ ctx.lineTo(x + width, y + height - radius);
180
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
181
+ ctx.lineTo(x + radius, y + height);
182
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
183
+ ctx.lineTo(x, y + radius);
184
+ ctx.quadraticCurveTo(x, y, x + radius, y);
185
+ }
186
+
187
+ isPointInside(x, y) {
188
+ return x >= this.x && x <= this.x + this.width &&
189
+ y >= this.y && y <= this.y + this.height;
190
+ }
191
+ }
192
+
193
+ export default ImageCarousel;
@@ -0,0 +1,223 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Composant d'image
4
+ * @class
5
+ * @extends Component
6
+ * @property {string} src - URL de l'image
7
+ * @property {string} fit - Mode d'ajustement ('cover', 'contain', 'fill', 'none')
8
+ * @property {number} borderRadius - Rayon des coins
9
+ * @property {string} placeholder - Couleur de placeholder
10
+ * @property {boolean} loaded - Image chargée
11
+ * @property {boolean} loading - En cours de chargement
12
+ * @property {boolean} error - Erreur de chargement
13
+ * @property {HTMLImageElement} img - Élément image HTML
14
+ */
15
+ class ImageComponent extends Component {
16
+ /**
17
+ * Crée une instance de ImageComponent
18
+ * @param {CanvasFramework} framework - Framework parent
19
+ * @param {Object} [options={}] - Options de configuration
20
+ * @param {string} [options.src=''] - URL de l'image
21
+ * @param {string} [options.fit='cover'] - Mode d'ajustement
22
+ * @param {number} [options.borderRadius=0] - Rayon des coins
23
+ * @param {string} [options.placeholder='#E0E0E0'] - Couleur de placeholder
24
+ */
25
+ constructor(framework, options = {}) {
26
+ super(framework, options);
27
+ this.src = options.src || '';
28
+ this.fit = options.fit || 'cover'; // cover, contain, fill, none
29
+ this.borderRadius = options.borderRadius || 0;
30
+ this.placeholder = options.placeholder || '#E0E0E0';
31
+ this.loaded = false;
32
+ this.loading = false;
33
+ this.error = false;
34
+
35
+ // Créer l'élément image
36
+ this.img = null;
37
+ if (this.src) {
38
+ this.loadImage();
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Charge l'image
44
+ * @private
45
+ */
46
+ loadImage() {
47
+ if (this.loading) return;
48
+
49
+ this.loading = true;
50
+ this.img = new Image();
51
+ this.img.crossOrigin = 'anonymous';
52
+
53
+ this.img.onload = () => {
54
+ this.loaded = true;
55
+ this.loading = false;
56
+ this.error = false;
57
+ };
58
+
59
+ this.img.onerror = () => {
60
+ this.loaded = false;
61
+ this.loading = false;
62
+ this.error = true;
63
+ };
64
+
65
+ this.img.src = this.src;
66
+ }
67
+
68
+ /**
69
+ * Change l'URL de l'image
70
+ * @param {string} newSrc - Nouvelle URL
71
+ */
72
+ setSrc(newSrc) {
73
+ this.src = newSrc;
74
+ this.loaded = false;
75
+ this.error = false;
76
+ this.loadImage();
77
+ }
78
+
79
+ /**
80
+ * Dessine l'image
81
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
82
+ */
83
+ draw(ctx) {
84
+ ctx.save();
85
+
86
+ // Clipping avec borderRadius
87
+ if (this.borderRadius > 0) {
88
+ ctx.beginPath();
89
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
90
+ ctx.clip();
91
+ }
92
+
93
+ if (this.loaded && this.img) {
94
+ // Dessiner l'image selon le mode fit
95
+ const imgRatio = this.img.width / this.img.height;
96
+ const boxRatio = this.width / this.height;
97
+
98
+ let drawWidth, drawHeight, drawX, drawY;
99
+
100
+ switch (this.fit) {
101
+ case 'cover':
102
+ if (imgRatio > boxRatio) {
103
+ drawHeight = this.height;
104
+ drawWidth = drawHeight * imgRatio;
105
+ drawX = this.x - (drawWidth - this.width) / 2;
106
+ drawY = this.y;
107
+ } else {
108
+ drawWidth = this.width;
109
+ drawHeight = drawWidth / imgRatio;
110
+ drawX = this.x;
111
+ drawY = this.y - (drawHeight - this.height) / 2;
112
+ }
113
+ break;
114
+
115
+ case 'contain':
116
+ if (imgRatio > boxRatio) {
117
+ drawWidth = this.width;
118
+ drawHeight = drawWidth / imgRatio;
119
+ drawX = this.x;
120
+ drawY = this.y + (this.height - drawHeight) / 2;
121
+ } else {
122
+ drawHeight = this.height;
123
+ drawWidth = drawHeight * imgRatio;
124
+ drawX = this.x + (this.width - drawWidth) / 2;
125
+ drawY = this.y;
126
+ }
127
+ break;
128
+
129
+ case 'fill':
130
+ drawX = this.x;
131
+ drawY = this.y;
132
+ drawWidth = this.width;
133
+ drawHeight = this.height;
134
+ break;
135
+
136
+ case 'none':
137
+ drawX = this.x + (this.width - this.img.width) / 2;
138
+ drawY = this.y + (this.height - this.img.height) / 2;
139
+ drawWidth = this.img.width;
140
+ drawHeight = this.img.height;
141
+ break;
142
+
143
+ default:
144
+ drawX = this.x;
145
+ drawY = this.y;
146
+ drawWidth = this.width;
147
+ drawHeight = this.height;
148
+ }
149
+
150
+ ctx.drawImage(this.img, drawX, drawY, drawWidth, drawHeight);
151
+
152
+ } else if (this.loading) {
153
+ // Placeholder avec spinner
154
+ ctx.fillStyle = this.placeholder;
155
+ ctx.fillRect(this.x, this.y, this.width, this.height);
156
+
157
+ // Petit spinner au centre
158
+ const centerX = this.x + this.width / 2;
159
+ const centerY = this.y + this.height / 2;
160
+ const spinnerSize = Math.min(40, this.width / 3, this.height / 3);
161
+
162
+ ctx.strokeStyle = '#999999';
163
+ ctx.lineWidth = 3;
164
+ ctx.lineCap = 'round';
165
+ const rotation = (Date.now() / 10) % 360 * (Math.PI / 180);
166
+ ctx.beginPath();
167
+ ctx.arc(centerX, centerY, spinnerSize / 2, rotation, rotation + Math.PI * 1.5);
168
+ ctx.stroke();
169
+
170
+ } else if (this.error) {
171
+ // Placeholder avec icône d'erreur
172
+ ctx.fillStyle = this.placeholder;
173
+ ctx.fillRect(this.x, this.y, this.width, this.height);
174
+
175
+ ctx.fillStyle = '#999999';
176
+ ctx.font = '48px sans-serif';
177
+ ctx.textAlign = 'center';
178
+ ctx.textBaseline = 'middle';
179
+ ctx.fillText('⚠️', this.x + this.width / 2, this.y + this.height / 2);
180
+
181
+ } else {
182
+ // Placeholder simple
183
+ ctx.fillStyle = this.placeholder;
184
+ ctx.fillRect(this.x, this.y, this.width, this.height);
185
+ }
186
+
187
+ ctx.restore();
188
+ }
189
+
190
+ /**
191
+ * Dessine un rectangle avec coins arrondis
192
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
193
+ * @param {number} x - Position X
194
+ * @param {number} y - Position Y
195
+ * @param {number} width - Largeur
196
+ * @param {number} height - Hauteur
197
+ * @param {number} radius - Rayon des coins
198
+ * @private
199
+ */
200
+ roundRect(ctx, x, y, width, height, radius) {
201
+ ctx.beginPath();
202
+ ctx.moveTo(x + radius, y);
203
+ ctx.lineTo(x + width - radius, y);
204
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
205
+ ctx.lineTo(x + width, y + height - radius);
206
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
207
+ ctx.lineTo(x + radius, y + height);
208
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
209
+ ctx.lineTo(x, y + radius);
210
+ ctx.quadraticCurveTo(x, y, x + radius, y);
211
+ ctx.closePath();
212
+ }
213
+
214
+ /**
215
+ * Vérifie si un point est dans les limites
216
+ * @returns {boolean} False (non cliquable par défaut)
217
+ */
218
+ isPointInside() {
219
+ return false; // Non cliquable par défaut
220
+ }
221
+ }
222
+
223
+ export default ImageComponent;