canvasframework 0.5.37 → 0.5.39

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 (2) hide show
  1. package/components/Card.js +309 -84
  2. package/package.json +1 -1
@@ -40,48 +40,242 @@ class Card extends Component {
40
40
  this.elevation = options.elevation || 0;
41
41
  this.autoLayout = options.autoLayout !== undefined ? options.autoLayout : true;
42
42
 
43
- // 🔴 IMPORTANT : Activer la gestion des clics pour les enfants
44
- this.clickableChildren = true;
45
-
46
- // 🔴 Détecter la plateforme
47
- //this.isCapacitor = typeof window !== 'undefined' && (window.Capacitor || window.cordova);
48
-
49
43
  // Stocker les positions relatives des enfants
50
44
  this.childPositions = new Map();
45
+
46
+ // NOUVEAU: Gestion des événements
47
+ this._canvas = null;
48
+ this._isTouchDevice = false;
49
+ this._clickListeners = new Map();
50
+ this._touchListeners = new Map();
51
+ this._enabled = options.enabled !== undefined ? options.enabled : true;
52
+ this._interactive = options.interactive !== undefined ? options.interactive : true;
53
+
54
+ // NOUVEAU: Pour le hot reload, nettoyer les anciens événements
55
+ if (typeof window !== 'undefined') {
56
+ this._setupEventCleanup();
57
+ }
58
+ }
59
+
60
+ // NOUVEAU: Initialisation des événements
61
+ _initEvents() {
62
+ if (!this.framework || !this.framework.canvas) return;
63
+
64
+ this._canvas = this.framework.canvas;
65
+
66
+ // Détecter si on est sur mobile/touch
67
+ this._isTouchDevice = 'ontouchstart' in window ||
68
+ navigator.maxTouchPoints > 0 ||
69
+ navigator.msMaxTouchPoints > 0;
70
+
71
+ // Écouter les événements sur le canvas
72
+ this._canvas.addEventListener('click', this._handleCanvasClick.bind(this));
73
+ this._canvas.addEventListener('touchstart', this._handleCanvasTouch.bind(this), { passive: false });
74
+ this._canvas.addEventListener('touchend', this._handleCanvasTouchEnd.bind(this), { passive: false });
75
+ }
76
+
77
+ // NOUVEAU: Nettoyage pour hot reload Vite
78
+ _setupEventCleanup() {
79
+ // Avant que la page ne soit déchargée (pour hot reload)
80
+ window.addEventListener('beforeunload', () => {
81
+ this._cleanupEvents();
82
+ });
83
+ }
84
+
85
+ // NOUVEAU: Nettoyer les événements
86
+ _cleanupEvents() {
87
+ if (this._canvas) {
88
+ this._canvas.removeEventListener('click', this._handleCanvasClick);
89
+ this._canvas.removeEventListener('touchstart', this._handleCanvasTouch);
90
+ this._canvas.removeEventListener('touchend', this._handleCanvasTouchEnd);
91
+ }
92
+ this._clickListeners.clear();
93
+ this._touchListeners.clear();
94
+ }
95
+
96
+ // NOUVEAU: Convertir les coordonnées du DOM vers canvas
97
+ _getCanvasCoordinates(clientX, clientY) {
98
+ if (!this._canvas) return { x: 0, y: 0 };
99
+
100
+ const rect = this._canvas.getBoundingClientRect();
101
+ const scaleX = this._canvas.width / rect.width;
102
+ const scaleY = this._canvas.height / rect.height;
103
+
104
+ return {
105
+ x: (clientX - rect.left) * scaleX,
106
+ y: (clientY - rect.top) * scaleY
107
+ };
108
+ }
109
+
110
+ // NOUVEAU: Gérer les clics sur le canvas
111
+ _handleCanvasClick(event) {
112
+ if (!this._enabled || !this.visible || !this._interactive) return;
113
+
114
+ const coords = this._getCanvasCoordinates(event.clientX, event.clientY);
115
+
116
+ // Vérifier si le clic est sur cette carte
117
+ if (this.isPointInside(coords.x, coords.y)) {
118
+ // Empêcher le comportement par défaut
119
+ event.preventDefault();
120
+ event.stopPropagation();
121
+
122
+ // Déclencher l'événement onClick de la carte
123
+ if (this.onClick) {
124
+ const localCoords = {
125
+ x: coords.x - this.x,
126
+ y: coords.y - this.y
127
+ };
128
+ this.onClick({
129
+ type: 'click',
130
+ x: localCoords.x,
131
+ y: localCoords.y,
132
+ clientX: event.clientX,
133
+ clientY: event.clientY,
134
+ originalEvent: event,
135
+ target: this
136
+ });
137
+ }
138
+
139
+ // Vérifier si un enfant est cliqué
140
+ this._propagateClickToChildren(coords.x, coords.y, event);
141
+ }
51
142
  }
52
-
53
- // 🔴 AJOUTER CETTE MÉTHODE
54
- _isMobileApp() {
55
- if (typeof window === 'undefined') return false;
56
- return !!(window.Capacitor || window.cordova || window.__FORCE_MOBILE_MODE__);
143
+
144
+ // NOUVEAU: Gérer les touches tactiles
145
+ _handleCanvasTouch(event) {
146
+ if (!this._enabled || !this.visible || !this._interactive) return;
147
+
148
+ if (event.touches.length > 0) {
149
+ event.preventDefault();
150
+ event.stopPropagation();
151
+
152
+ const touch = event.touches[0];
153
+ const coords = this._getCanvasCoordinates(touch.clientX, touch.clientY);
154
+
155
+ // Stocker la position de départ du touch
156
+ this._lastTouchStart = { x: coords.x, y: coords.y };
157
+
158
+ // Vérifier si le touch est sur cette carte
159
+ if (this.isPointInside(coords.x, coords.y)) {
160
+ // Déclencher l'événement onTouchStart de la carte
161
+ if (this.onTouchStart) {
162
+ const localCoords = {
163
+ x: coords.x - this.x,
164
+ y: coords.y - this.y
165
+ };
166
+ this.onTouchStart({
167
+ type: 'touchstart',
168
+ x: localCoords.x,
169
+ y: localCoords.y,
170
+ clientX: touch.clientX,
171
+ clientY: touch.clientY,
172
+ originalEvent: event,
173
+ target: this
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ // NOUVEAU: Gérer la fin des touches tactiles
181
+ _handleCanvasTouchEnd(event) {
182
+ if (!this._enabled || !this.visible || !this._interactive) return;
183
+
184
+ event.preventDefault();
185
+ event.stopPropagation();
186
+
187
+ if (event.changedTouches.length > 0) {
188
+ const touch = event.changedTouches[0];
189
+ const coords = this._getCanvasCoordinates(touch.clientX, touch.clientY);
190
+
191
+ // Vérifier si le touch end est sur cette carte
192
+ if (this.isPointInside(coords.x, coords.y)) {
193
+ // Déclencher l'événement onTouchEnd de la carte
194
+ if (this.onTouchEnd) {
195
+ const localCoords = {
196
+ x: coords.x - this.x,
197
+ y: coords.y - this.y
198
+ };
199
+ this.onTouchEnd({
200
+ type: 'touchend',
201
+ x: localCoords.x,
202
+ y: localCoords.y,
203
+ clientX: touch.clientX,
204
+ clientY: touch.clientY,
205
+ originalEvent: event,
206
+ target: this
207
+ });
208
+ }
209
+
210
+ // Simuler un clic si le touch start était sur la même position
211
+ if (this._lastTouchStart &&
212
+ Math.abs(coords.x - this._lastTouchStart.x) < 10 &&
213
+ Math.abs(coords.y - this._lastTouchStart.y) < 10) {
214
+ this._propagateClickToChildren(coords.x, coords.y, event);
215
+ }
216
+
217
+ this._lastTouchStart = null;
218
+ }
219
+ }
220
+ }
221
+
222
+ // NOUVEAU: Propager le clic aux enfants
223
+ _propagateClickToChildren(canvasX, canvasY, originalEvent) {
224
+ // Parcourir les enfants du dernier au premier (z-order)
225
+ for (let i = this.children.length - 1; i >= 0; i--) {
226
+ const child = this.children[i];
227
+
228
+ // Vérifier si l'enfant est visible et interactif
229
+ const childEnabled = child._enabled !== undefined ? child._enabled : true;
230
+ const childInteractive = child._interactive !== undefined ? child._interactive : true;
231
+
232
+ if (child.visible && childEnabled && childInteractive &&
233
+ child.isPointInside(canvasX, canvasY)) {
234
+
235
+ // Déclencher l'événement onClick de l'enfant
236
+ if (child.onClick) {
237
+ const localCoords = {
238
+ x: canvasX - child.x,
239
+ y: canvasY - child.y
240
+ };
241
+ child.onClick({
242
+ type: 'click',
243
+ x: localCoords.x,
244
+ y: localCoords.y,
245
+ clientX: originalEvent.clientX,
246
+ clientY: originalEvent.clientY,
247
+ originalEvent: originalEvent,
248
+ target: child
249
+ });
250
+ }
251
+
252
+ // Stopper la propagation après avoir trouvé un enfant cliqué
253
+ break;
254
+ }
255
+ }
57
256
  }
58
257
 
59
258
  /**
60
- * Ajoute un enfant
259
+ * Ajoute un enfant (position relative convertie en absolue)
61
260
  * @param {Component} child - Composant enfant
62
261
  * @returns {Component} L'enfant ajouté
63
262
  */
64
263
  add(child) {
65
264
  this.children.push(child);
66
265
 
67
- if (this._isMobileApp()) {
68
- // CAPACITOR : Convertir en positions absolues
69
- const relativeX = child.x;
70
- const relativeY = child.y;
71
-
72
- child.x = this.x + relativeX;
73
- child.y = this.y + relativeY;
74
-
75
- this.childPositions.set(child, {
76
- x: relativeX,
77
- y: relativeY
78
- });
79
- } else {
80
- // WEB : Garder les positions relatives
81
- this.childPositions.set(child, {
82
- x: child.x,
83
- y: child.y
84
- });
266
+ // CONVERTIR les positions relatives en positions absolues
267
+ child.x = this.x + child.x;
268
+ child.y = this.y + child.y;
269
+
270
+ // Stocker la position relative originale
271
+ this.childPositions.set(child, {
272
+ x: child.x - this.x,
273
+ y: child.y - this.y
274
+ });
275
+
276
+ // NOUVEAU: Initialiser les événements si c'est la première fois
277
+ if (this.children.length === 1 && this._canvas === null) {
278
+ this._initEvents();
85
279
  }
86
280
 
87
281
  // Si autoLayout est activé, organiser automatiquement
@@ -140,15 +334,9 @@ class Card extends Component {
140
334
  child.width = this.width - (this.padding * 2);
141
335
  }
142
336
 
143
- if (this._isMobileApp()) {
144
- // CAPACITOR : Positions absolues
145
- child.x = this.x + childX;
146
- child.y = this.y + currentY;
147
- } else {
148
- // WEB : Positions relatives
149
- child.x = childX;
150
- child.y = currentY;
151
- }
337
+ // Positionner l'enfant RELATIVEMENT à la Card
338
+ child.x = this.x + childX;
339
+ child.y = this.y + currentY;
152
340
 
153
341
  // Stocker la position relative
154
342
  this.childPositions.set(child, { x: childX, y: currentY });
@@ -179,15 +367,9 @@ class Card extends Component {
179
367
  child.height = this.height - (this.padding * 2);
180
368
  }
181
369
 
182
- if (this._isMobileApp()) {
183
- // CAPACITOR : Positions absolues
184
- child.x = this.x + currentX;
185
- child.y = this.y + childY;
186
- } else {
187
- // WEB : Positions relatives
188
- child.x = currentX;
189
- child.y = childY;
190
- }
370
+ // Positionner l'enfant RELATIVEMENT à la Card
371
+ child.x = this.x + currentX;
372
+ child.y = this.y + childY;
191
373
 
192
374
  // Stocker la position relative
193
375
  this.childPositions.set(child, { x: currentX, y: childY });
@@ -209,20 +391,15 @@ class Card extends Component {
209
391
  * @param {number} y - Nouvelle position Y
210
392
  */
211
393
  setPosition(x, y) {
212
- if (this._isMobileApp()) {
213
- // CAPACITOR : Déplacer les enfants avec la carte
214
- const deltaX = x - this.x;
215
- const deltaY = y - this.y;
216
-
217
- super.setPosition(x, y);
218
-
219
- for (let child of this.children) {
220
- child.x += deltaX;
221
- child.y += deltaY;
222
- }
223
- } else {
224
- // WEB : Les enfants sont en relatif, pas besoin de les déplacer
225
- super.setPosition(x, y);
394
+ const deltaX = x - this.x;
395
+ const deltaY = y - this.y;
396
+
397
+ super.setPosition(x, y);
398
+
399
+ // Déplacer tous les enfants avec la carte
400
+ for (let child of this.children) {
401
+ child.x += deltaX;
402
+ child.y += deltaY;
226
403
  }
227
404
  }
228
405
 
@@ -234,13 +411,8 @@ class Card extends Component {
234
411
  */
235
412
  setChildPosition(child, relativeX, relativeY) {
236
413
  if (this.children.includes(child)) {
237
- if (this._isMobileApp()) {
238
- child.x = this.x + relativeX;
239
- child.y = this.y + relativeY;
240
- } else {
241
- child.x = relativeX;
242
- child.y = relativeY;
243
- }
414
+ child.x = this.x + relativeX;
415
+ child.y = this.y + relativeY;
244
416
  this.childPositions.set(child, { x: relativeX, y: relativeY });
245
417
  }
246
418
  }
@@ -335,21 +507,8 @@ class Card extends Component {
335
507
  }
336
508
 
337
509
  // Dessiner les enfants
338
- if (this._isMobileApp()) {
339
- // CAPACITOR : Dessiner directement (positions absolues)
340
- for (let child of this.children) {
341
- if (child.visible) child.draw(ctx);
342
- }
343
- } else {
344
- // WEB : Dessiner avec translate (positions relatives)
345
- ctx.save();
346
- ctx.translate(this.x, this.y);
347
-
348
- for (let child of this.children) {
349
- if (child.visible) child.draw(ctx);
350
- }
351
-
352
- ctx.restore();
510
+ for (let child of this.children) {
511
+ if (child.visible) child.draw(ctx);
353
512
  }
354
513
 
355
514
  ctx.restore();
@@ -391,12 +550,63 @@ class Card extends Component {
391
550
  * @returns {boolean} True si le point est dans la vue
392
551
  */
393
552
  isPointInside(x, y) {
553
+ // Pour les rectangles arrondis, vérification plus précise
554
+ if (this.borderRadius > 0) {
555
+ return this._isPointInRoundedRect(x, y);
556
+ }
557
+
558
+ // Pour les rectangles normaux
394
559
  return x >= this.x &&
395
560
  x <= this.x + this.width &&
396
561
  y >= this.y &&
397
562
  y <= this.y + this.height;
398
563
  }
399
564
 
565
+ // NOUVEAU: Vérifier si un point est dans un rectangle arrondi
566
+ _isPointInRoundedRect(x, y) {
567
+ const rectX = this.x;
568
+ const rectY = this.y;
569
+ const width = this.width;
570
+ const height = this.height;
571
+ const radius = this.borderRadius;
572
+
573
+ // Vérifier la zone centrale (sans les coins arrondis)
574
+ if (x >= rectX + radius && x <= rectX + width - radius &&
575
+ y >= rectY && y <= rectY + height) {
576
+ return true;
577
+ }
578
+ if (x >= rectX && x <= rectX + width &&
579
+ y >= rectY + radius && y <= rectY + height - radius) {
580
+ return true;
581
+ }
582
+
583
+ // Vérifier les quatre coins arrondis
584
+ const checkCorner = (cornerX, cornerY) => {
585
+ const dx = x - cornerX;
586
+ const dy = y - cornerY;
587
+ return (dx * dx + dy * dy) <= (radius * radius);
588
+ };
589
+
590
+ // Coin supérieur gauche
591
+ if (x < rectX + radius && y < rectY + radius) {
592
+ return checkCorner(rectX + radius, rectY + radius);
593
+ }
594
+ // Coin supérieur droit
595
+ if (x > rectX + width - radius && y < rectY + radius) {
596
+ return checkCorner(rectX + width - radius, rectY + radius);
597
+ }
598
+ // Coin inférieur gauche
599
+ if (x < rectX + radius && y > rectY + height - radius) {
600
+ return checkCorner(rectX + radius, rectY + height - radius);
601
+ }
602
+ // Coin inférieur droit
603
+ if (x > rectX + width - radius && y > rectY + height - radius) {
604
+ return checkCorner(rectX + width - radius, rectY + height - radius);
605
+ }
606
+
607
+ return false;
608
+ }
609
+
400
610
  /**
401
611
  * Définit le niveau d'élévation
402
612
  * @param {number} elevation - Nouveau niveau d'élévation (0-5)
@@ -533,6 +743,21 @@ class Card extends Component {
533
743
  getChildPosition(child) {
534
744
  return this.childPositions.get(child) || null;
535
745
  }
746
+
747
+ // NOUVEAU: Méthodes pour activer/désactiver l'interactivité
748
+ setEnabled(enabled) {
749
+ this._enabled = enabled;
750
+ }
751
+
752
+ setInteractive(interactive) {
753
+ this._interactive = interactive;
754
+ }
755
+
756
+ // NOUVEAU: Méthode de nettoyage
757
+ destroy() {
758
+ this._cleanupEvents();
759
+ super.destroy();
760
+ }
536
761
  }
537
762
 
538
763
  export default Card;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.5.37",
3
+ "version": "0.5.39",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/beyons/CanvasFramework.git"