canvasframework 0.5.24 → 0.5.26
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 +255 -52
- package/package.json +1 -1
package/components/Card.js
CHANGED
|
@@ -1,46 +1,44 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Container avec système de layout et effet d'élévation
|
|
5
5
|
* @class
|
|
6
6
|
* @extends Component
|
|
7
7
|
* @property {Component[]} children - Enfants
|
|
8
8
|
* @property {number} padding - Padding interne
|
|
9
|
-
* @property {number} gap - Espacement entre enfants
|
|
9
|
+
* @property {number} gap - Espacement constant entre enfants
|
|
10
10
|
* @property {string} direction - Direction ('column' ou 'row')
|
|
11
|
-
* @property {string} align - Alignement ('start', 'center', 'end')
|
|
11
|
+
* @property {string} align - Alignement ('start', 'center', 'end', 'stretch')
|
|
12
12
|
* @property {string} bgColor - Couleur de fond
|
|
13
13
|
* @property {number} borderRadius - Rayon des coins
|
|
14
|
-
* @property {number} elevation -
|
|
15
|
-
* @property {boolean}
|
|
14
|
+
* @property {number} elevation - Niveau d'élévation (ombres)
|
|
15
|
+
* @property {boolean} autoHeight - Ajuste automatiquement la hauteur selon le contenu
|
|
16
16
|
*/
|
|
17
17
|
class Card extends Component {
|
|
18
18
|
/**
|
|
19
19
|
* Crée une instance de Card
|
|
20
20
|
* @param {CanvasFramework} framework - Framework parent
|
|
21
21
|
* @param {Object} [options={}] - Options de configuration
|
|
22
|
-
* @param {number} [options.padding=
|
|
23
|
-
* @param {number} [options.gap=0] - Espacement entre enfants
|
|
22
|
+
* @param {number} [options.padding=0] - Padding interne
|
|
23
|
+
* @param {number} [options.gap=0] - Espacement constant entre enfants
|
|
24
24
|
* @param {string} [options.direction='column'] - Direction
|
|
25
25
|
* @param {string} [options.align='start'] - Alignement
|
|
26
|
-
* @param {string} [options.bgColor='
|
|
27
|
-
* @param {number} [options.borderRadius] - Rayon des coins
|
|
28
|
-
* @param {number} [options.elevation=
|
|
29
|
-
* @param {boolean} [options.
|
|
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.autoHeight=false] - Ajuste automatiquement la hauteur
|
|
30
30
|
*/
|
|
31
31
|
constructor(framework, options = {}) {
|
|
32
32
|
super(framework, options);
|
|
33
33
|
this.children = [];
|
|
34
|
-
this.padding = options.padding
|
|
35
|
-
this.gap = options.gap || 0;
|
|
36
|
-
this.direction = options.direction || 'column';
|
|
37
|
-
this.align = options.align || 'start';
|
|
38
|
-
this.bgColor = options.bgColor || '
|
|
39
|
-
this.borderRadius = options.borderRadius
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.elevation = options.elevation !== undefined ? options.elevation : 2;
|
|
43
|
-
this.clipContent = options.clipContent !== false;
|
|
34
|
+
this.padding = options.padding || 0;
|
|
35
|
+
this.gap = options.gap || 0; // Espacement constant entre chaque enfant
|
|
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.autoHeight = options.autoHeight || false;
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
/**
|
|
@@ -55,34 +53,157 @@ class Card extends Component {
|
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
/**
|
|
58
|
-
*
|
|
56
|
+
* Supprime un enfant
|
|
57
|
+
* @param {Component} child - Composant enfant à supprimer
|
|
58
|
+
* @returns {boolean} True si l'enfant a été supprimé
|
|
59
|
+
*/
|
|
60
|
+
remove(child) {
|
|
61
|
+
const index = this.children.indexOf(child);
|
|
62
|
+
if (index > -1) {
|
|
63
|
+
this.children.splice(index, 1);
|
|
64
|
+
this.layout();
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Supprime tous les enfants
|
|
72
|
+
*/
|
|
73
|
+
clear() {
|
|
74
|
+
this.children = [];
|
|
75
|
+
this.layout();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Organise les enfants selon le layout avec espacement constant
|
|
59
80
|
* @private
|
|
60
81
|
*/
|
|
61
82
|
layout() {
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
if (this.children.length === 0) return;
|
|
84
|
+
|
|
85
|
+
let currentX = this.x + this.padding;
|
|
86
|
+
let currentY = this.y + this.padding;
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
if (this.direction === 'column') {
|
|
89
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
90
|
+
const child = this.children[i];
|
|
91
|
+
|
|
92
|
+
// Positionner l'enfant
|
|
67
93
|
child.x = currentX;
|
|
68
94
|
child.y = currentY;
|
|
95
|
+
|
|
96
|
+
// Gérer l'alignement horizontal
|
|
69
97
|
if (this.align === 'center') {
|
|
70
|
-
child.x = (this.width - child.width) / 2;
|
|
98
|
+
child.x = this.x + (this.width - child.width) / 2;
|
|
71
99
|
} else if (this.align === 'end') {
|
|
72
|
-
child.x = this.width - child.width - this.padding;
|
|
100
|
+
child.x = this.x + this.width - child.width - this.padding;
|
|
101
|
+
} else if (this.align === 'stretch') {
|
|
102
|
+
child.x = this.x + this.padding;
|
|
103
|
+
child.width = this.width - (this.padding * 2);
|
|
73
104
|
}
|
|
105
|
+
|
|
106
|
+
// Mettre à jour la position Y pour l'enfant suivant
|
|
107
|
+
// Toujours le même espacement (gap) entre chaque enfant
|
|
74
108
|
currentY += child.height + this.gap;
|
|
75
|
-
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Ajuster la hauteur automatiquement si autoHeight est activé
|
|
112
|
+
if (this.autoHeight) {
|
|
113
|
+
const totalChildrenHeight = this.children.reduce((sum, child) => sum + child.height, 0);
|
|
114
|
+
const totalGapHeight = this.gap * Math.max(0, this.children.length - 1);
|
|
115
|
+
const neededHeight = totalChildrenHeight + totalGapHeight + (this.padding * 2);
|
|
116
|
+
|
|
117
|
+
if (neededHeight !== this.height) {
|
|
118
|
+
this.height = neededHeight;
|
|
119
|
+
// Notifier un changement de dimensions
|
|
120
|
+
if (this.onResize) this.onResize(this.width, this.height);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Direction 'row' (logique similaire pour l'alignement horizontal)
|
|
125
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
126
|
+
const child = this.children[i];
|
|
127
|
+
|
|
76
128
|
child.x = currentX;
|
|
77
129
|
child.y = currentY;
|
|
130
|
+
|
|
131
|
+
// Gérer l'alignement vertical
|
|
78
132
|
if (this.align === 'center') {
|
|
79
|
-
child.y = (this.height - child.height) / 2;
|
|
133
|
+
child.y = this.y + (this.height - child.height) / 2;
|
|
80
134
|
} else if (this.align === 'end') {
|
|
81
|
-
child.y = this.height - child.height - this.padding;
|
|
135
|
+
child.y = this.y + this.height - child.height - this.padding;
|
|
136
|
+
} else if (this.align === 'stretch') {
|
|
137
|
+
child.y = this.y + this.padding;
|
|
138
|
+
child.height = this.height - (this.padding * 2);
|
|
82
139
|
}
|
|
140
|
+
|
|
141
|
+
// Espacement constant entre chaque enfant
|
|
83
142
|
currentX += child.width + this.gap;
|
|
84
143
|
}
|
|
144
|
+
|
|
145
|
+
// Ajuster la largeur automatiquement si autoHeight est activé (pour row)
|
|
146
|
+
if (this.autoHeight) {
|
|
147
|
+
const totalChildrenWidth = this.children.reduce((sum, child) => sum + child.width, 0);
|
|
148
|
+
const totalGapWidth = this.gap * Math.max(0, this.children.length - 1);
|
|
149
|
+
const neededWidth = totalChildrenWidth + totalGapWidth + (this.padding * 2);
|
|
150
|
+
|
|
151
|
+
if (neededWidth !== this.width) {
|
|
152
|
+
this.width = neededWidth;
|
|
153
|
+
// Notifier un changement de dimensions
|
|
154
|
+
if (this.onResize) this.onResize(this.width, this.height);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Génère les paramètres d'ombre selon le niveau d'élévation
|
|
162
|
+
* @param {number} elevation - Niveau d'élévation (0-5)
|
|
163
|
+
* @returns {Object} Configuration de l'ombre
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
getShadowConfig(elevation) {
|
|
167
|
+
const shadows = [
|
|
168
|
+
{ blur: 0, offsetY: 0, color: 'transparent', spread: 0 },
|
|
169
|
+
{ blur: 2, offsetY: 1, color: 'rgba(0,0,0,0.12)', spread: 0 },
|
|
170
|
+
{ blur: 3, offsetY: 1, color: 'rgba(0,0,0,0.14)', spread: 0 },
|
|
171
|
+
{ blur: 4, offsetY: 2, color: 'rgba(0,0,0,0.16)', spread: 0 },
|
|
172
|
+
{ blur: 6, offsetY: 3, color: 'rgba(0,0,0,0.18)', spread: 0 },
|
|
173
|
+
{ blur: 8, offsetY: 4, color: 'rgba(0,0,0,0.20)', spread: 0 },
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
return shadows[Math.min(elevation, shadows.length - 1)];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Dessine l'effet d'ombre selon l'élévation
|
|
181
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
drawShadow(ctx) {
|
|
185
|
+
if (this.elevation <= 0) return;
|
|
186
|
+
|
|
187
|
+
const shadow = this.getShadowConfig(this.elevation);
|
|
188
|
+
|
|
189
|
+
ctx.save();
|
|
190
|
+
|
|
191
|
+
ctx.shadowColor = shadow.color;
|
|
192
|
+
ctx.shadowBlur = shadow.blur;
|
|
193
|
+
ctx.shadowOffsetX = 0;
|
|
194
|
+
ctx.shadowOffsetY = shadow.offsetY;
|
|
195
|
+
|
|
196
|
+
if (this.borderRadius > 0) {
|
|
197
|
+
ctx.beginPath();
|
|
198
|
+
this.roundRect(ctx, this.x, this.y + shadow.offsetY, this.width, this.height, this.borderRadius);
|
|
199
|
+
ctx.fillStyle = this.bgColor === 'transparent' ? 'white' : this.bgColor;
|
|
200
|
+
ctx.fill();
|
|
201
|
+
} else {
|
|
202
|
+
ctx.fillStyle = this.bgColor === 'transparent' ? 'white' : this.bgColor;
|
|
203
|
+
ctx.fillRect(this.x, this.y + shadow.offsetY, this.width, this.height);
|
|
85
204
|
}
|
|
205
|
+
|
|
206
|
+
ctx.restore();
|
|
86
207
|
}
|
|
87
208
|
|
|
88
209
|
/**
|
|
@@ -92,14 +213,12 @@ class Card extends Component {
|
|
|
92
213
|
draw(ctx) {
|
|
93
214
|
ctx.save();
|
|
94
215
|
|
|
95
|
-
//
|
|
216
|
+
// Dessiner l'ombre si elevation > 0
|
|
96
217
|
if (this.elevation > 0) {
|
|
97
|
-
|
|
98
|
-
ctx.shadowBlur = this.elevation * 3;
|
|
99
|
-
ctx.shadowOffsetY = this.elevation;
|
|
218
|
+
this.drawShadow(ctx);
|
|
100
219
|
}
|
|
101
220
|
|
|
102
|
-
//
|
|
221
|
+
// Dessiner le fond de la carte
|
|
103
222
|
if (this.bgColor !== 'transparent') {
|
|
104
223
|
ctx.fillStyle = this.bgColor;
|
|
105
224
|
if (this.borderRadius > 0) {
|
|
@@ -111,30 +230,19 @@ class Card extends Component {
|
|
|
111
230
|
}
|
|
112
231
|
}
|
|
113
232
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
ctx.shadowBlur = 0;
|
|
117
|
-
ctx.shadowOffsetY = 0;
|
|
118
|
-
|
|
119
|
-
// Clipping (optionnel)
|
|
120
|
-
if (this.clipContent && this.borderRadius > 0) {
|
|
233
|
+
// Appliquer le clipping pour le borderRadius (empêche les débordements)
|
|
234
|
+
if (this.borderRadius > 0) {
|
|
121
235
|
ctx.beginPath();
|
|
122
236
|
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
123
237
|
ctx.clip();
|
|
124
238
|
}
|
|
125
239
|
|
|
126
|
-
//
|
|
127
|
-
ctx.save();
|
|
128
|
-
ctx.translate(this.x, this.y);
|
|
129
|
-
|
|
240
|
+
// Dessiner les enfants
|
|
130
241
|
for (let child of this.children) {
|
|
131
|
-
if (child.visible)
|
|
132
|
-
child.draw(ctx);
|
|
133
|
-
}
|
|
242
|
+
if (child.visible) child.draw(ctx);
|
|
134
243
|
}
|
|
135
244
|
|
|
136
245
|
ctx.restore();
|
|
137
|
-
ctx.restore();
|
|
138
246
|
}
|
|
139
247
|
|
|
140
248
|
/**
|
|
@@ -148,6 +256,11 @@ class Card extends Component {
|
|
|
148
256
|
* @private
|
|
149
257
|
*/
|
|
150
258
|
roundRect(ctx, x, y, width, height, radius) {
|
|
259
|
+
if (radius === 0) {
|
|
260
|
+
ctx.rect(x, y, width, height);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
151
264
|
ctx.beginPath();
|
|
152
265
|
ctx.moveTo(x + radius, y);
|
|
153
266
|
ctx.lineTo(x + width - radius, y);
|
|
@@ -165,7 +278,7 @@ class Card extends Component {
|
|
|
165
278
|
* Vérifie si un point est dans les limites
|
|
166
279
|
* @param {number} x - Coordonnée X
|
|
167
280
|
* @param {number} y - Coordonnée Y
|
|
168
|
-
* @returns {boolean} True si le point est dans la
|
|
281
|
+
* @returns {boolean} True si le point est dans la vue
|
|
169
282
|
*/
|
|
170
283
|
isPointInside(x, y) {
|
|
171
284
|
return x >= this.x &&
|
|
@@ -173,6 +286,96 @@ class Card extends Component {
|
|
|
173
286
|
y >= this.y &&
|
|
174
287
|
y <= this.y + this.height;
|
|
175
288
|
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Définit le niveau d'élévation
|
|
292
|
+
* @param {number} elevation - Nouveau niveau d'élévation (0-5)
|
|
293
|
+
*/
|
|
294
|
+
setElevation(elevation) {
|
|
295
|
+
this.elevation = Math.max(0, Math.min(elevation, 5));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Augmente le niveau d'élévation
|
|
300
|
+
*/
|
|
301
|
+
raise() {
|
|
302
|
+
this.setElevation(this.elevation + 1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Réduit le niveau d'élévation
|
|
307
|
+
*/
|
|
308
|
+
lower() {
|
|
309
|
+
this.setElevation(this.elevation - 1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Met à jour la position de la carte (surcharge pour recalculer le layout)
|
|
314
|
+
* @param {number} x - Nouvelle position X
|
|
315
|
+
* @param {number} y - Nouvelle position Y
|
|
316
|
+
*/
|
|
317
|
+
setPosition(x, y) {
|
|
318
|
+
const deltaX = x - this.x;
|
|
319
|
+
const deltaY = y - this.y;
|
|
320
|
+
|
|
321
|
+
super.setPosition(x, y);
|
|
322
|
+
|
|
323
|
+
// Déplacer tous les enfants avec la carte
|
|
324
|
+
for (let child of this.children) {
|
|
325
|
+
child.x += deltaX;
|
|
326
|
+
child.y += deltaY;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Met à jour les dimensions (surcharge pour recalculer le layout)
|
|
332
|
+
* @param {number} width - Nouvelle largeur
|
|
333
|
+
* @param {number} height - Nouvelle hauteur
|
|
334
|
+
*/
|
|
335
|
+
setSize(width, height) {
|
|
336
|
+
super.setSize(width, height);
|
|
337
|
+
this.layout();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Définit l'espacement entre enfants
|
|
342
|
+
* @param {number} gap - Nouvel espacement
|
|
343
|
+
*/
|
|
344
|
+
setGap(gap) {
|
|
345
|
+
this.gap = Math.max(0, gap);
|
|
346
|
+
this.layout();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Définit le padding
|
|
351
|
+
* @param {number} padding - Nouveau padding
|
|
352
|
+
*/
|
|
353
|
+
setPadding(padding) {
|
|
354
|
+
this.padding = Math.max(0, padding);
|
|
355
|
+
this.layout();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Définit la direction du layout
|
|
360
|
+
* @param {string} direction - 'column' ou 'row'
|
|
361
|
+
*/
|
|
362
|
+
setDirection(direction) {
|
|
363
|
+
if (direction === 'column' || direction === 'row') {
|
|
364
|
+
this.direction = direction;
|
|
365
|
+
this.layout();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Définit l'alignement
|
|
371
|
+
* @param {string} align - 'start', 'center', 'end' ou 'stretch'
|
|
372
|
+
*/
|
|
373
|
+
setAlign(align) {
|
|
374
|
+
if (['start', 'center', 'end', 'stretch'].includes(align)) {
|
|
375
|
+
this.align = align;
|
|
376
|
+
this.layout();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
176
379
|
}
|
|
177
380
|
|
|
178
381
|
export default Card;
|