canvasframework 0.5.39 → 0.5.41
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/Cards.js +232 -0
- package/core/CanvasFramework.js +4 -3
- package/core/UIBuilder.js +2 -2
- package/index.js +1 -1
- package/package.json +1 -1
- package/components/Card.js +0 -763
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Container avec système de layout et élévation
|
|
5
|
+
* @class
|
|
6
|
+
* @extends Component
|
|
7
|
+
*/
|
|
8
|
+
class Cards extends Component {
|
|
9
|
+
constructor(framework, options = {}, children = []) {
|
|
10
|
+
super(framework, options);
|
|
11
|
+
this.children = [];
|
|
12
|
+
this.padding = options.padding || 0;
|
|
13
|
+
this.gap = options.gap || 0;
|
|
14
|
+
this.direction = options.direction || 'column';
|
|
15
|
+
this.align = options.align || 'start';
|
|
16
|
+
this.bgColor = options.bgColor || 'transparent';
|
|
17
|
+
this.borderRadius = options.borderRadius || 0;
|
|
18
|
+
this.elevation = options.elevation || 0;
|
|
19
|
+
this.shadowColor = options.shadowColor || 'rgba(0,0,0,0.15)';
|
|
20
|
+
this._applyElevationStyles();
|
|
21
|
+
|
|
22
|
+
// Mode de positionnement
|
|
23
|
+
this.positionMode = options.positionMode || 'flow';
|
|
24
|
+
|
|
25
|
+
// Stocker les positions relatives des enfants
|
|
26
|
+
this.childRelativePositions = new Map();
|
|
27
|
+
|
|
28
|
+
// Ajouter les enfants passés dans le constructeur
|
|
29
|
+
if (children && children.length > 0) {
|
|
30
|
+
this.addChildren(children);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_applyElevationStyles() {
|
|
35
|
+
const elevationStyles = {
|
|
36
|
+
0: { blur: 0, offsetY: 0, spread: 0, opacity: 0 },
|
|
37
|
+
1: { blur: 2, offsetY: 1, spread: 0, opacity: 0.1 },
|
|
38
|
+
2: { blur: 4, offsetY: 2, spread: 1, opacity: 0.15 },
|
|
39
|
+
3: { blur: 8, offsetY: 4, spread: 2, opacity: 0.2 },
|
|
40
|
+
4: { blur: 16, offsetY: 8, spread: 3, opacity: 0.25 },
|
|
41
|
+
5: { blur: 24, offsetY: 12, spread: 4, opacity: 0.3 }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const style = elevationStyles[Math.min(this.elevation, 5)] || elevationStyles[0];
|
|
45
|
+
|
|
46
|
+
this.shadowBlur = style.blur;
|
|
47
|
+
this.shadowOffsetY = style.offsetY;
|
|
48
|
+
this.shadowSpread = style.spread;
|
|
49
|
+
this.shadowOpacity = style.opacity;
|
|
50
|
+
|
|
51
|
+
this._updateShadowColor();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_updateShadowColor() {
|
|
55
|
+
const rgbMatch = this.shadowColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
56
|
+
if (rgbMatch) {
|
|
57
|
+
const r = rgbMatch[1];
|
|
58
|
+
const g = rgbMatch[2];
|
|
59
|
+
const b = rgbMatch[3];
|
|
60
|
+
this._computedShadowColor = `rgba(${r}, ${g}, ${b}, ${this.shadowOpacity})`;
|
|
61
|
+
} else {
|
|
62
|
+
this._computedShadowColor = this.shadowColor;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ajoute plusieurs enfants
|
|
68
|
+
* @param {Component[]} children - Tableau d'enfants
|
|
69
|
+
*/
|
|
70
|
+
addChildren(children) {
|
|
71
|
+
for (const child of children) {
|
|
72
|
+
this.add(child);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Ajoute un enfant
|
|
78
|
+
* @param {Component} child - Composant enfant
|
|
79
|
+
* @returns {Component} L'enfant ajouté
|
|
80
|
+
*/
|
|
81
|
+
add(child) {
|
|
82
|
+
this.children.push(child);
|
|
83
|
+
|
|
84
|
+
// Si l'enfant a des coordonnées x,y, on les interprète comme relatives au container
|
|
85
|
+
if (child.x !== undefined && child.y !== undefined) {
|
|
86
|
+
// Stocker les positions RELATIVES par rapport au container
|
|
87
|
+
this.childRelativePositions.set(child, {
|
|
88
|
+
relativeX: child.x, // x relatif au container
|
|
89
|
+
relativeY: child.y // y relatif au container
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Calculer la position absolue immédiatement
|
|
93
|
+
this.updateChildPosition(child);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return child;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Met à jour la position d'un enfant en fonction de la position du container
|
|
101
|
+
* @param {Component} child - L'enfant à positionner
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
updateChildPosition(child) {
|
|
105
|
+
const relativePos = this.childRelativePositions.get(child);
|
|
106
|
+
|
|
107
|
+
if (relativePos) {
|
|
108
|
+
// Position ABSOLUE = position container + position relative + padding
|
|
109
|
+
child.x = this.x + this.padding + relativePos.relativeX;
|
|
110
|
+
child.y = this.y + this.padding + relativePos.relativeY;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Met à jour la position de tous les enfants quand le container bouge
|
|
116
|
+
* @override
|
|
117
|
+
*/
|
|
118
|
+
setPosition(x, y) {
|
|
119
|
+
super.setPosition(x, y);
|
|
120
|
+
this.updateAllChildrenPositions();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Met à jour toutes les positions des enfants
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
updateAllChildrenPositions() {
|
|
128
|
+
for (const child of this.children) {
|
|
129
|
+
this.updateChildPosition(child);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Change la position relative d'un enfant dans le container
|
|
135
|
+
* @param {Component} child - L'enfant à repositionner
|
|
136
|
+
* @param {number} relativeX - Nouvelle position X relative
|
|
137
|
+
* @param {number} relativeY - Nouvelle position Y relative
|
|
138
|
+
*/
|
|
139
|
+
setChildPosition(child, relativeX, relativeY) {
|
|
140
|
+
this.childRelativePositions.set(child, {
|
|
141
|
+
relativeX: relativeX,
|
|
142
|
+
relativeY: relativeY
|
|
143
|
+
});
|
|
144
|
+
this.updateChildPosition(child);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
draw(ctx) {
|
|
148
|
+
ctx.save();
|
|
149
|
+
|
|
150
|
+
// Dessiner l'ombre
|
|
151
|
+
if (this.elevation > 0 && this.bgColor !== 'transparent') {
|
|
152
|
+
this.drawShadow(ctx);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Dessiner le fond
|
|
156
|
+
if (this.bgColor !== 'transparent') {
|
|
157
|
+
ctx.fillStyle = this.bgColor;
|
|
158
|
+
if (this.borderRadius > 0) {
|
|
159
|
+
ctx.beginPath();
|
|
160
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
161
|
+
ctx.fill();
|
|
162
|
+
} else {
|
|
163
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Dessiner les enfants
|
|
168
|
+
for (let child of this.children) {
|
|
169
|
+
if (child.visible) child.draw(ctx);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ctx.restore();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
drawShadow(ctx) {
|
|
176
|
+
ctx.save();
|
|
177
|
+
|
|
178
|
+
ctx.shadowColor = this._computedShadowColor;
|
|
179
|
+
ctx.shadowBlur = this.shadowBlur;
|
|
180
|
+
ctx.shadowOffsetX = 0;
|
|
181
|
+
ctx.shadowOffsetY = this.shadowOffsetY;
|
|
182
|
+
|
|
183
|
+
ctx.fillStyle = this.bgColor;
|
|
184
|
+
|
|
185
|
+
if (this.borderRadius > 0) {
|
|
186
|
+
ctx.beginPath();
|
|
187
|
+
const spread = this.shadowSpread;
|
|
188
|
+
this.roundRect(
|
|
189
|
+
ctx,
|
|
190
|
+
this.x - spread/2,
|
|
191
|
+
this.y - spread/2,
|
|
192
|
+
this.width + spread,
|
|
193
|
+
this.height + spread,
|
|
194
|
+
this.borderRadius + spread/2
|
|
195
|
+
);
|
|
196
|
+
ctx.fill();
|
|
197
|
+
} else {
|
|
198
|
+
const spread = this.shadowSpread;
|
|
199
|
+
ctx.fillRect(
|
|
200
|
+
this.x - spread/2,
|
|
201
|
+
this.y - spread/2,
|
|
202
|
+
this.width + spread,
|
|
203
|
+
this.height + spread
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ctx.restore();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
211
|
+
ctx.beginPath();
|
|
212
|
+
ctx.moveTo(x + radius, y);
|
|
213
|
+
ctx.lineTo(x + width - radius, y);
|
|
214
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
215
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
216
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
217
|
+
ctx.lineTo(x + radius, y + height);
|
|
218
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
219
|
+
ctx.lineTo(x, y + radius);
|
|
220
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
221
|
+
ctx.closePath();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
isPointInside(x, y) {
|
|
225
|
+
return x >= this.x &&
|
|
226
|
+
x <= this.x + this.width &&
|
|
227
|
+
y >= this.y &&
|
|
228
|
+
y <= this.y + this.height;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default Cards;
|
package/core/CanvasFramework.js
CHANGED
|
@@ -4,7 +4,8 @@ import Input from '../components/Input.js';
|
|
|
4
4
|
import Slider from '../components/Slider.js';
|
|
5
5
|
import Text from '../components/Text.js';
|
|
6
6
|
import View from '../components/View.js';
|
|
7
|
-
import Card from '../components/Card.js';
|
|
7
|
+
//import Card from '../components/Card.js';
|
|
8
|
+
import Cards from '../components/Cards.js';
|
|
8
9
|
import FAB from '../components/FAB.js';
|
|
9
10
|
import SpeedDialFAB from '../components/SpeedDialFAB.js';
|
|
10
11
|
import MorphingFAB from '../components/MorphingFAB.js';
|
|
@@ -2233,7 +2234,7 @@ class CanvasFramework {
|
|
|
2233
2234
|
if (comp.visible) {
|
|
2234
2235
|
const adjustedY = isFixedComponent(comp) ? y : y - this.scrollOffset;
|
|
2235
2236
|
|
|
2236
|
-
|
|
2237
|
+
/* if (comp instanceof Card && comp.clickableChildren && comp.children && comp.children.length > 0) {
|
|
2237
2238
|
if (comp.isPointInside(x, adjustedY)) {
|
|
2238
2239
|
const cardAdjustedY = adjustedY - comp.y - comp.padding;
|
|
2239
2240
|
const cardAdjustedX = x - comp.x - comp.padding;
|
|
@@ -2300,7 +2301,7 @@ class CanvasFramework {
|
|
|
2300
2301
|
}
|
|
2301
2302
|
}
|
|
2302
2303
|
}
|
|
2303
|
-
}
|
|
2304
|
+
}*/
|
|
2304
2305
|
|
|
2305
2306
|
if (comp.isPointInside(x, adjustedY)) {
|
|
2306
2307
|
switch (eventType) {
|
package/core/UIBuilder.js
CHANGED
|
@@ -5,7 +5,7 @@ import Input from '../components/Input.js';
|
|
|
5
5
|
import Slider from '../components/Slider.js';
|
|
6
6
|
import Text from '../components/Text.js';
|
|
7
7
|
import View from '../components/View.js';
|
|
8
|
-
import
|
|
8
|
+
import Cards from '../components/Cards.js';
|
|
9
9
|
import FAB from '../components/FAB.js';
|
|
10
10
|
import SpeedDialFAB from '../components/SpeedDialFAB.js';
|
|
11
11
|
import MorphingFAB from '../components/MorphingFAB.js';
|
|
@@ -82,7 +82,7 @@ const Components = {
|
|
|
82
82
|
Slider,
|
|
83
83
|
Text,
|
|
84
84
|
View,
|
|
85
|
-
|
|
85
|
+
Cards,
|
|
86
86
|
FAB,
|
|
87
87
|
SpeedDialFAB,
|
|
88
88
|
MorphingFAB,
|
package/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { default as Input } from './components/Input.js';
|
|
|
12
12
|
export { default as Slider } from './components/Slider.js';
|
|
13
13
|
export { default as Text } from './components/Text.js';
|
|
14
14
|
export { default as View } from './components/View.js';
|
|
15
|
-
export { default as
|
|
15
|
+
export { default as Cards } from './components/Cards.js';
|
|
16
16
|
export { default as FAB } from './components/FAB.js';
|
|
17
17
|
export { default as SpeedDialFAB } from './components/SpeedDialFAB.js';
|
|
18
18
|
export { default as MorphingFAB } from './components/MorphingFAB.js';
|
package/package.json
CHANGED
package/components/Card.js
DELETED
|
@@ -1,763 +0,0 @@
|
|
|
1
|
-
import Component from '../core/Component.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Container avec système de layout et effet d'élévation
|
|
5
|
-
* @class
|
|
6
|
-
* @extends Component
|
|
7
|
-
* @property {Component[]} children - Enfants
|
|
8
|
-
* @property {number} padding - Padding interne
|
|
9
|
-
* @property {number} gap - Espacement constant entre enfants
|
|
10
|
-
* @property {string} direction - Direction ('column' ou 'row')
|
|
11
|
-
* @property {string} align - Alignement ('start', 'center', 'end', 'stretch')
|
|
12
|
-
* @property {string} bgColor - Couleur de fond
|
|
13
|
-
* @property {number} borderRadius - Rayon des coins
|
|
14
|
-
* @property {number} elevation - Niveau d'élévation (ombres)
|
|
15
|
-
* @property {boolean} autoLayout - Active le layout automatique
|
|
16
|
-
*/
|
|
17
|
-
class Card extends Component {
|
|
18
|
-
/**
|
|
19
|
-
* Crée une instance de Card
|
|
20
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
21
|
-
* @param {Object} [options={}] - Options de configuration
|
|
22
|
-
* @param {number} [options.padding=0] - Padding interne
|
|
23
|
-
* @param {number} [options.gap=0] - Espacement constant entre enfants
|
|
24
|
-
* @param {string} [options.direction='column'] - Direction
|
|
25
|
-
* @param {string} [options.align='start'] - Alignement
|
|
26
|
-
* @param {string} [options.bgColor='transparent'] - Couleur de fond
|
|
27
|
-
* @param {number} [options.borderRadius=0] - Rayon des coins
|
|
28
|
-
* @param {number} [options.elevation=0] - Niveau d'élévation (0-5)
|
|
29
|
-
* @param {boolean} [options.autoLayout=true] - Active le layout automatique
|
|
30
|
-
*/
|
|
31
|
-
constructor(framework, options = {}) {
|
|
32
|
-
super(framework, options);
|
|
33
|
-
this.children = [];
|
|
34
|
-
this.padding = options.padding || 0;
|
|
35
|
-
this.gap = options.gap || 0;
|
|
36
|
-
this.direction = options.direction || 'column';
|
|
37
|
-
this.align = options.align || 'start';
|
|
38
|
-
this.bgColor = options.bgColor || 'transparent';
|
|
39
|
-
this.borderRadius = options.borderRadius || 0;
|
|
40
|
-
this.elevation = options.elevation || 0;
|
|
41
|
-
this.autoLayout = options.autoLayout !== undefined ? options.autoLayout : true;
|
|
42
|
-
|
|
43
|
-
// Stocker les positions relatives des enfants
|
|
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
|
-
}
|
|
142
|
-
}
|
|
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
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Ajoute un enfant (position relative convertie en absolue)
|
|
260
|
-
* @param {Component} child - Composant enfant
|
|
261
|
-
* @returns {Component} L'enfant ajouté
|
|
262
|
-
*/
|
|
263
|
-
add(child) {
|
|
264
|
-
this.children.push(child);
|
|
265
|
-
|
|
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();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Si autoLayout est activé, organiser automatiquement
|
|
282
|
-
if (this.autoLayout) {
|
|
283
|
-
this.layout();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return child;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Supprime un enfant
|
|
291
|
-
* @param {Component} child - Composant enfant à supprimer
|
|
292
|
-
* @returns {boolean} True si l'enfant a été supprimé
|
|
293
|
-
*/
|
|
294
|
-
remove(child) {
|
|
295
|
-
const index = this.children.indexOf(child);
|
|
296
|
-
if (index > -1) {
|
|
297
|
-
this.children.splice(index, 1);
|
|
298
|
-
this.childPositions.delete(child);
|
|
299
|
-
if (this.autoLayout) this.layout();
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Supprime tous les enfants
|
|
307
|
-
*/
|
|
308
|
-
clear() {
|
|
309
|
-
this.children = [];
|
|
310
|
-
this.childPositions.clear();
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Organise les enfants selon le layout
|
|
315
|
-
* @private
|
|
316
|
-
*/
|
|
317
|
-
layout() {
|
|
318
|
-
if (this.children.length === 0 || !this.autoLayout) return;
|
|
319
|
-
|
|
320
|
-
if (this.direction === 'column') {
|
|
321
|
-
let currentY = this.padding;
|
|
322
|
-
|
|
323
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
324
|
-
const child = this.children[i];
|
|
325
|
-
|
|
326
|
-
// Calculer la position X selon l'alignement
|
|
327
|
-
let childX = this.padding;
|
|
328
|
-
if (this.align === 'center') {
|
|
329
|
-
childX = (this.width - child.width) / 2;
|
|
330
|
-
} else if (this.align === 'end') {
|
|
331
|
-
childX = this.width - child.width - this.padding;
|
|
332
|
-
} else if (this.align === 'stretch') {
|
|
333
|
-
childX = this.padding;
|
|
334
|
-
child.width = this.width - (this.padding * 2);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Positionner l'enfant RELATIVEMENT à la Card
|
|
338
|
-
child.x = this.x + childX;
|
|
339
|
-
child.y = this.y + currentY;
|
|
340
|
-
|
|
341
|
-
// Stocker la position relative
|
|
342
|
-
this.childPositions.set(child, { x: childX, y: currentY });
|
|
343
|
-
|
|
344
|
-
// Mettre à jour la position Y pour l'enfant suivant
|
|
345
|
-
currentY += child.height;
|
|
346
|
-
|
|
347
|
-
// Ajouter le gap seulement si ce n'est pas le dernier enfant
|
|
348
|
-
if (i < this.children.length - 1) {
|
|
349
|
-
currentY += this.gap;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
// Direction 'row'
|
|
354
|
-
let currentX = this.padding;
|
|
355
|
-
|
|
356
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
357
|
-
const child = this.children[i];
|
|
358
|
-
|
|
359
|
-
// Calculer la position Y selon l'alignement
|
|
360
|
-
let childY = this.padding;
|
|
361
|
-
if (this.align === 'center') {
|
|
362
|
-
childY = (this.height - child.height) / 2;
|
|
363
|
-
} else if (this.align === 'end') {
|
|
364
|
-
childY = this.height - child.height - this.padding;
|
|
365
|
-
} else if (this.align === 'stretch') {
|
|
366
|
-
childY = this.padding;
|
|
367
|
-
child.height = this.height - (this.padding * 2);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Positionner l'enfant RELATIVEMENT à la Card
|
|
371
|
-
child.x = this.x + currentX;
|
|
372
|
-
child.y = this.y + childY;
|
|
373
|
-
|
|
374
|
-
// Stocker la position relative
|
|
375
|
-
this.childPositions.set(child, { x: currentX, y: childY });
|
|
376
|
-
|
|
377
|
-
// Mettre à jour la position X pour l'enfant suivant
|
|
378
|
-
currentX += child.width;
|
|
379
|
-
|
|
380
|
-
// Ajouter le gap seulement si ce n'est pas le dernier enfant
|
|
381
|
-
if (i < this.children.length - 1) {
|
|
382
|
-
currentX += this.gap;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Met à jour la position de la carte et ajuste les enfants
|
|
390
|
-
* @param {number} x - Nouvelle position X
|
|
391
|
-
* @param {number} y - Nouvelle position Y
|
|
392
|
-
*/
|
|
393
|
-
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;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Définit la position d'un enfant dans le système de coordonnées de la Card
|
|
408
|
-
* @param {Component} child - L'enfant à positionner
|
|
409
|
-
* @param {number} relativeX - Position X relative à la Card
|
|
410
|
-
* @param {number} relativeY - Position Y relative à la Card
|
|
411
|
-
*/
|
|
412
|
-
setChildPosition(child, relativeX, relativeY) {
|
|
413
|
-
if (this.children.includes(child)) {
|
|
414
|
-
child.x = this.x + relativeX;
|
|
415
|
-
child.y = this.y + relativeY;
|
|
416
|
-
this.childPositions.set(child, { x: relativeX, y: relativeY });
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Active/désactive le layout automatique
|
|
422
|
-
* @param {boolean} enabled - True pour activer le layout automatique
|
|
423
|
-
*/
|
|
424
|
-
setAutoLayout(enabled) {
|
|
425
|
-
this.autoLayout = enabled;
|
|
426
|
-
if (enabled) this.layout();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Force un recalcul du layout
|
|
431
|
-
*/
|
|
432
|
-
updateLayout() {
|
|
433
|
-
this.layout();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Génère les paramètres d'ombre selon le niveau d'élévation
|
|
438
|
-
* @param {number} elevation - Niveau d'élévation (0-5)
|
|
439
|
-
* @returns {Object} Configuration de l'ombre
|
|
440
|
-
* @private
|
|
441
|
-
*/
|
|
442
|
-
getShadowConfig(elevation) {
|
|
443
|
-
const shadows = [
|
|
444
|
-
{ blur: 0, offsetY: 0, color: 'transparent', spread: 0 },
|
|
445
|
-
{ blur: 2, offsetY: 1, color: 'rgba(0,0,0,0.12)', spread: 0 },
|
|
446
|
-
{ blur: 3, offsetY: 1, color: 'rgba(0,0,0,0.14)', spread: 0 },
|
|
447
|
-
{ blur: 4, offsetY: 2, color: 'rgba(0,0,0,0.16)', spread: 0 },
|
|
448
|
-
{ blur: 6, offsetY: 3, color: 'rgba(0,0,0,0.18)', spread: 0 },
|
|
449
|
-
{ blur: 8, offsetY: 4, color: 'rgba(0,0,0,0.20)', spread: 0 },
|
|
450
|
-
];
|
|
451
|
-
|
|
452
|
-
return shadows[Math.min(elevation, shadows.length - 1)];
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Dessine l'effet d'ombre selon l'élévation
|
|
457
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
458
|
-
* @private
|
|
459
|
-
*/
|
|
460
|
-
drawShadow(ctx) {
|
|
461
|
-
if (this.elevation <= 0) return;
|
|
462
|
-
|
|
463
|
-
const shadow = this.getShadowConfig(this.elevation);
|
|
464
|
-
|
|
465
|
-
ctx.save();
|
|
466
|
-
|
|
467
|
-
ctx.shadowColor = shadow.color;
|
|
468
|
-
ctx.shadowBlur = shadow.blur;
|
|
469
|
-
ctx.shadowOffsetX = 0;
|
|
470
|
-
ctx.shadowOffsetY = shadow.offsetY;
|
|
471
|
-
|
|
472
|
-
if (this.borderRadius > 0) {
|
|
473
|
-
ctx.beginPath();
|
|
474
|
-
this.roundRect(ctx, this.x, this.y + shadow.offsetY, this.width, this.height, this.borderRadius);
|
|
475
|
-
ctx.fillStyle = this.bgColor === 'transparent' ? 'white' : this.bgColor;
|
|
476
|
-
ctx.fill();
|
|
477
|
-
} else {
|
|
478
|
-
ctx.fillStyle = this.bgColor === 'transparent' ? 'white' : this.bgColor;
|
|
479
|
-
ctx.fillRect(this.x, this.y + shadow.offsetY, this.width, this.height);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
ctx.restore();
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Dessine la carte et ses enfants
|
|
487
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
488
|
-
*/
|
|
489
|
-
draw(ctx) {
|
|
490
|
-
ctx.save();
|
|
491
|
-
|
|
492
|
-
// Dessiner l'ombre si elevation > 0
|
|
493
|
-
if (this.elevation > 0) {
|
|
494
|
-
this.drawShadow(ctx);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Dessiner le fond de la carte
|
|
498
|
-
if (this.bgColor !== 'transparent') {
|
|
499
|
-
ctx.fillStyle = this.bgColor;
|
|
500
|
-
if (this.borderRadius > 0) {
|
|
501
|
-
ctx.beginPath();
|
|
502
|
-
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
503
|
-
ctx.fill();
|
|
504
|
-
} else {
|
|
505
|
-
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Dessiner les enfants
|
|
510
|
-
for (let child of this.children) {
|
|
511
|
-
if (child.visible) child.draw(ctx);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
ctx.restore();
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Dessine un rectangle avec coins arrondis
|
|
519
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
520
|
-
* @param {number} x - Position X
|
|
521
|
-
* @param {number} y - Position Y
|
|
522
|
-
* @param {number} width - Largeur
|
|
523
|
-
* @param {number} height - Hauteur
|
|
524
|
-
* @param {number} radius - Rayon des coins
|
|
525
|
-
* @private
|
|
526
|
-
*/
|
|
527
|
-
roundRect(ctx, x, y, width, height, radius) {
|
|
528
|
-
if (radius === 0) {
|
|
529
|
-
ctx.rect(x, y, width, height);
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
ctx.beginPath();
|
|
534
|
-
ctx.moveTo(x + radius, y);
|
|
535
|
-
ctx.lineTo(x + width - radius, y);
|
|
536
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
537
|
-
ctx.lineTo(x + width, y + height - radius);
|
|
538
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
539
|
-
ctx.lineTo(x + radius, y + height);
|
|
540
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
541
|
-
ctx.lineTo(x, y + radius);
|
|
542
|
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
543
|
-
ctx.closePath();
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Vérifie si un point est dans les limites
|
|
548
|
-
* @param {number} x - Coordonnée X
|
|
549
|
-
* @param {number} y - Coordonnée Y
|
|
550
|
-
* @returns {boolean} True si le point est dans la vue
|
|
551
|
-
*/
|
|
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
|
|
559
|
-
return x >= this.x &&
|
|
560
|
-
x <= this.x + this.width &&
|
|
561
|
-
y >= this.y &&
|
|
562
|
-
y <= this.y + this.height;
|
|
563
|
-
}
|
|
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
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Définit le niveau d'élévation
|
|
612
|
-
* @param {number} elevation - Nouveau niveau d'élévation (0-5)
|
|
613
|
-
*/
|
|
614
|
-
setElevation(elevation) {
|
|
615
|
-
this.elevation = Math.max(0, Math.min(elevation, 5));
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* Augmente le niveau d'élévation
|
|
620
|
-
*/
|
|
621
|
-
raise() {
|
|
622
|
-
this.setElevation(this.elevation + 1);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* Réduit le niveau d'élévation
|
|
627
|
-
*/
|
|
628
|
-
lower() {
|
|
629
|
-
this.setElevation(this.elevation - 1);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Définit l'espacement entre enfants
|
|
634
|
-
* @param {number} gap - Nouvel espacement
|
|
635
|
-
*/
|
|
636
|
-
setGap(gap) {
|
|
637
|
-
this.gap = Math.max(0, gap);
|
|
638
|
-
if (this.autoLayout) this.layout();
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Définit le padding
|
|
643
|
-
* @param {number} padding - Nouveau padding
|
|
644
|
-
*/
|
|
645
|
-
setPadding(padding) {
|
|
646
|
-
this.padding = Math.max(0, padding);
|
|
647
|
-
if (this.autoLayout) this.layout();
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Définit la direction du layout
|
|
652
|
-
* @param {string} direction - 'column' ou 'row'
|
|
653
|
-
*/
|
|
654
|
-
setDirection(direction) {
|
|
655
|
-
if (direction === 'column' || direction === 'row') {
|
|
656
|
-
this.direction = direction;
|
|
657
|
-
if (this.autoLayout) this.layout();
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* Définit l'alignement
|
|
663
|
-
* @param {string} align - 'start', 'center', 'end' ou 'stretch'
|
|
664
|
-
*/
|
|
665
|
-
setAlign(align) {
|
|
666
|
-
if (['start', 'center', 'end', 'stretch'].includes(align)) {
|
|
667
|
-
this.align = align;
|
|
668
|
-
if (this.autoLayout) this.layout();
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Calcule la hauteur totale nécessaire pour contenir tous les enfants
|
|
674
|
-
* @returns {number} Hauteur totale nécessaire
|
|
675
|
-
*/
|
|
676
|
-
getTotalHeight() {
|
|
677
|
-
if (this.children.length === 0) return 0;
|
|
678
|
-
|
|
679
|
-
if (this.direction === 'column') {
|
|
680
|
-
const totalChildrenHeight = this.children.reduce((sum, child) => sum + child.height, 0);
|
|
681
|
-
const totalGapHeight = this.gap * Math.max(0, this.children.length - 1);
|
|
682
|
-
return totalChildrenHeight + totalGapHeight + (this.padding * 2);
|
|
683
|
-
}
|
|
684
|
-
return this.height;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* Calcule la largeur totale nécessaire pour contenir tous les enfants
|
|
689
|
-
* @returns {number} Largeur totale nécessaire
|
|
690
|
-
*/
|
|
691
|
-
getTotalWidth() {
|
|
692
|
-
if (this.children.length === 0) return 0;
|
|
693
|
-
|
|
694
|
-
if (this.direction === 'row') {
|
|
695
|
-
const totalChildrenWidth = this.children.reduce((sum, child) => sum + child.width, 0);
|
|
696
|
-
const totalGapWidth = this.gap * Math.max(0, this.children.length - 1);
|
|
697
|
-
return totalChildrenWidth + totalGapWidth + (this.padding * 2);
|
|
698
|
-
}
|
|
699
|
-
return this.width;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
/**
|
|
703
|
-
* Ajuste automatiquement la hauteur de la carte pour contenir tous les enfants
|
|
704
|
-
*/
|
|
705
|
-
fitHeight() {
|
|
706
|
-
if (this.direction === 'column') {
|
|
707
|
-
this.height = this.getTotalHeight();
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Ajuste automatiquement la largeur de la carte pour contenir tous les enfants
|
|
713
|
-
*/
|
|
714
|
-
fitWidth() {
|
|
715
|
-
if (this.direction === 'row') {
|
|
716
|
-
this.width = this.getTotalWidth();
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Ajuste automatiquement les dimensions de la carte pour contenir tous les enfants
|
|
722
|
-
*/
|
|
723
|
-
fitSize() {
|
|
724
|
-
this.fitWidth();
|
|
725
|
-
this.fitHeight();
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Met à jour les dimensions et relayout si autoLayout est activé
|
|
730
|
-
* @param {number} width - Nouvelle largeur
|
|
731
|
-
* @param {number} height - Nouvelle hauteur
|
|
732
|
-
*/
|
|
733
|
-
setSize(width, height) {
|
|
734
|
-
super.setSize(width, height);
|
|
735
|
-
if (this.autoLayout) this.layout();
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* Obtient la position relative d'un enfant
|
|
740
|
-
* @param {Component} child - L'enfant
|
|
741
|
-
* @returns {Object|null} Position relative {x, y} ou null si non trouvé
|
|
742
|
-
*/
|
|
743
|
-
getChildPosition(child) {
|
|
744
|
-
return this.childPositions.get(child) || null;
|
|
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
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
export default Card;
|