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,281 @@
1
+ import Component from '../core/Component.js';
2
+ /**
3
+ * Stepper (incrémenteur/décrémenteur)
4
+ * @class
5
+ * @extends Component
6
+ * @property {number} value - Valeur
7
+ * @property {number} min - Minimum
8
+ * @property {number} max - Maximum
9
+ * @property {number} step - Pas d'incrémentation
10
+ * @property {string} platform - Plateforme
11
+ * @property {Function} onChange - Callback au changement
12
+ * @property {number} buttonWidth - Largeur des boutons
13
+ * @property {boolean} decrementPressed - Bouton décrémenter pressé
14
+ * @property {boolean} incrementPressed - Bouton incrémenter pressé
15
+ */
16
+ class Stepper extends Component {
17
+ /**
18
+ * Crée une instance de Stepper
19
+ * @param {CanvasFramework} framework - Framework parent
20
+ * @param {Object} [options={}] - Options de configuration
21
+ * @param {number} [options.value=0] - Valeur initiale
22
+ * @param {number} [options.min=0] - Minimum
23
+ * @param {number} [options.max=100] - Maximum
24
+ * @param {number} [options.step=1] - Pas
25
+ * @param {Function} [options.onChange] - Callback au changement
26
+ * @param {number} [options.width=120] - Largeur
27
+ * @param {number} [options.height=40] - Hauteur
28
+ */
29
+ constructor(framework, options = {}) {
30
+ super(framework, options);
31
+ this.value = options.value || 0;
32
+ this.min = options.min !== undefined ? options.min : 0;
33
+ this.max = options.max !== undefined ? options.max : 100;
34
+ this.step = options.step || 1;
35
+ this.platform = framework.platform;
36
+ this.onChange = options.onChange;
37
+ this.width = options.width || 120;
38
+ this.height = options.height || 40;
39
+ this.buttonWidth = this.height;
40
+
41
+ this.decrementPressed = false;
42
+ this.incrementPressed = false;
43
+
44
+ this.onPress = this.handlePress.bind(this);
45
+ }
46
+
47
+ /**
48
+ * Incrémente la valeur
49
+ */
50
+ increment() {
51
+ if (this.value + this.step <= this.max) {
52
+ this.value += this.step;
53
+ if (this.onChange) this.onChange(this.value);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Décrémente la valeur
59
+ */
60
+ decrement() {
61
+ if (this.value - this.step >= this.min) {
62
+ this.value -= this.step;
63
+ if (this.onChange) this.onChange(this.value);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Définit la valeur
69
+ * @param {number} value - Nouvelle valeur
70
+ */
71
+ setValue(value) {
72
+ this.value = Math.max(this.min, Math.min(this.max, value));
73
+ if (this.onChange) this.onChange(this.value);
74
+ }
75
+
76
+ /**
77
+ * Dessine le stepper
78
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
79
+ */
80
+ draw(ctx) {
81
+ ctx.save();
82
+
83
+ if (this.platform === 'material') {
84
+ // Material Design Stepper
85
+
86
+ // Bouton décrement (-)
87
+ const canDecrement = this.value > this.min;
88
+ ctx.fillStyle = this.decrementPressed ? '#E0E0E0' : '#F5F5F5';
89
+ ctx.strokeStyle = canDecrement ? '#6200EE' : '#CCCCCC';
90
+ ctx.lineWidth = 2;
91
+
92
+ ctx.beginPath();
93
+ this.roundRect(ctx, this.x, this.y, this.buttonWidth, this.height, 4);
94
+ ctx.fill();
95
+ ctx.stroke();
96
+
97
+ // Icône -
98
+ ctx.strokeStyle = canDecrement ? '#6200EE' : '#CCCCCC';
99
+ ctx.lineWidth = 2;
100
+ ctx.lineCap = 'round';
101
+ ctx.beginPath();
102
+ ctx.moveTo(this.x + this.buttonWidth / 2 - 8, this.y + this.height / 2);
103
+ ctx.lineTo(this.x + this.buttonWidth / 2 + 8, this.y + this.height / 2);
104
+ ctx.stroke();
105
+
106
+ // Zone centrale (valeur)
107
+ ctx.fillStyle = '#FFFFFF';
108
+ ctx.fillRect(this.x + this.buttonWidth, this.y,
109
+ this.width - (this.buttonWidth * 2), this.height);
110
+
111
+ ctx.strokeStyle = '#E0E0E0';
112
+ ctx.lineWidth = 1;
113
+ ctx.strokeRect(this.x + this.buttonWidth, this.y,
114
+ this.width - (this.buttonWidth * 2), this.height);
115
+
116
+ // Valeur
117
+ ctx.fillStyle = '#000000';
118
+ ctx.font = 'bold 16px Roboto, sans-serif';
119
+ ctx.textAlign = 'center';
120
+ ctx.textBaseline = 'middle';
121
+ ctx.fillText(this.value.toString(), this.x + this.width / 2, this.y + this.height / 2);
122
+
123
+ // Bouton increment (+)
124
+ const canIncrement = this.value < this.max;
125
+ ctx.fillStyle = this.incrementPressed ? '#E0E0E0' : '#F5F5F5';
126
+ ctx.strokeStyle = canIncrement ? '#6200EE' : '#CCCCCC';
127
+ ctx.lineWidth = 2;
128
+
129
+ ctx.beginPath();
130
+ this.roundRect(ctx, this.x + this.width - this.buttonWidth, this.y,
131
+ this.buttonWidth, this.height, 4);
132
+ ctx.fill();
133
+ ctx.stroke();
134
+
135
+ // Icône +
136
+ ctx.strokeStyle = canIncrement ? '#6200EE' : '#CCCCCC';
137
+ ctx.lineWidth = 2;
138
+ ctx.lineCap = 'round';
139
+ const plusX = this.x + this.width - this.buttonWidth / 2;
140
+ const plusY = this.y + this.height / 2;
141
+
142
+ ctx.beginPath();
143
+ ctx.moveTo(plusX - 8, plusY);
144
+ ctx.lineTo(plusX + 8, plusY);
145
+ ctx.stroke();
146
+
147
+ ctx.beginPath();
148
+ ctx.moveTo(plusX, plusY - 8);
149
+ ctx.lineTo(plusX, plusY + 8);
150
+ ctx.stroke();
151
+
152
+ } else {
153
+ // Cupertino (iOS) Stepper
154
+
155
+ // Container
156
+ ctx.strokeStyle = '#C7C7CC';
157
+ ctx.lineWidth = 1;
158
+ ctx.beginPath();
159
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, this.height / 2);
160
+ ctx.stroke();
161
+
162
+ // Divider central
163
+ ctx.beginPath();
164
+ ctx.moveTo(this.x + this.width / 2, this.y);
165
+ ctx.lineTo(this.x + this.width / 2, this.y + this.height);
166
+ ctx.stroke();
167
+
168
+ // Bouton décrement (-)
169
+ const canDecrement = this.value > this.min;
170
+ if (this.decrementPressed) {
171
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
172
+ ctx.beginPath();
173
+ ctx.arc(this.x + this.width / 4, this.y + this.height / 2,
174
+ this.height / 2 - 2, Math.PI / 2, Math.PI * 1.5);
175
+ ctx.lineTo(this.x + this.width / 2, this.y);
176
+ ctx.lineTo(this.x + this.width / 2, this.y + this.height);
177
+ ctx.closePath();
178
+ ctx.fill();
179
+ }
180
+
181
+ ctx.strokeStyle = canDecrement ? '#007AFF' : '#C7C7CC';
182
+ ctx.lineWidth = 2;
183
+ ctx.lineCap = 'round';
184
+ ctx.beginPath();
185
+ ctx.moveTo(this.x + this.width / 4 - 10, this.y + this.height / 2);
186
+ ctx.lineTo(this.x + this.width / 4 + 10, this.y + this.height / 2);
187
+ ctx.stroke();
188
+
189
+ // Bouton increment (+)
190
+ const canIncrement = this.value < this.max;
191
+ if (this.incrementPressed) {
192
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
193
+ ctx.beginPath();
194
+ ctx.arc(this.x + (this.width * 3 / 4), this.y + this.height / 2,
195
+ this.height / 2 - 2, Math.PI * 1.5, Math.PI / 2);
196
+ ctx.lineTo(this.x + this.width / 2, this.y + this.height);
197
+ ctx.lineTo(this.x + this.width / 2, this.y);
198
+ ctx.closePath();
199
+ ctx.fill();
200
+ }
201
+
202
+ ctx.strokeStyle = canIncrement ? '#007AFF' : '#C7C7CC';
203
+ ctx.lineWidth = 2;
204
+ ctx.lineCap = 'round';
205
+ const plusX = this.x + (this.width * 3 / 4);
206
+ const plusY = this.y + this.height / 2;
207
+
208
+ ctx.beginPath();
209
+ ctx.moveTo(plusX - 10, plusY);
210
+ ctx.lineTo(plusX + 10, plusY);
211
+ ctx.stroke();
212
+
213
+ ctx.beginPath();
214
+ ctx.moveTo(plusX, plusY - 10);
215
+ ctx.lineTo(plusX, plusY + 10);
216
+ ctx.stroke();
217
+ }
218
+
219
+ ctx.restore();
220
+ }
221
+
222
+ /**
223
+ * Gère la pression (clic)
224
+ * @param {number} x - Coordonnée X
225
+ * @param {number} y - Coordonnée Y
226
+ * @private
227
+ */
228
+ handlePress(x, y) {
229
+ const adjustedY = y - this.framework.scrollOffset;
230
+
231
+ // Bouton décrement (gauche)
232
+ if (x >= this.x && x <= this.x + this.buttonWidth) {
233
+ this.decrementPressed = true;
234
+ this.decrement();
235
+ setTimeout(() => { this.decrementPressed = false; }, 150);
236
+ }
237
+ // Bouton increment (droite)
238
+ else if (x >= this.x + this.width - this.buttonWidth && x <= this.x + this.width) {
239
+ this.incrementPressed = true;
240
+ this.increment();
241
+ setTimeout(() => { this.incrementPressed = false; }, 150);
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Dessine un rectangle avec coins arrondis
247
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
248
+ * @param {number} x - Position X
249
+ * @param {number} y - Position Y
250
+ * @param {number} width - Largeur
251
+ * @param {number} height - Hauteur
252
+ * @param {number} radius - Rayon des coins
253
+ * @private
254
+ */
255
+ roundRect(ctx, x, y, width, height, radius) {
256
+ ctx.beginPath();
257
+ ctx.moveTo(x + radius, y);
258
+ ctx.lineTo(x + width - radius, y);
259
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
260
+ ctx.lineTo(x + width, y + height - radius);
261
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
262
+ ctx.lineTo(x + radius, y + height);
263
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
264
+ ctx.lineTo(x, y + radius);
265
+ ctx.quadraticCurveTo(x, y, x + radius, y);
266
+ ctx.closePath();
267
+ }
268
+
269
+ /**
270
+ * Vérifie si un point est dans les limites
271
+ * @param {number} x - Coordonnée X
272
+ * @param {number} y - Coordonnée Y
273
+ * @returns {boolean} True si le point est dans le stepper
274
+ */
275
+ isPointInside(x, y) {
276
+ return x >= this.x && x <= this.x + this.width &&
277
+ y >= this.y && y <= this.y + this.height;
278
+ }
279
+ }
280
+
281
+ export default Stepper;
@@ -0,0 +1,327 @@
1
+ import Component from '../core/Component.js';
2
+
3
+ /**
4
+ * Élément de liste avec support swipe et actions
5
+ * @class
6
+ * @extends Component
7
+ */
8
+ class SwipeableListItem extends Component {
9
+ constructor(framework, options = {}) {
10
+ super(framework, options);
11
+
12
+ this.title = options.title || '';
13
+ this.subtitle = options.subtitle || '';
14
+ this.height = options.height || (this.subtitle ? 72 : 56);
15
+ this.width = options.width || framework.width;
16
+ this.bgColor = options.bgColor || '#FFFFFF';
17
+ this.platform = framework.platform;
18
+
19
+ this.leftActions = options.leftActions || [];
20
+ this.rightActions = options.rightActions || [];
21
+
22
+ this.dragOffset = 0;
23
+ this.dragging = false;
24
+ this.startX = 0;
25
+ this.startY = 0;
26
+ this.hasMoved = false;
27
+
28
+ this.ripples = [];
29
+ this.animationFrame = null;
30
+ this.lastAnimationTime = 0;
31
+
32
+ // 🔹 Binding des handlers
33
+ this.handleMouseDown = this.handleMouseDown.bind(this);
34
+ this.handleMouseMove = this.handleMouseMove.bind(this);
35
+ this.handleMouseUp = this.handleMouseUp.bind(this);
36
+ this.handleTouchStart = this.handleTouchStart.bind(this);
37
+ this.handleTouchMove = this.handleTouchMove.bind(this);
38
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
39
+
40
+ console.log('🏗️ SwipeableListItem créé', {
41
+ hasCanvas: !!this.framework.canvas,
42
+ x: this.x,
43
+ y: this.y,
44
+ width: this.width,
45
+ height: this.height
46
+ });
47
+
48
+ // 🔹 Enregistrer les événements
49
+ this.setupEventListeners();
50
+ }
51
+
52
+ /**
53
+ * Configure les écouteurs d'événements
54
+ * @private
55
+ */
56
+ setupEventListeners() {
57
+ if (!this.framework.canvas) {
58
+ console.error('❌ Pas de canvas trouvé dans le framework');
59
+ return;
60
+ }
61
+
62
+ const canvas = this.framework.canvas;
63
+
64
+ // Événements souris
65
+ canvas.addEventListener('mousedown', this.handleMouseDown);
66
+ canvas.addEventListener('mousemove', this.handleMouseMove);
67
+ canvas.addEventListener('mouseup', this.handleMouseUp);
68
+
69
+ // Événements tactiles
70
+ canvas.addEventListener('touchstart', this.handleTouchStart, { passive: false });
71
+ canvas.addEventListener('touchmove', this.handleTouchMove, { passive: false });
72
+ canvas.addEventListener('touchend', this.handleTouchEnd);
73
+
74
+ console.log('✅ Événements configurés');
75
+ }
76
+
77
+ /**
78
+ * Nettoyer les événements lors de la destruction
79
+ */
80
+ destroy() {
81
+ if (this.framework.canvas) {
82
+ const canvas = this.framework.canvas;
83
+ canvas.removeEventListener('mousedown', this.handleMouseDown);
84
+ canvas.removeEventListener('mousemove', this.handleMouseMove);
85
+ canvas.removeEventListener('mouseup', this.handleMouseUp);
86
+ canvas.removeEventListener('touchstart', this.handleTouchStart);
87
+ canvas.removeEventListener('touchmove', this.handleTouchMove);
88
+ canvas.removeEventListener('touchend', this.handleTouchEnd);
89
+ }
90
+
91
+ if (this.animationFrame) {
92
+ cancelAnimationFrame(this.animationFrame);
93
+ this.animationFrame = null;
94
+ }
95
+
96
+ if (super.destroy) {
97
+ super.destroy();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Obtenir les coordonnées depuis un événement
103
+ * @private
104
+ */
105
+ getCoordinates(event) {
106
+ const rect = this.framework.canvas.getBoundingClientRect();
107
+ if (event.touches && event.touches.length > 0) {
108
+ return {
109
+ x: event.touches[0].clientX - rect.left,
110
+ y: event.touches[0].clientY - rect.top
111
+ };
112
+ }
113
+ return {
114
+ x: event.clientX - rect.left,
115
+ y: event.clientY - rect.top
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Handler mousedown
121
+ * @private
122
+ */
123
+ handleMouseDown(event) {
124
+ const coords = this.getCoordinates(event);
125
+ this.onDragStart(coords.x, coords.y);
126
+ }
127
+
128
+ /**
129
+ * Handler mousemove
130
+ * @private
131
+ */
132
+ handleMouseMove(event) {
133
+ if (!this.dragging) return;
134
+ const coords = this.getCoordinates(event);
135
+ this.onDragMove(coords.x, coords.y);
136
+ this.requestRender();
137
+ }
138
+
139
+ /**
140
+ * Handler mouseup
141
+ * @private
142
+ */
143
+ handleMouseUp(event) {
144
+ if (!this.dragging) return;
145
+ this.onDragEnd();
146
+ this.requestRender();
147
+ }
148
+
149
+ /**
150
+ * Handler touchstart
151
+ * @private
152
+ */
153
+ handleTouchStart(event) {
154
+ const coords = this.getCoordinates(event);
155
+ if (this.isPointInside(coords.x, coords.y)) {
156
+ event.preventDefault();
157
+ this.onDragStart(coords.x, coords.y);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Handler touchmove
163
+ * @private
164
+ */
165
+ handleTouchMove(event) {
166
+ if (!this.dragging) return;
167
+ event.preventDefault();
168
+ const coords = this.getCoordinates(event);
169
+ this.onDragMove(coords.x, coords.y);
170
+ this.requestRender();
171
+ }
172
+
173
+ /**
174
+ * Handler touchend
175
+ * @private
176
+ */
177
+ handleTouchEnd(event) {
178
+ if (!this.dragging) return;
179
+ event.preventDefault();
180
+ this.onDragEnd();
181
+ this.requestRender();
182
+ }
183
+
184
+ /**
185
+ * Demander un redessin
186
+ * @private
187
+ */
188
+ requestRender() {
189
+ if (this.framework && this.framework.requestRender) {
190
+ this.framework.requestRender();
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Début du swipe
196
+ * @param {number} x
197
+ * @param {number} y
198
+ */
199
+ onDragStart(x, y) {
200
+ const inside = this.isPointInside(x, y);
201
+
202
+ if (inside) {
203
+ this.dragging = true;
204
+ this.startX = x;
205
+ this.startY = y;
206
+ this.hasMoved = false;
207
+ } else {
208
+ console.log('❌ Point en dehors des limites');
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Déplacement pendant swipe
214
+ * @param {number} x
215
+ * @param {number} y
216
+ */
217
+ onDragMove(x, y) {
218
+ if (this.dragging) {
219
+ const deltaX = x - this.startX;
220
+ const deltaY = Math.abs(y - this.startY);
221
+
222
+
223
+ // Swipe horizontal seulement si déplacement > 5px
224
+ if (Math.abs(deltaX) > 5 || this.hasMoved) {
225
+ this.hasMoved = true;
226
+ this.dragOffset = deltaX;
227
+
228
+ // Limiter le déplacement
229
+ const maxOffset = Math.max(
230
+ this.leftActions.length * 80,
231
+ this.rightActions.length * 80
232
+ );
233
+ this.dragOffset = Math.min(Math.max(this.dragOffset, -maxOffset), maxOffset);
234
+
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Fin du swipe
241
+ */
242
+ onDragEnd() {
243
+ if (this.dragging) {
244
+
245
+ if (this.hasMoved) {
246
+ if (this.dragOffset > 80 && this.leftActions[0]) {
247
+ this.leftActions[0].onClick?.();
248
+ } else if (this.dragOffset < -80 && this.rightActions[0]) {
249
+ this.rightActions[0].onClick?.();
250
+ }
251
+ }
252
+
253
+ this.dragOffset = 0;
254
+ this.dragging = false;
255
+ this.hasMoved = false;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Dessine l'item
261
+ * @param {CanvasRenderingContext2D} ctx
262
+ */
263
+ draw(ctx) {
264
+ ctx.save();
265
+
266
+ // Actions swipe (arrière-plan)
267
+ if (this.dragOffset > 0 && this.leftActions[0]) {
268
+ ctx.fillStyle = this.leftActions[0].color || '#388E3C';
269
+ ctx.fillRect(this.x, this.y, this.dragOffset, this.height);
270
+ ctx.fillStyle = '#FFF';
271
+ ctx.font = 'bold 14px sans-serif';
272
+ ctx.textAlign = 'center';
273
+ ctx.textBaseline = 'middle';
274
+ ctx.fillText(this.leftActions[0].text, this.x + this.dragOffset / 2, this.y + this.height / 2);
275
+ } else if (this.dragOffset < 0 && this.rightActions[0]) {
276
+ const offset = -this.dragOffset;
277
+ ctx.fillStyle = this.rightActions[0].color || '#D32F2F';
278
+ ctx.fillRect(this.x + this.width - offset, this.y, offset, this.height);
279
+ ctx.fillStyle = '#FFF';
280
+ ctx.font = 'bold 14px sans-serif';
281
+ ctx.textAlign = 'center';
282
+ ctx.textBaseline = 'middle';
283
+ ctx.fillText(this.rightActions[0].text, this.x + this.width - offset / 2, this.y + this.height / 2);
284
+ }
285
+
286
+ // Déplacement du contenu principal
287
+ ctx.translate(this.dragOffset, 0);
288
+
289
+ // Background
290
+ ctx.fillStyle = this.bgColor;
291
+ ctx.fillRect(this.x, this.y, this.width, this.height);
292
+
293
+ // Texte principal
294
+ ctx.fillStyle = '#000';
295
+ ctx.font = '16px -apple-system, Roboto, sans-serif';
296
+ ctx.textAlign = 'left';
297
+ ctx.textBaseline = this.subtitle ? 'top' : 'middle';
298
+ const titleY = this.subtitle ? this.y + 16 : this.y + this.height / 2;
299
+ ctx.fillText(this.title, this.x + 16, titleY);
300
+
301
+ // Sous-titre
302
+ if (this.subtitle) {
303
+ ctx.fillStyle = '#757575';
304
+ ctx.font = '14px -apple-system, Roboto, sans-serif';
305
+ ctx.fillText(this.subtitle, this.x + 16, this.y + 38);
306
+ }
307
+
308
+ // Bordure inférieure
309
+ ctx.strokeStyle = '#E0E0E0';
310
+ ctx.lineWidth = 1;
311
+ ctx.beginPath();
312
+ ctx.moveTo(this.x, this.y + this.height);
313
+ ctx.lineTo(this.x + this.width, this.y + this.height);
314
+ ctx.stroke();
315
+
316
+ ctx.restore();
317
+ }
318
+
319
+ isPointInside(x, y) {
320
+ const inside = x >= this.x && x <= this.x + this.width &&
321
+ y >= this.y && y <= this.y + this.height;
322
+
323
+ return inside;
324
+ }
325
+ }
326
+
327
+ export default SwipeableListItem;