canvasframework 0.3.16 → 0.3.18

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.
@@ -93,6 +93,14 @@ class Button extends Component {
93
93
  this.rippleColor = this.hexToRgba(baseColor, 0.3);
94
94
  this.elevation = 0;
95
95
  break;
96
+
97
+ default :
98
+ this.bgColor = options.bgColor || '#FFFFFF';
99
+ this.textColor = options.textColor || baseColor;
100
+ this.borderWidth = 0;
101
+ this.rippleColor = this.hexToRgba(baseColor, 0.2);
102
+ this.elevation = options.elevation || 4;
103
+ break;
96
104
  }
97
105
  }
98
106
 
@@ -127,6 +135,12 @@ class Button extends Component {
127
135
  this.textColor = options.textColor || baseColor;
128
136
  this.borderWidth = 0;
129
137
  break;
138
+
139
+ default :
140
+ this.bgColor = 'transparent';
141
+ this.textColor = options.textColor || baseColor;
142
+ this.borderWidth = 0;
143
+ break;
130
144
  }
131
145
  }
132
146
 
@@ -3,22 +3,9 @@ import Component from '../core/Component.js';
3
3
  /**
4
4
  * Carousel / Slider d'images avec swipe horizontal et lazy load
5
5
  * Compatible Material et Cupertino
6
- * @class
7
- * @extends Component
6
+ * Tout le scroll est géré par le composant
8
7
  */
9
8
  class ImageCarousel extends Component {
10
- /**
11
- * @param {CanvasFramework} framework - Framework parent
12
- * @param {Object} [options={}]
13
- * @param {Array<string>} [options.images=[]] - URLs des images
14
- * @param {number} [options.height=200] - Hauteur du carousel
15
- * @param {number} [options.spacing=16] - Espacement entre images
16
- * @param {number} [options.borderRadius=8] - Coins arrondis
17
- * @param {number} [options.pageIndicatorSize=8] - Taille des dots
18
- * @param {string} [options.pageIndicatorColor='#6200EE'] - Couleur dot actif
19
- * @param {Function} [options.onSwipeEnd] - Callback quand la page change
20
- * @param {Function} [options.onImageClick] - Callback clic sur image
21
- */
22
9
  constructor(framework, options = {}) {
23
10
  super(framework, options);
24
11
 
@@ -33,8 +20,9 @@ class ImageCarousel extends Component {
33
20
  this.pageIndicatorColor = options.pageIndicatorColor || '#6200EE';
34
21
 
35
22
  this.platform = framework.platform;
36
- this.startX = 0;
23
+
37
24
  this.isDragging = false;
25
+ this.lastX = 0;
38
26
  this.velocity = 0;
39
27
 
40
28
  this.onSwipeEnd = options.onSwipeEnd || null;
@@ -42,98 +30,140 @@ class ImageCarousel extends Component {
42
30
 
43
31
  this.loadedImages = Array(this.images.length).fill(null);
44
32
 
45
- // Bind swipe
46
- this.framework.addEventListener('touchstart', this.onTouchStart.bind(this));
47
- this.framework.addEventListener('touchmove', this.onTouchMove.bind(this));
48
- this.framework.addEventListener('touchend', this.onTouchEnd.bind(this));
49
-
33
+ this._setupEventHandlers();
50
34
  this.animateScroll();
51
35
  }
52
36
 
53
- onTouchStart(e) {
54
- const touch = e.touches[0];
55
- this.startX = touch.clientX;
56
- this.isDragging = true;
57
- this.velocity = 0;
58
- this.lastTime = performance.now();
59
- this.lastX = touch.clientX;
60
- }
37
+ // --------------------------
38
+ // Event handlers
39
+ // --------------------------
40
+ _setupEventHandlers() {
41
+ const canvas = this.framework.canvas;
42
+
43
+ // TOUCH
44
+ canvas.addEventListener('touchstart', (e) => {
45
+ if (e.touches.length === 1 && this.isPointInsideTouch(e.touches[0])) {
46
+ this.isDragging = true;
47
+ this.lastX = e.touches[0].clientX;
48
+ this.velocity = 0;
49
+ e.preventDefault();
50
+ }
51
+ });
52
+
53
+ canvas.addEventListener('touchmove', (e) => {
54
+ if (this.isDragging && e.touches.length === 1) {
55
+ const delta = e.touches[0].clientX - this.lastX;
56
+ this.scrollX += delta;
57
+ this.velocity = delta;
58
+ this.lastX = e.touches[0].clientX;
59
+
60
+ this._clampScroll();
61
+ this._requestRedraw();
62
+ e.preventDefault();
63
+ }
64
+ });
61
65
 
62
- onTouchMove(e) {
63
- if (!this.isDragging) return;
64
- const touch = e.touches[0];
65
- const delta = touch.clientX - this.startX;
66
- this.scrollX = -this.currentIndex * (this.width + this.spacing) + delta;
67
-
68
- // calculer velocity
69
- const now = performance.now();
70
- const dt = now - this.lastTime;
71
- this.velocity = (touch.clientX - this.lastX) / dt * 16; // approximation
72
- this.lastTime = now;
73
- this.lastX = touch.clientX;
74
- }
66
+ canvas.addEventListener('touchend', () => this._endDrag());
67
+
68
+ // MOUSE
69
+ canvas.addEventListener('mousedown', (e) => {
70
+ if (this.isPointInside(e)) {
71
+ this.isDragging = true;
72
+ this.lastX = e.clientX;
73
+ this.velocity = 0;
74
+ e.preventDefault();
75
+ }
76
+ });
77
+
78
+ canvas.addEventListener('mousemove', (e) => {
79
+ if (this.isDragging) {
80
+ const delta = e.clientX - this.lastX;
81
+ this.scrollX += delta;
82
+ this.velocity = delta;
83
+ this.lastX = e.clientX;
84
+
85
+ this._clampScroll();
86
+ this._requestRedraw();
87
+ }
88
+ });
75
89
 
76
- onTouchEnd() {
77
- if (!this.isDragging) return;
90
+ canvas.addEventListener('mouseup', () => this._endDrag());
91
+ canvas.addEventListener('mouseleave', () => this._endDrag());
92
+ }
78
93
 
79
- // momentum scroll
80
- const momentumThreshold = this.width / 3;
81
- let targetIndex = this.currentIndex;
94
+ _endDrag() {
95
+ if (this.isDragging) {
96
+ this.isDragging = false;
97
+ // Snap à la page la plus proche
98
+ const targetIndex = Math.round(-this.scrollX / (this.width + this.spacing));
99
+ this.currentIndex = Math.min(Math.max(targetIndex, 0), this.images.length - 1);
100
+ this.scrollX = -this.currentIndex * (this.width + this.spacing);
82
101
 
83
- if (this.velocity < -0.5) targetIndex = Math.min(this.currentIndex + 1, this.images.length - 1);
84
- else if (this.velocity > 0.5) targetIndex = Math.max(this.currentIndex - 1, 0);
85
- else {
86
- const deltaIndex = Math.round(-this.scrollX / (this.width + this.spacing)) - this.currentIndex;
87
- if (deltaIndex > 0) targetIndex = Math.min(this.currentIndex + 1, this.images.length - 1);
88
- else if (deltaIndex < 0) targetIndex = Math.max(this.currentIndex - 1, 0);
102
+ if (this.onSwipeEnd) this.onSwipeEnd(this.currentIndex);
89
103
  }
104
+ }
90
105
 
91
- this.currentIndex = targetIndex;
92
- this.scrollX = -this.currentIndex * (this.width + this.spacing);
106
+ _clampScroll() {
107
+ const maxScroll = 0;
108
+ const minScroll = -(this.images.length - 1) * (this.width + this.spacing);
109
+ if (this.scrollX > maxScroll) this.scrollX = maxScroll;
110
+ if (this.scrollX < minScroll) this.scrollX = minScroll;
111
+ }
93
112
 
94
- this.isDragging = false;
95
- this.velocity = 0;
113
+ isPointInsideTouch(touch) {
114
+ const rect = this.framework.canvas.getBoundingClientRect();
115
+ const x = touch.clientX - rect.left;
116
+ const y = touch.clientY - rect.top;
117
+ return this.isPointInside(x, y);
118
+ }
119
+
120
+ isPointInside(x, y) {
121
+ return x >= this.x && x <= this.x + this.width &&
122
+ y >= this.y && y <= this.y + this.height;
123
+ }
96
124
 
97
- if (this.onSwipeEnd) this.onSwipeEnd(this.currentIndex);
125
+ _requestRedraw() {
126
+ if (this.framework.markComponentDirty) this.framework.markComponentDirty(this);
98
127
  }
99
128
 
129
+ // --------------------------
130
+ // Animation / Inertie
131
+ // --------------------------
100
132
  animateScroll() {
101
133
  const animate = () => {
102
134
  if (!this.isDragging) {
103
- // inertia effect
135
+ // inertia
104
136
  if (Math.abs(this.velocity) > 0.1) {
105
137
  this.scrollX += this.velocity;
106
- this.velocity *= 0.95; // friction
138
+ this.velocity *= 0.95;
107
139
 
108
- // clamp
109
- if (this.scrollX > 0) this.scrollX = 0;
110
- const maxScroll = -(this.images.length - 1) * (this.width + this.spacing);
111
- if (this.scrollX < maxScroll) this.scrollX = maxScroll;
140
+ this._clampScroll();
112
141
  } else {
113
- // snap to nearest
142
+ // snap doux vers la page
114
143
  const target = -this.currentIndex * (this.width + this.spacing);
115
144
  this.scrollX += (target - this.scrollX) * 0.2;
116
145
  }
117
146
  }
118
-
119
147
  requestAnimationFrame(animate);
120
148
  };
121
149
  animate();
122
150
  }
123
151
 
152
+ // --------------------------
153
+ // Draw
154
+ // --------------------------
124
155
  draw(ctx) {
125
156
  ctx.save();
126
-
127
157
  const startX = this.x + this.scrollX + this.spacing / 2;
128
158
 
129
159
  for (let i = 0; i < this.images.length; i++) {
130
160
  const imgX = startX + i * (this.width + this.spacing);
131
161
 
132
- // charger lazy image
162
+ // lazy load
133
163
  if (!this.loadedImages[i]) {
134
164
  const img = new Image();
135
165
  img.src = this.images[i];
136
- img.onload = () => { this.loadedImages[i] = img; };
166
+ img.onload = () => { this.loadedImages[i] = img; this._requestRedraw(); };
137
167
  }
138
168
 
139
169
  ctx.save();
@@ -152,10 +182,11 @@ class ImageCarousel extends Component {
152
182
  ctx.textBaseline = 'middle';
153
183
  ctx.fillText('🖼', imgX + this.width / 2, this.y + this.height / 2);
154
184
  }
185
+
155
186
  ctx.restore();
156
187
  }
157
188
 
158
- // Pagination Material
189
+ // pagination Material
159
190
  if (this.platform === 'material') {
160
191
  const dotY = this.y + this.height + 12;
161
192
  const totalWidth = this.images.length * this.pageIndicatorSize * 2;
@@ -183,11 +214,6 @@ class ImageCarousel extends Component {
183
214
  ctx.lineTo(x, y + radius);
184
215
  ctx.quadraticCurveTo(x, y, x + radius, y);
185
216
  }
186
-
187
- isPointInside(x, y) {
188
- return x >= this.x && x <= this.x + this.width &&
189
- y >= this.y && y <= this.y + this.height;
190
- }
191
217
  }
192
218
 
193
219
  export default ImageCarousel;
@@ -414,14 +414,44 @@ class InputTags extends Component {
414
414
 
415
415
  return cursorX;
416
416
  }
417
+
418
+ updateHeight() {
419
+ const maxX = this.x + this.width - 10; // marge droite
420
+ let currentX = this.x + 10;
421
+ let currentY = this.y + 10;
422
+ let lines = 1;
423
+
424
+ for (let i = 0; i < this.tags.length; i++) {
425
+ const tagWidth = this.measureTagWidth(this.tags[i]);
426
+
427
+ if (currentX + tagWidth > maxX) {
428
+ currentX = this.x + 10;
429
+ currentY += this.tagHeight + this.tagSpacing;
430
+ lines++;
431
+ }
432
+
433
+ currentX += tagWidth + this.tagSpacing;
434
+ }
435
+
436
+ // Ajouter la largeur du texte en cours
437
+ const textWidth = this.value.length * (this.fontSize * 0.6);
438
+ if (currentX + textWidth > maxX) {
439
+ lines++;
440
+ }
441
+
442
+ // Mettre à jour la hauteur dynamique
443
+ this.height = Math.max(lines * (this.tagHeight + this.tagSpacing) + 10, 40); // 40 = minHeight
444
+ }
417
445
 
418
446
  /**
419
447
  * Dessine l'input
420
448
  * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
421
449
  */
422
450
  draw(ctx) {
451
+ this.updateHeight();
423
452
  ctx.save();
424
-
453
+
454
+ // Dessin du contour
425
455
  if (this.platform === 'material') {
426
456
  ctx.strokeStyle = this.focused ? '#6200EE' : '#CCCCCC';
427
457
  ctx.lineWidth = this.focused ? 2 : 1;
@@ -436,30 +466,38 @@ class InputTags extends Component {
436
466
  this.roundRect(ctx, this.x, this.y, this.width, this.height, 8);
437
467
  ctx.stroke();
438
468
  }
439
-
469
+
440
470
  // Position de départ pour dessiner les tags
441
471
  let currentX = this.x + 10;
442
- const tagY = this.y + 10;
443
-
472
+ let currentY = this.y + 10;
473
+ const maxX = this.x + this.width - 10;
474
+
444
475
  // Dessiner les tags
445
476
  for (let i = 0; i < this.tags.length; i++) {
446
- this.drawTag(ctx, this.tags[i], currentX, tagY);
447
477
  const tagWidth = this.measureTagWidth(this.tags[i]);
478
+
479
+ // Retour à la ligne si dépassement
480
+ if (currentX + tagWidth > maxX) {
481
+ currentX = this.x + 10;
482
+ currentY += this.tagHeight + this.tagSpacing;
483
+ }
484
+
485
+ this.drawTag(ctx, this.tags[i], currentX, currentY);
448
486
  currentX += tagWidth + this.tagSpacing;
449
487
  }
450
-
451
- // Dessiner le texte en cours
488
+
489
+ // Dessiner le texte en cours ou placeholder
452
490
  if (this.value || (this.focused && this.tags.length === 0)) {
453
491
  ctx.fillStyle = this.value ? '#000000' : '#999999';
454
492
  ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
455
493
  ctx.textAlign = 'left';
456
494
  ctx.textBaseline = 'middle';
457
-
495
+
458
496
  const displayText = this.value || this.placeholder;
459
- const textY = this.y + this.height / 2;
460
-
497
+ const textY = currentY + this.tagHeight / 2;
498
+
461
499
  ctx.fillText(displayText, currentX, textY);
462
-
500
+
463
501
  // Curseur
464
502
  if (this.focused && this.cursorVisible) {
465
503
  const textWidth = ctx.measureText(this.value).width;
@@ -467,7 +505,7 @@ class InputTags extends Component {
467
505
  ctx.fillRect(currentX + textWidth, textY - this.fontSize / 2, 2, this.fontSize);
468
506
  }
469
507
  }
470
-
508
+
471
509
  ctx.restore();
472
510
  }
473
511
 
@@ -1,24 +1,12 @@
1
1
  import Component from '../core/Component.js';
2
2
  import ListItem from '../components/ListItem.js';
3
+
3
4
  /**
4
5
  * Conteneur pour les éléments de liste (ListItems) avec défilement automatique
5
6
  * @class
6
7
  * @extends Component
7
- * @param {Framework} framework - Instance du framework
8
- * @param {Object} [options={}] - Options de configuration
9
- * @param {number} [options.itemHeight=56] - Hauteur de chaque item en pixels
10
- * @param {Function} [options.onItemClick] - Callback appelé lors du clic sur un item
11
- * @param {number} [options.y=0] - Position Y de départ
12
- * @example
13
- * const list = new List(framework, {
14
- * itemHeight: 64,
15
- * onItemClick: (index, itemOptions) => console.log('Item clicked:', index)
16
- * });
17
8
  */
18
9
  class List extends Component {
19
- /**
20
- * @constructs List
21
- */
22
10
  constructor(framework, options = {}) {
23
11
  super(framework, options);
24
12
  /** @type {ListItem[]} */
@@ -28,37 +16,50 @@ class List extends Component {
28
16
  /** @type {Function|undefined} */
29
17
  this.onItemClick = options.onItemClick;
30
18
  /** @type {number} */
31
- this.y = options.y || 0; // Position Y de départ
19
+ this.y = options.y || 0;
32
20
  }
33
21
 
34
22
  /**
35
23
  * Ajoute un item à la liste
36
- * @param {Object} itemOptions - Options pour l'item
37
- * @param {string} itemOptions.text - Texte à afficher
38
- * @param {Function} [itemOptions.onClick] - Callback spécifique à l'item
39
- * @param {Object} [itemOptions.style] - Style optionnel pour l'item
40
- * @returns {ListItem} L'item créé
24
+ * @param {Object|Component} itemOptions - Options pour l'item OU instance de Component (ex: SwipeableListItem)
25
+ * @returns {ListItem|Component} L'item créé ou passé
41
26
  */
42
27
  addItem(itemOptions) {
43
- const item = new ListItem(this.framework, {
44
- ...itemOptions,
45
- x: this.x,
46
- y: this.y + (this.items.length * this.itemHeight),
47
- width: this.width,
48
- height: this.itemHeight, // IMPORTANT: définir la hauteur
49
- onClick: () => {
50
- if (this.onItemClick) {
51
- this.onItemClick(this.items.length, itemOptions);
52
- }
53
- if (itemOptions.onClick) {
54
- itemOptions.onClick();
28
+ let item;
29
+
30
+ // 🔹 Vérifier si c'est déjà un composant instancié (SwipeableListItem)
31
+ if (itemOptions instanceof Component) {
32
+ item = itemOptions;
33
+
34
+ // 🔹 CRITIQUE : Définir la position et taille de l'item
35
+ item.x = this.x;
36
+ item.y = this.y + (this.items.length * this.itemHeight);
37
+ item.width = this.width;
38
+ item.height = this.itemHeight;
39
+
40
+ console.log(`📦 SwipeableListItem positionné à y=${item.y}`);
41
+ } else {
42
+ // Créer un ListItem standard
43
+ item = new ListItem(this.framework, {
44
+ ...itemOptions,
45
+ x: this.x,
46
+ y: this.y + (this.items.length * this.itemHeight),
47
+ width: this.width,
48
+ height: this.itemHeight,
49
+ onClick: () => {
50
+ if (this.onItemClick) {
51
+ this.onItemClick(this.items.length, itemOptions);
52
+ }
53
+ if (itemOptions.onClick) {
54
+ itemOptions.onClick();
55
+ }
55
56
  }
56
- }
57
- });
57
+ });
58
+ }
58
59
 
59
60
  this.items.push(item);
60
- this.framework.add(item); // Ajouter chaque item au framework
61
- this.height = this.items.length * this.itemHeight; // Mettre à jour la hauteur totale
61
+ this.framework.add(item);
62
+ this.height = this.items.length * this.itemHeight;
62
63
 
63
64
  return item;
64
65
  }