canvasframework 0.3.14 → 0.3.16

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.
@@ -1,26 +1,11 @@
1
1
  import Component from '../core/Component.js';
2
+
2
3
  /**
3
- * Composant BottomSheet (feuille modale depuis le bas) avec drag & drop
4
+ * BottomSheet avec styles Material et Cupertino
4
5
  * @class
5
6
  * @extends Component
6
- * @param {Framework} framework - Instance du framework
7
- * @param {Object} [options={}] - Options de configuration
8
- * @param {number} [options.height=framework.height * 0.6] - Hauteur du bottom sheet
9
- * @param {boolean} [options.dragHandle=true] - Afficher la poignée de drag
10
- * @param {boolean} [options.closeOnOverlayClick=true] - Fermer au clic sur l'overlay
11
- * @param {string} [options.bgColor='#FFFFFF'] - Couleur de fond
12
- * @param {number} [options.borderRadius=16] - Rayon des coins arrondis
13
- * @example
14
- * const bottomSheet = new BottomSheet(framework, {
15
- * height: 400,
16
- * bgColor: '#F5F5F5',
17
- * borderRadius: 20
18
- * });
19
7
  */
20
8
  class BottomSheet extends Component {
21
- /**
22
- * @constructs BottomSheet
23
- */
24
9
  constructor(framework, options = {}) {
25
10
  super(framework, {
26
11
  x: 0,
@@ -29,65 +14,53 @@ class BottomSheet extends Component {
29
14
  height: options.height || framework.height * 0.6,
30
15
  visible: false
31
16
  });
32
-
33
- /** @type {Component[]} */
17
+
18
+ this.platform = framework.platform; // material / cupertino
19
+
34
20
  this.children = [];
35
- /** @type {boolean} */
36
21
  this.dragHandle = options.dragHandle !== false;
37
- /** @type {boolean} */
38
22
  this.closeOnOverlayClick = options.closeOnOverlayClick !== false;
39
- /** @type {string} */
40
- this.bgColor = options.bgColor || '#FFFFFF';
41
- /** @type {number} */
42
- this.borderRadius = options.borderRadius || 16;
43
- /** @type {number} */
23
+
24
+ // Styles plateforme
25
+ if (this.platform === 'material') {
26
+ this.bgColor = options.bgColor || '#FFFFFF';
27
+ this.overlayColor = 'rgba(0,0,0,0.5)';
28
+ this.shadowBlur = 20;
29
+ this.shadowOffsetY = -5;
30
+ this.borderRadius = 11;
31
+ } else { // cupertino
32
+ this.bgColor = options.bgColor || 'rgba(255,255,255,0.95)';
33
+ this.overlayColor = 'rgba(0,0,0,0.2)';
34
+ this.shadowBlur = 0;
35
+ this.shadowOffsetY = 0;
36
+ this.borderRadius = options.borderRadius || 20;
37
+ }
38
+
44
39
  this.targetY = framework.height;
45
- /** @type {boolean} */
46
40
  this.isOpen = false;
47
- /** @type {boolean} */
48
41
  this.animating = false;
49
- /** @type {boolean} */
50
42
  this.dragging = false;
51
- /** @type {number} */
52
43
  this.dragStartY = 0;
53
- /** @type {number} */
54
44
  this.dragOffset = 0;
55
- /** @type {number} */
56
- this.overlayOpacity = 0;
57
-
58
- // IMPORTANT: Supprimer les bindings ici et les gérer différemment
45
+ this.lastClickTime = 0;
46
+
59
47
  this.onPress = this.handlePress.bind(this);
60
48
  this.onMove = this.handleMove.bind(this);
61
49
  this.onRelease = this.handleRelease.bind(this);
62
-
63
- // Pour suivre le dernier clic
64
- /** @type {number} */
65
- this.lastClickTime = 0;
66
50
  }
67
-
68
- /**
69
- * Ajoute un enfant au bottom sheet
70
- * @param {Component} child - Composant enfant à ajouter
71
- * @returns {Component} L'enfant ajouté
72
- */
51
+
73
52
  add(child) {
74
53
  this.children.push(child);
75
54
  return child;
76
55
  }
77
-
78
- /**
79
- * Ouvre le bottom sheet avec animation
80
- */
56
+
81
57
  open() {
82
58
  this.visible = true;
83
59
  this.isOpen = true;
84
60
  this.targetY = this.framework.height - this.height;
85
61
  this.animate();
86
62
  }
87
-
88
- /**
89
- * Ferme le bottom sheet avec animation
90
- */
63
+
91
64
  close() {
92
65
  this.isOpen = false;
93
66
  this.targetY = this.framework.height;
@@ -95,237 +68,140 @@ class BottomSheet extends Component {
95
68
  this.visible = false;
96
69
  });
97
70
  }
98
-
99
- /**
100
- * Anime le bottom sheet vers sa position cible
101
- * @param {Function} [callback] - Callback appelé à la fin de l'animation
102
- * @private
103
- */
71
+
104
72
  animate(callback) {
105
73
  if (this.animating) return;
106
74
  this.animating = true;
107
-
75
+
108
76
  const step = () => {
109
- const diff = this.targetY - this.y;
110
-
77
+ let diff = this.targetY - this.y;
78
+
111
79
  if (Math.abs(diff) < 1) {
112
80
  this.y = this.targetY;
113
- this.overlayOpacity = this.isOpen ? 0.5 : 0;
114
81
  this.animating = false;
115
82
  if (callback) callback();
116
83
  return;
117
84
  }
118
-
119
- this.y += diff * 0.2;
120
-
121
- // Animer l'opacité de l'overlay
122
- const progress = 1 - ((this.y - (this.framework.height - this.height)) / this.height);
123
- this.overlayOpacity = Math.max(0, Math.min(0.5, progress * 0.5));
124
-
85
+
86
+ // Animation type spring pour iOS, easing pour Material
87
+ if (this.platform === 'cupertino') {
88
+ diff *= 0.15; // spring
89
+ } else {
90
+ diff *= 0.2; // easing
91
+ }
92
+
93
+ this.y += diff;
94
+
125
95
  requestAnimationFrame(step);
126
96
  };
127
-
97
+
128
98
  step();
129
99
  }
130
-
131
- /**
132
- * Gère le clic/touch sur le bottom sheet
133
- * @param {number} x - Position X du clic
134
- * @param {number} y - Position Y du clic
135
- */
100
+
136
101
  handlePress(x, y) {
137
- // Empêcher les doubles clics rapides
138
102
  const now = Date.now();
139
103
  if (now - this.lastClickTime < 300) return;
140
104
  this.lastClickTime = now;
141
-
142
- // Calculer les coordonnées dans le sheet (sans scrollOffset car le sheet est fixe)
143
- const adjustedY = y; // Pas d'ajustement de scroll pour le BottomSheet
144
-
145
- // Clic sur l'overlay (zone sombre)
146
- if (adjustedY < this.y && this.closeOnOverlayClick) {
105
+
106
+ if (y < this.y && this.closeOnOverlayClick) {
147
107
  this.close();
148
108
  return;
149
109
  }
150
-
151
- // Début du drag sur la poignée
152
- if (this.dragHandle && adjustedY >= this.y && adjustedY <= this.y + 40) {
153
- this.dragging = true;
154
- this.dragStartY = adjustedY;
155
- this.dragOffset = 0;
156
- this.framework.activeComponent = this;
157
- return;
158
- }
159
-
160
- // Vérifier les clics sur les enfants
110
+
111
+ // Clic sur la poignée
112
+ if (this.dragHandle && y >= this.y && y <= this.y + 40) {
113
+ // Fermer le bottom sheet immédiatement
114
+ this.close();
115
+ return;
116
+ }
117
+
118
+ // Gestion clic enfants
161
119
  const contentY = this.y + (this.dragHandle ? 40 : 16);
162
-
163
- // Parcourir les enfants dans l'ordre inverse (du dernier au premier)
164
120
  for (let i = this.children.length - 1; i >= 0; i--) {
165
121
  const child = this.children[i];
166
-
167
122
  if (!child.visible) continue;
168
-
169
- // Calculer les coordonnées absolues de l'enfant
123
+
170
124
  const childAbsX = this.x + 16 + child.x;
171
125
  const childAbsY = contentY + child.y;
172
-
173
- // Vérifier si le clic est dans l'enfant
174
- if (adjustedY >= childAbsY &&
175
- adjustedY <= childAbsY + child.height &&
176
- x >= childAbsX &&
177
- x <= childAbsX + child.width) {
178
-
179
- // Si l'enfant a un onClick, le déclencher
180
- if (child.onClick) {
181
- child.onClick();
182
- return;
183
- }
184
-
185
- // Si l'enfant a un onPress, le déclencher
186
- if (child.onPress) {
187
- // Calculer les coordonnées relatives pour l'enfant
188
- const relativeX = x - childAbsX;
189
- const relativeY = adjustedY - childAbsY;
190
- child.onPress(relativeX, relativeY);
191
- return;
192
- }
193
-
194
- // Marquer l'enfant comme pressé pour l'effet visuel
195
- child.pressed = true;
196
-
197
- // Si c'est un bouton, déclencher son onClick après un délai
198
- if (child instanceof Button || child instanceof FAB) {
199
- setTimeout(() => {
200
- if (child.onClick) child.onClick();
201
- child.pressed = false;
202
- }, 150);
203
- }
126
+
127
+ if (x >= childAbsX && x <= childAbsX + child.width &&
128
+ y >= childAbsY && y <= childAbsY + child.height) {
129
+ if (child.onClick) child.onClick();
204
130
  return;
205
131
  }
206
132
  }
207
133
  }
208
-
209
- /**
210
- * Gère le déplacement pendant le drag
211
- * @param {number} x - Position X actuelle
212
- * @param {number} y - Position Y actuelle
213
- */
134
+
214
135
  handleMove(x, y) {
215
- if (this.dragging) {
216
- const adjustedY = y; // Pas d'ajustement de scroll
217
- this.dragOffset = adjustedY - this.dragStartY;
218
-
219
- // Limiter le drag vers le haut
220
- const newY = (this.framework.height - this.height) + this.dragOffset;
221
- if (newY >= this.framework.height - this.height) {
222
- this.y = newY;
223
-
224
- // Mettre à jour l'opacité de l'overlay
225
- const progress = 1 - ((this.y - (this.framework.height - this.height)) / this.height);
226
- this.overlayOpacity = Math.max(0, Math.min(0.5, progress * 0.5));
227
- }
228
- }
136
+ if (!this.dragging) return;
137
+ this.dragOffset = y - this.dragStartY;
138
+ let newY = (this.framework.height - this.height) + this.dragOffset;
139
+ if (newY >= this.framework.height - this.height) this.y = newY;
229
140
  }
230
-
231
- /**
232
- * Gère le relâchement après un drag
233
- * @param {number} x - Position X du relâchement
234
- * @param {number} y - Position Y du relâchement
235
- */
236
- handleRelease(x, y) {
237
- if (this.dragging) {
238
- this.dragging = false;
239
- this.framework.activeComponent = null;
240
-
241
- // Si on a dragué plus de 30% vers le bas, fermer
242
- if (this.dragOffset > this.height * 0.3) {
243
- this.close();
244
- } else {
245
- // Sinon, revenir à la position ouverte
246
- this.targetY = this.framework.height - this.height;
247
- this.animate();
248
- }
249
- }
250
-
251
- // Réinitialiser l'état pressed pour tous les enfants
252
- for (let child of this.children) {
253
- child.pressed = false;
141
+
142
+ handleRelease() {
143
+ if (!this.dragging) return;
144
+ this.dragging = false;
145
+ this.framework.activeComponent = null;
146
+
147
+ if (this.dragOffset > this.height * 0.3) this.close();
148
+ else {
149
+ this.targetY = this.framework.height - this.height;
150
+ this.animate();
254
151
  }
255
152
  }
256
-
257
- /**
258
- * Dessine le bottom sheet
259
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
260
- */
153
+
261
154
  draw(ctx) {
262
155
  if (!this.visible) return;
263
-
264
156
  ctx.save();
265
-
266
- // Overlay sombre
267
- ctx.fillStyle = `rgba(0, 0, 0, ${this.overlayOpacity})`;
157
+
158
+ // Overlay
159
+ ctx.fillStyle = this.overlayColor;
268
160
  ctx.fillRect(0, 0, this.framework.width, this.framework.height);
269
-
270
- // BottomSheet
161
+
162
+ // Sheet
271
163
  ctx.fillStyle = this.bgColor;
272
- ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
273
- ctx.shadowBlur = 20;
274
- ctx.shadowOffsetY = -5;
275
-
164
+ ctx.shadowColor = this.platform === 'material' ? 'rgba(0,0,0,0.3)' : 'transparent';
165
+ ctx.shadowBlur = this.shadowBlur;
166
+ ctx.shadowOffsetY = this.shadowOffsetY;
167
+
276
168
  ctx.beginPath();
277
169
  this.roundRectTop(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
278
170
  ctx.fill();
279
-
280
171
  ctx.shadowColor = 'transparent';
281
-
282
- // Drag Handle
172
+
173
+ // Drag handle
283
174
  if (this.dragHandle) {
284
- ctx.fillStyle = '#CCCCCC';
175
+ ctx.fillStyle = this.platform === 'material' ? '#CCCCCC' : '#E0E0E0';
285
176
  ctx.beginPath();
286
177
  this.roundRect(ctx, this.width / 2 - 20, this.y + 12, 40, 4, 2);
287
178
  ctx.fill();
288
179
  }
289
-
290
- // Contenu (avec clipping)
180
+
181
+ // Enfants
291
182
  const contentY = this.y + (this.dragHandle ? 40 : 16);
292
183
  const contentHeight = this.height - (this.dragHandle ? 40 : 16);
293
-
184
+
294
185
  ctx.save();
295
186
  ctx.beginPath();
296
187
  ctx.rect(this.x, contentY, this.width, contentHeight);
297
188
  ctx.clip();
298
-
299
- // Dessiner les enfants
189
+
300
190
  for (let child of this.children) {
301
- if (child.visible) {
302
- const originalX = child.x;
303
- const originalY = child.y;
304
-
305
- child.x = this.x + 16 + originalX;
306
- child.y = contentY + originalY;
307
-
308
- child.draw(ctx);
309
-
310
- child.x = originalX;
311
- child.y = originalY;
312
- }
191
+ if (!child.visible) continue;
192
+ const origX = child.x;
193
+ const origY = child.y;
194
+ child.x = this.x + 16 + origX;
195
+ child.y = contentY + origY;
196
+ child.draw(ctx);
197
+ child.x = origX;
198
+ child.y = origY;
313
199
  }
314
-
200
+
315
201
  ctx.restore();
316
202
  ctx.restore();
317
203
  }
318
-
319
- /**
320
- * Dessine un rectangle avec seulement les coins supérieurs arrondis
321
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
322
- * @param {number} x - Position X
323
- * @param {number} y - Position Y
324
- * @param {number} width - Largeur
325
- * @param {number} height - Hauteur
326
- * @param {number} radius - Rayon des coins
327
- * @private
328
- */
204
+
329
205
  roundRectTop(ctx, x, y, width, height, radius) {
330
206
  ctx.moveTo(x + radius, y);
331
207
  ctx.lineTo(x + width - radius, y);
@@ -335,19 +211,9 @@ class BottomSheet extends Component {
335
211
  ctx.lineTo(x, y + radius);
336
212
  ctx.quadraticCurveTo(x, y, x + radius, y);
337
213
  }
338
-
339
- /**
340
- * Dessine un rectangle avec des coins arrondis
341
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
342
- * @param {number} x - Position X
343
- * @param {number} y - Position Y
344
- * @param {number} width - Largeur
345
- * @param {number} height - Hauteur
346
- * @param {number} radius - Rayon des coins
347
- * @private
348
- */
214
+
349
215
  roundRect(ctx, x, y, width, height, radius) {
350
- ctx.beginPath();
216
+ if (radius === 0) return ctx.rect(x, y, width, height);
351
217
  ctx.moveTo(x + radius, y);
352
218
  ctx.lineTo(x + width - radius, y);
353
219
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
@@ -359,16 +225,10 @@ class BottomSheet extends Component {
359
225
  ctx.quadraticCurveTo(x, y, x + radius, y);
360
226
  ctx.closePath();
361
227
  }
362
-
363
- /**
364
- * Vérifie si un point est à l'intérieur du composant
365
- * @param {number} x - Position X
366
- * @param {number} y - Position Y
367
- * @returns {boolean} Toujours true si visible (le composant occupe tout l'écran)
368
- */
228
+
369
229
  isPointInside(x, y) {
370
230
  return this.visible;
371
231
  }
372
232
  }
373
233
 
374
- export default BottomSheet;
234
+ export default BottomSheet;