canvasframework 0.5.56 → 0.5.58

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.
@@ -1,8 +1,8 @@
1
1
  import Component from '../core/Component.js';
2
2
 
3
3
  /**
4
- * Composant QRCodeGenerator simple
5
- * Génère et affiche un QR code à partir d'une chaîne de caractères
4
+ * QRCodeGenerator SANS librairie externe
5
+ * Version simplifiée qui fonctionne pour texte court et URLs
6
6
  *
7
7
  * @example
8
8
  * new QRCodeGenerator(framework, {
@@ -18,158 +18,243 @@ class QRCodeGenerator extends Component {
18
18
  super(framework, options);
19
19
 
20
20
  // Données du QR code
21
- this.data = options.data || options.text || 'https://example.com';
21
+ this.data = options.data || options.text || 'Hello';
22
22
 
23
23
  // Dimensions
24
24
  this.width = options.width || 250;
25
25
  this.height = options.height || 250;
26
-
27
- // Le QR code est toujours carré, on prend la plus petite dimension
28
26
  this.qrSize = Math.min(this.width, this.height);
29
27
 
30
- // Options du QR code
31
- this.errorCorrectionLevel = options.errorCorrectionLevel || 'M';
28
+ // Options visuelles
32
29
  this.backgroundColor = options.backgroundColor || '#ffffff';
33
30
  this.foregroundColor = options.foregroundColor || '#000000';
34
- this.margin = options.margin !== undefined ? options.margin : 1;
31
+ this.padding = options.padding || 20;
35
32
 
36
- // État interne
37
- this.qrCanvas = null;
38
- this.qrImage = null;
39
- this.isGenerating = false;
40
- this.error = null;
33
+ // État
34
+ this.qrMatrix = null;
35
+ this.moduleSize = 0;
41
36
 
42
- // Charger la librairie QRCode
43
- this.loadQRCodeLibrary();
44
- }
45
-
46
- /**
47
- * Charge la librairie qrcode.js depuis CDN
48
- */
49
- loadQRCodeLibrary() {
50
- if (typeof QRCode === 'undefined') {
51
- const script = document.createElement('script');
52
- script.src = 'https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js';
53
- script.onload = () => {
54
- console.log('[QRCodeGenerator] Library loaded');
55
- this.generateQRCode();
56
- };
57
- script.onerror = () => {
58
- console.error('[QRCodeGenerator] Failed to load library');
59
- this.error = 'Échec du chargement';
60
- this.markDirty();
61
- };
62
- document.head.appendChild(script);
63
- } else {
64
- // Librairie déjà chargée
65
- this.generateQRCode();
66
- }
37
+ // Générer immédiatement
38
+ this.generateQRCode();
67
39
  }
68
40
 
69
41
  async _mount() {
70
42
  super._mount?.();
71
-
72
- // Générer le QR code si les données sont définies et la lib est chargée
73
- if (this.data && typeof QRCode !== 'undefined' && !this.qrCanvas) {
74
- await this.generateQRCode();
43
+ if (!this.qrMatrix) {
44
+ this.generateQRCode();
75
45
  }
76
46
  }
77
47
 
78
48
  destroy() {
79
- if (this.qrCanvas) {
80
- this.qrCanvas = null;
81
- }
82
- if (this.qrImage) {
83
- this.qrImage = null;
84
- }
49
+ this.qrMatrix = null;
85
50
  super.destroy?.();
86
51
  }
87
52
 
88
53
  onUnmount() {
89
54
  console.log('[QRCodeGenerator] onUnmount called');
90
- if (this.qrCanvas) {
91
- this.qrCanvas = null;
92
- }
93
- if (this.qrImage) {
94
- this.qrImage = null;
95
- }
55
+ this.qrMatrix = null;
96
56
  }
97
57
 
98
58
  /**
99
- * Génère le QR code
59
+ * Génère un QR code simple (Version 1 : 21x21)
100
60
  */
101
- async generateQRCode() {
102
- if (typeof QRCode === 'undefined') {
103
- this.error = 'Librairie non chargée';
61
+ generateQRCode() {
62
+ try {
63
+ // Taille fixe pour Version 1 (supporte ~25 caractères)
64
+ const size = 21;
65
+
66
+ // Créer la matrice
67
+ this.qrMatrix = this.createMatrix(size);
68
+
69
+ // Ajouter les patterns de base
70
+ this.addFinderPatterns(this.qrMatrix);
71
+ this.addTimingPatterns(this.qrMatrix);
72
+ this.addAlignmentPattern(this.qrMatrix);
73
+
74
+ // Ajouter les données (simplifié)
75
+ this.addDataSimple(this.qrMatrix);
76
+
77
+ // Calculer la taille d'un module
78
+ const availableSize = this.qrSize - (this.padding * 2);
79
+ this.moduleSize = Math.floor(availableSize / size);
80
+
81
+ console.log('[QRCodeGenerator] QR Code generated:', size + 'x' + size);
104
82
  this.markDirty();
105
- return;
83
+
84
+ } catch (err) {
85
+ console.error('[QRCodeGenerator] Error:', err);
106
86
  }
87
+ }
107
88
 
108
- if (!this.data) {
109
- this.error = 'Aucune donnée';
110
- this.markDirty();
111
- return;
89
+ /**
90
+ * Crée une matrice vide
91
+ */
92
+ createMatrix(size) {
93
+ const matrix = [];
94
+ for (let i = 0; i < size; i++) {
95
+ matrix[i] = new Array(size).fill(-1); // -1 = non défini
112
96
  }
97
+ return matrix;
98
+ }
113
99
 
114
- this.isGenerating = true;
115
- this.error = null;
116
- this.markDirty();
117
-
118
- try {
119
- // Créer un canvas temporaire
120
- const tempCanvas = document.createElement('canvas');
100
+ /**
101
+ * Ajoute les patterns de position (3 carrés dans les coins)
102
+ */
103
+ addFinderPatterns(matrix) {
104
+ const size = matrix.length;
105
+
106
+ // Pattern 7x7
107
+ const addFinder = (startX, startY) => {
108
+ for (let dy = 0; dy < 7; dy++) {
109
+ for (let dx = 0; dx < 7; dx++) {
110
+ const x = startX + dx;
111
+ const y = startY + dy;
112
+
113
+ // Carré extérieur (7x7)
114
+ if (dx === 0 || dx === 6 || dy === 0 || dy === 6) {
115
+ matrix[y][x] = 1;
116
+ }
117
+ // Carré intérieur (3x3) centré
118
+ else if (dx >= 2 && dx <= 4 && dy >= 2 && dy <= 4) {
119
+ matrix[y][x] = 1;
120
+ }
121
+ // Blanc entre les deux
122
+ else {
123
+ matrix[y][x] = 0;
124
+ }
125
+ }
126
+ }
121
127
 
122
- // Options de génération
123
- const options = {
124
- errorCorrectionLevel: this.errorCorrectionLevel,
125
- margin: this.margin,
126
- width: this.qrSize,
127
- color: {
128
- dark: this.foregroundColor,
129
- light: this.backgroundColor
128
+ // Séparateurs blancs (1 pixel autour)
129
+ for (let i = 0; i < 8; i++) {
130
+ if (startX === 0 && startY === 0) {
131
+ // Haut gauche
132
+ if (i < 7) matrix[7][i] = 0;
133
+ if (i < 7) matrix[i][7] = 0;
134
+ } else if (startX === size - 7 && startY === 0) {
135
+ // Haut droit
136
+ if (i < 7) matrix[7][size - 8 + i] = 0;
137
+ if (i < 7) matrix[i][size - 8] = 0;
138
+ } else if (startX === 0 && startY === size - 7) {
139
+ // Bas gauche
140
+ if (i < 7) matrix[size - 8][i] = 0;
141
+ if (i < 7) matrix[size - 8 + i][7] = 0;
130
142
  }
131
- };
143
+ }
144
+ };
145
+
146
+ // Haut gauche
147
+ addFinder(0, 0);
148
+
149
+ // Haut droit
150
+ addFinder(size - 7, 0);
151
+
152
+ // Bas gauche
153
+ addFinder(0, size - 7);
154
+ }
132
155
 
133
- // Générer le QR code
134
- await QRCode.toCanvas(tempCanvas, this.data, options);
135
-
136
- this.qrCanvas = tempCanvas;
137
-
138
- // Convertir en image pour le rendu
139
- const dataURL = tempCanvas.toDataURL('image/png');
140
- this.qrImage = new Image();
141
- this.qrImage.onload = () => {
142
- this.isGenerating = false;
143
- this.markDirty();
144
- };
145
- this.qrImage.src = dataURL;
156
+ /**
157
+ * Ajoute les lignes de timing
158
+ */
159
+ addTimingPatterns(matrix) {
160
+ const size = matrix.length;
161
+
162
+ // Ligne horizontale (ligne 6)
163
+ for (let i = 8; i < size - 8; i++) {
164
+ matrix[6][i] = (i % 2 === 0) ? 1 : 0;
165
+ }
166
+
167
+ // Ligne verticale (colonne 6)
168
+ for (let i = 8; i < size - 8; i++) {
169
+ matrix[i][6] = (i % 2 === 0) ? 1 : 0;
170
+ }
171
+ }
146
172
 
147
- } catch (err) {
148
- this.error = 'Erreur génération';
149
- this.isGenerating = false;
150
- console.error('[QRCodeGenerator] Error:', err);
151
- this.markDirty();
173
+ /**
174
+ * Ajoute le pattern d'alignement (pour Version 1, pas nécessaire, mais on le met quand même)
175
+ */
176
+ addAlignmentPattern(matrix) {
177
+ // Pour Version 1, pas d'alignment pattern
178
+ // On remplit juste avec le module noir obligatoire
179
+ const size = matrix.length;
180
+ matrix[size - 8][8] = 1; // Module noir fixe
181
+ }
182
+
183
+ /**
184
+ * Ajoute les données de manière simplifiée (pattern basé sur les données)
185
+ */
186
+ addDataSimple(matrix) {
187
+ const size = matrix.length;
188
+
189
+ // Convertir les données en une séquence de bits basique
190
+ let bits = '';
191
+ for (let i = 0; i < this.data.length; i++) {
192
+ const charCode = this.data.charCodeAt(i);
193
+ // Convertir en binaire sur 8 bits
194
+ bits += charCode.toString(2).padStart(8, '0');
195
+ }
196
+
197
+ // Ajouter un padding si nécessaire
198
+ while (bits.length < 100) {
199
+ bits += '0';
200
+ }
201
+
202
+ let bitIndex = 0;
203
+
204
+ // Remplir la matrice en zigzag (de droite à gauche, de bas en haut)
205
+ for (let col = size - 1; col > 0; col -= 2) {
206
+ // Sauter la colonne de timing
207
+ if (col === 6) col--;
208
+
209
+ for (let row = 0; row < size; row++) {
210
+ // Alterner direction : bas->haut puis haut->bas
211
+ const y = ((col + 1) % 4 < 2) ? (size - 1 - row) : row;
212
+
213
+ // Remplir 2 colonnes à la fois
214
+ for (let c = 0; c < 2; c++) {
215
+ const x = col - c;
216
+
217
+ // Ne pas écraser les patterns existants
218
+ if (matrix[y][x] !== -1) continue;
219
+
220
+ // Placer un bit
221
+ if (bitIndex < bits.length) {
222
+ matrix[y][x] = bits[bitIndex] === '1' ? 1 : 0;
223
+ bitIndex++;
224
+ } else {
225
+ matrix[y][x] = 0;
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ // Remplir les cases restantes avec 0
232
+ for (let y = 0; y < size; y++) {
233
+ for (let x = 0; x < size; x++) {
234
+ if (matrix[y][x] === -1) {
235
+ matrix[y][x] = 0;
236
+ }
237
+ }
152
238
  }
153
239
  }
154
240
 
155
241
  /**
156
- * Change les données et régénère le QR code
242
+ * Change les données et régénère
157
243
  */
158
244
  async setData(newData) {
159
245
  if (this.data === newData) return;
160
-
161
246
  this.data = newData;
162
- await this.generateQRCode();
247
+ this.generateQRCode();
163
248
  }
164
249
 
165
250
  /**
166
- * Change la taille du QR code
251
+ * Change la taille
167
252
  */
168
253
  async setSize(newSize) {
169
254
  this.width = newSize;
170
255
  this.height = newSize;
171
256
  this.qrSize = newSize;
172
- await this.generateQRCode();
257
+ this.generateQRCode();
173
258
  }
174
259
 
175
260
  draw(ctx) {
@@ -179,12 +264,8 @@ class QRCodeGenerator extends Component {
179
264
  ctx.fillStyle = this.backgroundColor;
180
265
  ctx.fillRect(this.x, this.y, this.width, this.height);
181
266
 
182
- // Centrer le QR code dans les dimensions du composant
183
- const offsetX = (this.width - this.qrSize) / 2;
184
- const offsetY = (this.height - this.qrSize) / 2;
185
-
186
- // État : Génération en cours
187
- if (this.isGenerating) {
267
+ if (!this.qrMatrix || this.moduleSize === 0) {
268
+ // Message de chargement
188
269
  ctx.fillStyle = '#666';
189
270
  ctx.font = '14px Arial';
190
271
  ctx.textAlign = 'center';
@@ -198,30 +279,25 @@ class QRCodeGenerator extends Component {
198
279
  return;
199
280
  }
200
281
 
201
- // État : Erreur
202
- if (this.error) {
203
- ctx.fillStyle = '#ff4444';
204
- ctx.font = '12px Arial';
205
- ctx.textAlign = 'center';
206
- ctx.textBaseline = 'middle';
207
- ctx.fillText(
208
- this.error,
209
- this.x + this.width / 2,
210
- this.y + this.height / 2
211
- );
212
- ctx.restore();
213
- return;
214
- }
282
+ // Centrer le QR code
283
+ const qrPixelSize = this.qrMatrix.length * this.moduleSize;
284
+ const offsetX = (this.width - qrPixelSize) / 2;
285
+ const offsetY = (this.height - qrPixelSize) / 2;
215
286
 
216
- // Dessiner le QR code
217
- if (this.qrImage && this.qrImage.complete) {
218
- ctx.drawImage(
219
- this.qrImage,
220
- this.x + offsetX,
221
- this.y + offsetY,
222
- this.qrSize,
223
- this.qrSize
224
- );
287
+ // Dessiner les modules
288
+ ctx.fillStyle = this.foregroundColor;
289
+
290
+ for (let y = 0; y < this.qrMatrix.length; y++) {
291
+ for (let x = 0; x < this.qrMatrix[y].length; x++) {
292
+ if (this.qrMatrix[y][x] === 1) {
293
+ ctx.fillRect(
294
+ this.x + offsetX + x * this.moduleSize,
295
+ this.y + offsetY + y * this.moduleSize,
296
+ this.moduleSize,
297
+ this.moduleSize
298
+ );
299
+ }
300
+ }
225
301
  }
226
302
 
227
303
  ctx.restore();
@@ -374,7 +374,12 @@ class CanvasFramework {
374
374
  };
375
375
  this._firstRenderDone = false;
376
376
  this._startupStartTime = startTime;
377
+ // Dans le constructeur, après this.scrollFriction = 0.95;
378
+ this.scrollFriction = 0.95;
377
379
 
380
+ // ✅ AJOUTER
381
+ this.overscrollDistance = 0;
382
+ this.isOverscrollAnimating = false;
378
383
  // ✅ Créer automatiquement le canvas
379
384
  this.canvas = document.createElement('canvas');
380
385
  this.canvas.id = canvasId || `canvas-${Date.now()}`;
@@ -620,6 +625,7 @@ class CanvasFramework {
620
625
  if (this.optimizations.useSpatialPartitioning) {
621
626
  this._initSpatialPartitioning();
622
627
  }
628
+
623
629
  }
624
630
 
625
631
  /**
@@ -688,7 +694,7 @@ class CanvasFramework {
688
694
  * Crée le Worker pour le calcul du scroll
689
695
  */
690
696
  createScrollWorker() {
691
- const workerCode = `
697
+ const workerCode = `
692
698
  let state = {
693
699
  scrollOffset: 0,
694
700
  scrollVelocity: 0,
@@ -697,7 +703,10 @@ class CanvasFramework {
697
703
  maxScroll: 0,
698
704
  height: 0,
699
705
  lastTouchY: 0,
700
- components: []
706
+ components: [],
707
+ overscrollDistance: 0,
708
+ maxOverscroll: 150, // ✅ Limite maximale
709
+ overscrollResistance: 0.3
701
710
  };
702
711
 
703
712
  const FIXED_COMPONENT_TYPES = [
@@ -708,13 +717,11 @@ class CanvasFramework {
708
717
 
709
718
  const calculateMaxScroll = () => {
710
719
  let maxY = 0;
711
-
712
720
  for (const comp of state.components) {
713
721
  if (FIXED_COMPONENT_TYPES.includes(comp.type) || !comp.visible) continue;
714
722
  const bottom = comp.y + comp.height;
715
723
  if (bottom > maxY) maxY = bottom;
716
724
  }
717
-
718
725
  return Math.max(0, maxY - state.height + 50);
719
726
  };
720
727
 
@@ -722,7 +729,29 @@ class CanvasFramework {
722
729
  if (Math.abs(state.scrollVelocity) > 0.1 && !state.isDragging) {
723
730
  state.scrollOffset += state.scrollVelocity;
724
731
  state.maxScroll = calculateMaxScroll();
725
- state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
732
+
733
+ // ✅ CORRIGER: Limiter l'overscroll pendant l'inertie
734
+ if (state.scrollOffset > 0) {
735
+ // Limiter à maxOverscroll
736
+ if (state.scrollOffset > state.maxOverscroll) {
737
+ state.scrollOffset = state.maxOverscroll;
738
+ state.scrollVelocity = 0;
739
+ }
740
+ state.overscrollDistance = state.scrollOffset;
741
+ state.scrollVelocity *= 0.85;
742
+ } else if (state.scrollOffset < -state.maxScroll) {
743
+ const excess = Math.abs(state.scrollOffset + state.maxScroll);
744
+ // Limiter à maxOverscroll
745
+ if (excess > state.maxOverscroll) {
746
+ state.scrollOffset = -state.maxScroll - state.maxOverscroll;
747
+ state.scrollVelocity = 0;
748
+ }
749
+ state.overscrollDistance = state.scrollOffset + state.maxScroll;
750
+ state.scrollVelocity *= 0.85;
751
+ } else {
752
+ state.overscrollDistance = 0;
753
+ }
754
+
726
755
  state.scrollVelocity *= state.scrollFriction;
727
756
  } else {
728
757
  state.scrollVelocity = 0;
@@ -731,20 +760,82 @@ class CanvasFramework {
731
760
  return {
732
761
  scrollOffset: state.scrollOffset,
733
762
  scrollVelocity: state.scrollVelocity,
734
- maxScroll: state.maxScroll
763
+ maxScroll: state.maxScroll,
764
+ overscrollDistance: state.overscrollDistance
735
765
  };
736
766
  };
737
767
 
738
768
  const handleTouchMove = (deltaY) => {
739
769
  if (state.isDragging) {
740
- state.scrollOffset += deltaY;
741
770
  state.maxScroll = calculateMaxScroll();
742
- state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
771
+ const wouldBeOffset = state.scrollOffset + deltaY;
772
+
773
+ let actualDelta = deltaY;
774
+
775
+ // ✅ CORRIGER: Appliquer les limites d'overscroll
776
+ if (wouldBeOffset > 0) {
777
+ // Overscroll en haut
778
+ const currentOverscroll = state.scrollOffset > 0 ? state.scrollOffset : 0;
779
+
780
+ // Si on a déjà atteint la limite, ne plus bouger
781
+ if (currentOverscroll >= state.maxOverscroll) {
782
+ actualDelta = 0;
783
+ state.overscrollDistance = state.maxOverscroll;
784
+ } else {
785
+ // Calculer la résistance progressive
786
+ const overscrollRatio = currentOverscroll / state.maxOverscroll;
787
+ const resistance = state.overscrollResistance * (1 - overscrollRatio * 0.7);
788
+
789
+ actualDelta = deltaY * resistance;
790
+
791
+ // S'assurer qu'on ne dépasse pas la limite
792
+ const newOverscroll = currentOverscroll + actualDelta;
793
+ if (newOverscroll > state.maxOverscroll) {
794
+ actualDelta = state.maxOverscroll - currentOverscroll;
795
+ }
796
+
797
+ state.overscrollDistance = currentOverscroll + actualDelta;
798
+ }
799
+ } else if (wouldBeOffset < -state.maxScroll) {
800
+ // Overscroll en bas
801
+ const currentOverscroll = state.scrollOffset < -state.maxScroll
802
+ ? Math.abs(state.scrollOffset + state.maxScroll)
803
+ : 0;
804
+
805
+ // Si on a déjà atteint la limite, ne plus bouger
806
+ if (currentOverscroll >= state.maxOverscroll) {
807
+ actualDelta = 0;
808
+ state.overscrollDistance = -state.maxOverscroll;
809
+ } else {
810
+ // Calculer la résistance progressive
811
+ const overscrollRatio = currentOverscroll / state.maxOverscroll;
812
+ const resistance = state.overscrollResistance * (1 - overscrollRatio * 0.7);
813
+
814
+ actualDelta = deltaY * resistance;
815
+
816
+ // S'assurer qu'on ne dépasse pas la limite
817
+ const newOverscroll = currentOverscroll + Math.abs(actualDelta);
818
+ if (newOverscroll > state.maxOverscroll) {
819
+ actualDelta = deltaY > 0
820
+ ? state.maxOverscroll - currentOverscroll
821
+ : -(state.maxOverscroll - currentOverscroll);
822
+ }
823
+
824
+ state.overscrollDistance = -(currentOverscroll + Math.abs(actualDelta));
825
+ }
826
+ } else {
827
+ // Scroll normal
828
+ state.overscrollDistance = 0;
829
+ }
830
+
831
+ state.scrollOffset += actualDelta;
743
832
  state.scrollVelocity = deltaY;
833
+
744
834
  return {
745
835
  scrollOffset: state.scrollOffset,
746
836
  scrollVelocity: state.scrollVelocity,
747
- maxScroll: state.maxScroll
837
+ maxScroll: state.maxScroll,
838
+ overscrollDistance: state.overscrollDistance
748
839
  };
749
840
  }
750
841
  return null;
@@ -755,16 +846,14 @@ class CanvasFramework {
755
846
 
756
847
  switch (type) {
757
848
  case 'INIT':
758
- state = {
759
- ...state,
760
- ...payload
761
- };
849
+ state = { ...state, ...payload };
762
850
  state.maxScroll = calculateMaxScroll();
763
851
  self.postMessage({
764
852
  type: 'INITIALIZED',
765
853
  payload: {
766
854
  scrollOffset: state.scrollOffset,
767
- maxScroll: state.maxScroll
855
+ maxScroll: state.maxScroll,
856
+ overscrollDistance: 0
768
857
  }
769
858
  });
770
859
  break;
@@ -817,12 +906,43 @@ class CanvasFramework {
817
906
  case 'SET_SCROLL_OFFSET':
818
907
  state.scrollOffset = payload.scrollOffset;
819
908
  state.maxScroll = calculateMaxScroll();
820
- state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
909
+ state.overscrollDistance = 0;
910
+ self.postMessage({
911
+ type: 'SCROLL_UPDATED',
912
+ payload: {
913
+ scrollOffset: state.scrollOffset,
914
+ maxScroll: state.maxScroll,
915
+ overscrollDistance: 0
916
+ }
917
+ });
918
+ break;
919
+
920
+ case 'ANIMATE_RETURN':
921
+ state.maxScroll = calculateMaxScroll();
922
+
923
+ if (state.scrollOffset > 0) {
924
+ state.scrollOffset *= 0.75;
925
+ state.overscrollDistance = state.scrollOffset;
926
+ } else if (state.scrollOffset < -state.maxScroll) {
927
+ const diff = state.scrollOffset + state.maxScroll;
928
+ state.scrollOffset = -state.maxScroll + (diff * 0.75);
929
+ state.overscrollDistance = diff * 0.75;
930
+ }
931
+
932
+ const shouldContinue = Math.abs(state.overscrollDistance) > 1;
933
+
934
+ if (!shouldContinue) {
935
+ state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
936
+ state.overscrollDistance = 0;
937
+ }
938
+
821
939
  self.postMessage({
822
940
  type: 'SCROLL_UPDATED',
823
941
  payload: {
824
942
  scrollOffset: state.scrollOffset,
825
- maxScroll: state.maxScroll
943
+ maxScroll: state.maxScroll,
944
+ overscrollDistance: state.overscrollDistance,
945
+ shouldContinue
826
946
  }
827
947
  });
828
948
  break;
@@ -834,7 +954,8 @@ class CanvasFramework {
834
954
  scrollOffset: state.scrollOffset,
835
955
  scrollVelocity: state.scrollVelocity,
836
956
  maxScroll: state.maxScroll,
837
- isDragging: state.isDragging
957
+ isDragging: state.isDragging,
958
+ overscrollDistance: state.overscrollDistance
838
959
  }
839
960
  });
840
961
  break;
@@ -842,64 +963,71 @@ class CanvasFramework {
842
963
  };
843
964
  `;
844
965
 
845
- const blob = new Blob([workerCode], {
846
- type: 'application/javascript'
847
- });
848
- return new Worker(URL.createObjectURL(blob));
849
- }
966
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
967
+ return new Worker(URL.createObjectURL(blob));
968
+ }
850
969
 
851
970
  /**
852
971
  * Gère les messages du Scroll Worker
853
972
  */
854
973
  handleScrollWorkerMessage(e) {
855
- const {
856
- type,
857
- payload
858
- } = e.data;
974
+ const { type, payload } = e.data;
859
975
 
860
- switch (type) {
861
- case 'SCROLL_UPDATED':
862
- this.scrollOffset = payload.scrollOffset;
863
- this.scrollVelocity = payload.scrollVelocity;
864
- // ✅ CORRECTION IMPORTANTE : Vider le cache dirty pendant le scroll
865
- if (Math.abs(payload.scrollVelocity) > 0.5) {
866
- this.dirtyComponents.clear();
867
- }
868
- // Mettre à jour le cache
869
- this._cachedMaxScroll = payload.maxScroll;
870
- this._maxScrollDirty = false;
871
-
872
- // Marquer les composants comme sales pour mise à jour visuelle
873
- if (Math.abs(payload.scrollVelocity) > 0) {
874
- this.components.forEach(comp => {
875
- if (!this.isFixedComponent(comp)) {
876
- this.markComponentDirty(comp);
877
- }
878
- });
879
- }
880
- break;
976
+ switch (type) {
977
+ case 'SCROLL_UPDATED':
978
+ this.scrollOffset = payload.scrollOffset;
979
+ this.scrollVelocity = payload.scrollVelocity;
980
+
981
+ // AJOUTER
982
+ this.overscrollDistance = payload.overscrollDistance || 0;
983
+
984
+ if (Math.abs(payload.scrollVelocity) > 0.5) {
985
+ this.dirtyComponents.clear();
986
+ }
987
+
988
+ this._cachedMaxScroll = payload.maxScroll;
989
+ this._maxScrollDirty = false;
881
990
 
882
- case 'MAX_SCROLL_UPDATED':
883
- this._cachedMaxScroll = payload.maxScroll;
884
- this._maxScrollDirty = false;
885
- break;
991
+ if (Math.abs(payload.scrollVelocity) > 0) {
992
+ this.components.forEach(comp => {
993
+ if (!this.isFixedComponent(comp)) {
994
+ this.markComponentDirty(comp);
995
+ }
996
+ });
997
+ }
998
+
999
+ // ✅ AJOUTER: Continuer l'animation de retour si nécessaire
1000
+ if (payload.shouldContinue !== undefined && payload.shouldContinue) {
1001
+ requestAnimationFrame(() => {
1002
+ this.scrollWorker.postMessage({ type: 'ANIMATE_RETURN' });
1003
+ });
1004
+ } else if (payload.shouldContinue === false) {
1005
+ this.isOverscrollAnimating = false;
1006
+ }
1007
+ break;
886
1008
 
887
- case 'INITIALIZED':
888
- this.scrollOffset = payload.scrollOffset;
889
- this._cachedMaxScroll = payload.maxScroll;
890
- this._maxScrollDirty = false;
891
- break;
1009
+ case 'MAX_SCROLL_UPDATED':
1010
+ this._cachedMaxScroll = payload.maxScroll;
1011
+ this._maxScrollDirty = false;
1012
+ break;
892
1013
 
893
- case 'STATE':
894
- // Synchroniser l'état local
895
- this.scrollOffset = payload.scrollOffset;
896
- this.scrollVelocity = payload.scrollVelocity;
897
- this.isDragging = payload.isDragging;
898
- this._cachedMaxScroll = payload.maxScroll;
899
- this._maxScrollDirty = false;
900
- break;
901
- }
1014
+ case 'INITIALIZED':
1015
+ this.scrollOffset = payload.scrollOffset;
1016
+ this._cachedMaxScroll = payload.maxScroll;
1017
+ this._maxScrollDirty = false;
1018
+ this.overscrollDistance = 0; // ✅ AJOUTER
1019
+ break;
1020
+
1021
+ case 'STATE':
1022
+ this.scrollOffset = payload.scrollOffset;
1023
+ this.scrollVelocity = payload.scrollVelocity;
1024
+ this.isDragging = payload.isDragging;
1025
+ this._cachedMaxScroll = payload.maxScroll;
1026
+ this._maxScrollDirty = false;
1027
+ this.overscrollDistance = payload.overscrollDistance || 0; // ✅ AJOUTER
1028
+ break;
902
1029
  }
1030
+ }
903
1031
 
904
1032
  /**
905
1033
  * Initialise le Scroll Worker avec les données actuelles
@@ -2311,23 +2439,29 @@ class CanvasFramework {
2311
2439
  }
2312
2440
 
2313
2441
  handleTouchEnd(e) {
2314
- e.preventDefault();
2315
- const touch = e.changedTouches[0];
2316
- const pos = this.getTouchPos(touch);
2442
+ e.preventDefault();
2443
+ const touch = e.changedTouches[0];
2444
+ const pos = this.getTouchPos(touch);
2317
2445
 
2318
- if (!this.isDragging) {
2319
- this.checkComponentsAtPosition(pos.x, pos.y, 'end');
2320
- } else {
2321
- this.isDragging = false;
2322
- this.scrollWorker.postMessage({
2323
- type: 'SET_DRAGGING',
2324
- payload: {
2325
- isDragging: false,
2326
- lastVelocity: this.scrollVelocity
2327
- }
2328
- });
2329
- }
2330
- }
2446
+ if (!this.isDragging) {
2447
+ this.checkComponentsAtPosition(pos.x, pos.y, 'end');
2448
+ } else {
2449
+ this.isDragging = false;
2450
+ this.scrollWorker.postMessage({
2451
+ type: 'SET_DRAGGING',
2452
+ payload: {
2453
+ isDragging: false,
2454
+ lastVelocity: this.scrollVelocity
2455
+ }
2456
+ });
2457
+
2458
+ // ✅ AJOUTER: Démarrer l'animation de retour
2459
+ if (!this.isOverscrollAnimating) {
2460
+ this.isOverscrollAnimating = true;
2461
+ this.scrollWorker.postMessage({ type: 'ANIMATE_RETURN' });
2462
+ }
2463
+ }
2464
+ }
2331
2465
 
2332
2466
  handleMouseDown(e) {
2333
2467
  this.isDragging = false;
@@ -2375,21 +2509,26 @@ class CanvasFramework {
2375
2509
  }
2376
2510
  }
2377
2511
 
2378
- handleMouseUp(e) {
2379
- if (!this.isDragging) {
2380
- this.checkComponentsAtPosition(e.clientX, e.clientY, 'end');
2381
- } else {
2382
- this.isDragging = false;
2383
- this.scrollWorker.postMessage({
2384
- type: 'SET_DRAGGING',
2385
- payload: {
2386
- isDragging: false,
2387
- lastVelocity: this.scrollVelocity
2388
- }
2389
- });
2512
+ handleMouseUp(e) {
2513
+ if (!this.isDragging) {
2514
+ this.checkComponentsAtPosition(e.clientX, e.clientY, 'end');
2515
+ } else {
2516
+ this.isDragging = false;
2517
+ this.scrollWorker.postMessage({
2518
+ type: 'SET_DRAGGING',
2519
+ payload: {
2520
+ isDragging: false,
2521
+ lastVelocity: this.scrollVelocity
2522
+ }
2523
+ });
2524
+
2525
+ // ✅ AJOUTER: Démarrer l'animation de retour
2526
+ if (!this.isOverscrollAnimating) {
2527
+ this.isOverscrollAnimating = true;
2528
+ this.scrollWorker.postMessage({ type: 'ANIMATE_RETURN' });
2390
2529
  }
2391
2530
  }
2392
-
2531
+ }
2393
2532
 
2394
2533
  getTouchPos(touch) {
2395
2534
  const rect = this.canvas.getBoundingClientRect();
@@ -2988,6 +3127,46 @@ class CanvasFramework {
2988
3127
  this.renderFull();
2989
3128
  }
2990
3129
  }
3130
+
3131
+ /**
3132
+ * Dessine l'effet d'overscroll (overlay gris)
3133
+ */
3134
+ drawOverscrollEffect() {console.log('dessine');
3135
+ if (Math.abs(this.overscrollDistance) < 1) return;
3136
+
3137
+ const ctx = this.ctx;
3138
+ ctx.save();
3139
+
3140
+ // Calculer l'opacité (max 0.4 pour Android)
3141
+ const maxOverscroll = 150;
3142
+ const opacity = Math.min(Math.abs(this.overscrollDistance) / maxOverscroll, 1) * 0.4;
3143
+
3144
+ // Hauteur de l'overlay
3145
+ const overlayHeight = Math.min(Math.abs(this.overscrollDistance) * 1.2, 250);
3146
+
3147
+ let gradient;
3148
+
3149
+ // Overscroll en haut
3150
+ if (this.overscrollDistance > 0) {
3151
+ gradient = ctx.createLinearGradient(0, 0, 0, overlayHeight);
3152
+ gradient.addColorStop(0, `rgba(100, 100, 100, ${opacity})`);
3153
+ gradient.addColorStop(1, 'rgba(100, 100, 100, 0)');
3154
+
3155
+ ctx.fillStyle = gradient;
3156
+ ctx.fillRect(0, 0, this.width, overlayHeight);
3157
+ }
3158
+ // Overscroll en bas
3159
+ else if (this.overscrollDistance < 0) {
3160
+ gradient = ctx.createLinearGradient(0, this.height - overlayHeight, 0, this.height);
3161
+ gradient.addColorStop(0, 'rgba(100, 100, 100, 0)');
3162
+ gradient.addColorStop(1, `rgba(100, 100, 100, ${opacity})`);
3163
+
3164
+ ctx.fillStyle = gradient;
3165
+ ctx.fillRect(0, this.height - overlayHeight, this.width, overlayHeight);
3166
+ }
3167
+
3168
+ ctx.restore();
3169
+ }
2991
3170
 
2992
3171
  /**
2993
3172
  * Rendu normal (sans transition)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.5.56",
3
+ "version": "0.5.58",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/beyons/CanvasFramework.git"