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.
- package/components/Card.js +309 -84
- package/package.json +1 -1
package/components/Card.js
CHANGED
|
@@ -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
|
-
//
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
child.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
339
|
-
|
|
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;
|