canvasframework 0.3.9 → 0.3.11
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/AppBar.js +164 -70
- package/components/BottomNavigationBar.js +206 -69
- package/components/InputDatalist.js +723 -0
- package/components/InputTags.js +586 -0
- package/components/PasswordInput.js +462 -0
- package/components/TimePicker.js +443 -0
- package/core/CanvasFramework.js +7 -4
- package/index.js +4 -1
- package/package.json +1 -1
package/components/AppBar.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
* Barre d'application supérieure
|
|
4
|
+
* Barre d'application supérieure (Material & Cupertino)
|
|
4
5
|
* @class
|
|
5
6
|
* @extends Component
|
|
6
|
-
* @property {string} title - Titre
|
|
7
|
-
* @property {string|null} leftIcon - Icône gauche ('menu' ou 'back')
|
|
8
|
-
* @property {string|null} rightIcon - Icône droite ('search' ou 'more')
|
|
9
|
-
* @property {Function} onLeftClick - Callback au clic gauche
|
|
10
|
-
* @property {Function} onRightClick - Callback au clic droit
|
|
11
|
-
* @property {string} platform - Plateforme
|
|
12
|
-
* @property {string} bgColor - Couleur de fond
|
|
13
|
-
* @property {string} textColor - Couleur du texte
|
|
14
|
-
* @property {number} elevation - Élévation (ombre)
|
|
15
7
|
*/
|
|
16
8
|
class AppBar extends Component {
|
|
17
9
|
/**
|
|
@@ -19,13 +11,13 @@ class AppBar extends Component {
|
|
|
19
11
|
* @param {CanvasFramework} framework - Framework parent
|
|
20
12
|
* @param {Object} [options={}] - Options de configuration
|
|
21
13
|
* @param {string} [options.title=''] - Titre
|
|
22
|
-
* @param {string} [options.leftIcon] - Icône gauche
|
|
23
|
-
* @param {string} [options.rightIcon] - Icône droite
|
|
14
|
+
* @param {string} [options.leftIcon] - Icône gauche ('menu' ou 'back')
|
|
15
|
+
* @param {string} [options.rightIcon] - Icône droite ('search' ou 'more')
|
|
24
16
|
* @param {Function} [options.onLeftClick] - Callback gauche
|
|
25
17
|
* @param {Function} [options.onRightClick] - Callback droit
|
|
26
18
|
* @param {number} [options.height] - Hauteur (auto selon platform)
|
|
27
|
-
* @param {string} [options.bgColor] - Couleur de fond
|
|
28
|
-
* @param {string} [options.textColor] - Couleur du texte
|
|
19
|
+
* @param {string} [options.bgColor] - Couleur de fond
|
|
20
|
+
* @param {string} [options.textColor] - Couleur du texte
|
|
29
21
|
* @param {number} [options.elevation=4] - Élévation (Material)
|
|
30
22
|
*/
|
|
31
23
|
constructor(framework, options = {}) {
|
|
@@ -36,17 +28,31 @@ class AppBar extends Component {
|
|
|
36
28
|
height: options.height || (framework.platform === 'material' ? 56 : 44),
|
|
37
29
|
...options
|
|
38
30
|
});
|
|
31
|
+
|
|
39
32
|
this.title = options.title || '';
|
|
40
33
|
this.leftIcon = options.leftIcon || null;
|
|
41
34
|
this.rightIcon = options.rightIcon || null;
|
|
42
35
|
this.onLeftClick = options.onLeftClick;
|
|
43
36
|
this.onRightClick = options.onRightClick;
|
|
44
37
|
this.platform = framework.platform;
|
|
45
|
-
this.bgColor = options.bgColor || (framework.platform === 'material' ? '#6200EE' : '#F8F8F8');
|
|
46
|
-
this.textColor = options.textColor || (framework.platform === 'material' ? '#FFFFFF' : '#000000');
|
|
47
|
-
this.elevation = options.elevation !== undefined ? options.elevation : 4;
|
|
48
38
|
|
|
49
|
-
//
|
|
39
|
+
// Couleurs selon la plateforme
|
|
40
|
+
if (this.platform === 'material') {
|
|
41
|
+
this.bgColor = options.bgColor || '#6200EE';
|
|
42
|
+
this.textColor = options.textColor || '#FFFFFF';
|
|
43
|
+
this.elevation = options.elevation !== undefined ? options.elevation : 4;
|
|
44
|
+
} else {
|
|
45
|
+
// iOS : Transparent ou blanc avec blur effect (simulé)
|
|
46
|
+
this.bgColor = options.bgColor || 'rgba(248, 248, 248, 0.95)';
|
|
47
|
+
this.textColor = options.textColor || '#000000';
|
|
48
|
+
this.elevation = 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Ripple effect (Material uniquement)
|
|
52
|
+
this.ripples = [];
|
|
53
|
+
this.leftPressed = false;
|
|
54
|
+
this.rightPressed = false;
|
|
55
|
+
|
|
50
56
|
this.onPress = this.handlePress.bind(this);
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -69,50 +75,123 @@ class AppBar extends Component {
|
|
|
69
75
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
70
76
|
|
|
71
77
|
ctx.shadowColor = 'transparent';
|
|
78
|
+
ctx.shadowBlur = 0;
|
|
79
|
+
ctx.shadowOffsetY = 0;
|
|
72
80
|
|
|
73
81
|
// Bordure inférieure (iOS uniquement)
|
|
74
82
|
if (this.platform === 'cupertino') {
|
|
75
|
-
ctx.strokeStyle = '
|
|
83
|
+
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
|
76
84
|
ctx.lineWidth = 0.5;
|
|
77
85
|
ctx.beginPath();
|
|
78
|
-
ctx.moveTo(this.x, this.y + this.height);
|
|
79
|
-
ctx.lineTo(this.x + this.width, this.y + this.height);
|
|
86
|
+
ctx.moveTo(this.x, this.y + this.height - 0.5);
|
|
87
|
+
ctx.lineTo(this.x + this.width, this.y + this.height - 0.5);
|
|
80
88
|
ctx.stroke();
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
// Ripples pour les boutons (Material)
|
|
92
|
+
if (this.platform === 'material') {
|
|
93
|
+
this.drawRipples(ctx);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Overlay pressed pour iOS
|
|
97
|
+
if (this.platform === 'cupertino') {
|
|
98
|
+
if (this.leftPressed && this.leftIcon) {
|
|
99
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
100
|
+
ctx.beginPath();
|
|
101
|
+
ctx.arc(28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
102
|
+
ctx.fill();
|
|
103
|
+
}
|
|
104
|
+
if (this.rightPressed && this.rightIcon) {
|
|
105
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
106
|
+
ctx.beginPath();
|
|
107
|
+
ctx.arc(this.width - 28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
108
|
+
ctx.fill();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
83
112
|
// Titre
|
|
84
113
|
ctx.fillStyle = this.textColor;
|
|
114
|
+
const titleAlign = this.platform === 'material' && this.leftIcon ? 'left' : 'center';
|
|
115
|
+
const titleX = titleAlign === 'left' ? 72 : this.width / 2;
|
|
85
116
|
ctx.font = `${this.platform === 'material' ? 'bold ' : ''}20px -apple-system, Roboto, sans-serif`;
|
|
86
|
-
ctx.textAlign =
|
|
117
|
+
ctx.textAlign = titleAlign;
|
|
87
118
|
ctx.textBaseline = 'middle';
|
|
88
|
-
ctx.fillText(this.title,
|
|
119
|
+
ctx.fillText(this.title, titleX, this.y + this.height / 2);
|
|
89
120
|
|
|
90
|
-
// Icône gauche
|
|
91
|
-
if (this.leftIcon
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
121
|
+
// Icône gauche
|
|
122
|
+
if (this.leftIcon) {
|
|
123
|
+
const iconColor = this.platform === 'cupertino' ? '#007AFF' : this.textColor;
|
|
124
|
+
if (this.leftIcon === 'menu') {
|
|
125
|
+
this.drawMenuIcon(ctx, 16, this.y + this.height / 2, iconColor);
|
|
126
|
+
} else if (this.leftIcon === 'back') {
|
|
127
|
+
this.drawBackIcon(ctx, 16, this.y + this.height / 2, iconColor);
|
|
128
|
+
}
|
|
95
129
|
}
|
|
96
130
|
|
|
97
131
|
// Icône droite
|
|
98
|
-
if (this.rightIcon
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
if (this.rightIcon) {
|
|
133
|
+
const iconColor = this.platform === 'cupertino' ? '#007AFF' : this.textColor;
|
|
134
|
+
if (this.rightIcon === 'search') {
|
|
135
|
+
this.drawSearchIcon(ctx, this.width - 36, this.y + this.height / 2, iconColor);
|
|
136
|
+
} else if (this.rightIcon === 'more') {
|
|
137
|
+
this.drawMoreIcon(ctx, this.width - 36, this.y + this.height / 2, iconColor);
|
|
138
|
+
}
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
ctx.restore();
|
|
105
142
|
}
|
|
106
143
|
|
|
107
144
|
/**
|
|
108
|
-
* Dessine
|
|
109
|
-
* @
|
|
110
|
-
|
|
111
|
-
|
|
145
|
+
* Dessine les ripples
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
drawRipples(ctx) {
|
|
149
|
+
for (let ripple of this.ripples) {
|
|
150
|
+
ctx.save();
|
|
151
|
+
ctx.globalAlpha = ripple.opacity;
|
|
152
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
|
|
153
|
+
ctx.beginPath();
|
|
154
|
+
ctx.arc(ripple.x, ripple.y, ripple.radius, 0, Math.PI * 2);
|
|
155
|
+
ctx.fill();
|
|
156
|
+
ctx.restore();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Anime les effets ripple
|
|
112
162
|
* @private
|
|
113
163
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
animateRipple() {
|
|
165
|
+
const animate = () => {
|
|
166
|
+
let hasActiveRipples = false;
|
|
167
|
+
|
|
168
|
+
for (let ripple of this.ripples) {
|
|
169
|
+
if (ripple.radius < ripple.maxRadius) {
|
|
170
|
+
ripple.radius += ripple.maxRadius / 15;
|
|
171
|
+
hasActiveRipples = true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (ripple.radius >= ripple.maxRadius * 0.5) {
|
|
175
|
+
ripple.opacity -= 0.05;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
180
|
+
|
|
181
|
+
if (hasActiveRipples) {
|
|
182
|
+
requestAnimationFrame(animate);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
animate();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Dessine l'icône menu (hamburger)
|
|
191
|
+
* @private
|
|
192
|
+
*/
|
|
193
|
+
drawMenuIcon(ctx, x, y, color) {
|
|
194
|
+
ctx.strokeStyle = color;
|
|
116
195
|
ctx.lineWidth = 2;
|
|
117
196
|
ctx.lineCap = 'round';
|
|
118
197
|
for (let i = 0; i < 3; i++) {
|
|
@@ -125,13 +204,10 @@ class AppBar extends Component {
|
|
|
125
204
|
|
|
126
205
|
/**
|
|
127
206
|
* Dessine l'icône retour
|
|
128
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
129
|
-
* @param {number} x - Position X
|
|
130
|
-
* @param {number} y - Position Y
|
|
131
207
|
* @private
|
|
132
208
|
*/
|
|
133
|
-
drawBackIcon(ctx, x, y) {
|
|
134
|
-
ctx.strokeStyle =
|
|
209
|
+
drawBackIcon(ctx, x, y, color) {
|
|
210
|
+
ctx.strokeStyle = color;
|
|
135
211
|
ctx.lineWidth = 2;
|
|
136
212
|
ctx.lineCap = 'round';
|
|
137
213
|
ctx.lineJoin = 'round';
|
|
@@ -144,14 +220,12 @@ class AppBar extends Component {
|
|
|
144
220
|
|
|
145
221
|
/**
|
|
146
222
|
* Dessine l'icône recherche
|
|
147
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
148
|
-
* @param {number} x - Position X
|
|
149
|
-
* @param {number} y - Position Y
|
|
150
223
|
* @private
|
|
151
224
|
*/
|
|
152
|
-
drawSearchIcon(ctx, x, y) {
|
|
153
|
-
ctx.strokeStyle =
|
|
225
|
+
drawSearchIcon(ctx, x, y, color) {
|
|
226
|
+
ctx.strokeStyle = color;
|
|
154
227
|
ctx.lineWidth = 2;
|
|
228
|
+
ctx.lineCap = 'round';
|
|
155
229
|
ctx.beginPath();
|
|
156
230
|
ctx.arc(x + 8, y - 2, 8, 0, Math.PI * 2);
|
|
157
231
|
ctx.stroke();
|
|
@@ -162,58 +236,78 @@ class AppBar extends Component {
|
|
|
162
236
|
}
|
|
163
237
|
|
|
164
238
|
/**
|
|
165
|
-
* Dessine l'icône plus
|
|
166
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
167
|
-
* @param {number} x - Position X
|
|
168
|
-
* @param {number} y - Position Y
|
|
239
|
+
* Dessine l'icône plus (3 dots)
|
|
169
240
|
* @private
|
|
170
241
|
*/
|
|
171
|
-
drawMoreIcon(ctx, x, y) {
|
|
172
|
-
ctx.fillStyle =
|
|
242
|
+
drawMoreIcon(ctx, x, y, color) {
|
|
243
|
+
ctx.fillStyle = color;
|
|
244
|
+
const spacing = this.platform === 'material' ? 10 : 8;
|
|
173
245
|
for (let i = 0; i < 3; i++) {
|
|
174
246
|
ctx.beginPath();
|
|
175
|
-
ctx.arc(x + 12, y -
|
|
247
|
+
ctx.arc(x + 12, y - spacing + i * spacing, 2, 0, Math.PI * 2);
|
|
176
248
|
ctx.fill();
|
|
177
249
|
}
|
|
178
250
|
}
|
|
179
251
|
|
|
180
252
|
/**
|
|
181
253
|
* Vérifie si un point est dans les zones cliquables
|
|
182
|
-
* @param {number} x - Coordonnée X
|
|
183
|
-
* @param {number} y - Coordonnée Y
|
|
184
|
-
* @returns {boolean} True si le point est dans une zone cliquable
|
|
185
254
|
*/
|
|
186
255
|
isPointInside(x, y) {
|
|
187
|
-
// Zones cliquables pour les icônes
|
|
188
256
|
if (y >= this.y && y <= this.y + this.height) {
|
|
189
|
-
if (this.leftIcon && x >= 0 && x <= 56)
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
if (this.rightIcon && x >= this.width - 56 && x <= this.width) {
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
257
|
+
if (this.leftIcon && x >= 0 && x <= 56) return true;
|
|
258
|
+
if (this.rightIcon && x >= this.width - 56 && x <= this.width) return true;
|
|
195
259
|
}
|
|
196
260
|
return false;
|
|
197
261
|
}
|
|
198
262
|
|
|
199
263
|
/**
|
|
200
264
|
* Gère la pression (clic)
|
|
201
|
-
* @param {number} x - Coordonnée X
|
|
202
|
-
* @param {number} y - Coordonnée Y
|
|
203
|
-
* @returns {boolean} True si un clic a été traité
|
|
204
265
|
* @private
|
|
205
266
|
*/
|
|
206
267
|
handlePress(x, y) {
|
|
207
|
-
// Ajuster y avec le scrollOffset si nécessaire
|
|
208
268
|
const adjustedY = y;
|
|
209
269
|
|
|
210
|
-
// Détecter quelle zone a été cliquée
|
|
211
270
|
if (adjustedY >= this.y && adjustedY <= this.y + this.height) {
|
|
271
|
+
// Bouton gauche
|
|
212
272
|
if (this.leftIcon && x >= 0 && x <= 56) {
|
|
273
|
+
// Ripple effect (Material)
|
|
274
|
+
if (this.platform === 'material') {
|
|
275
|
+
this.ripples.push({
|
|
276
|
+
x: 28,
|
|
277
|
+
y: this.y + this.height / 2,
|
|
278
|
+
radius: 0,
|
|
279
|
+
maxRadius: 28,
|
|
280
|
+
opacity: 1
|
|
281
|
+
});
|
|
282
|
+
this.animateRipple();
|
|
283
|
+
} else {
|
|
284
|
+
// iOS pressed state
|
|
285
|
+
this.leftPressed = true;
|
|
286
|
+
setTimeout(() => { this.leftPressed = false; }, 150);
|
|
287
|
+
}
|
|
288
|
+
|
|
213
289
|
if (this.onLeftClick) this.onLeftClick();
|
|
214
290
|
return true;
|
|
215
291
|
}
|
|
292
|
+
|
|
293
|
+
// Bouton droit
|
|
216
294
|
if (this.rightIcon && x >= this.width - 56 && x <= this.width) {
|
|
295
|
+
// Ripple effect (Material)
|
|
296
|
+
if (this.platform === 'material') {
|
|
297
|
+
this.ripples.push({
|
|
298
|
+
x: this.width - 28,
|
|
299
|
+
y: this.y + this.height / 2,
|
|
300
|
+
radius: 0,
|
|
301
|
+
maxRadius: 28,
|
|
302
|
+
opacity: 1
|
|
303
|
+
});
|
|
304
|
+
this.animateRipple();
|
|
305
|
+
} else {
|
|
306
|
+
// iOS pressed state
|
|
307
|
+
this.rightPressed = true;
|
|
308
|
+
setTimeout(() => { this.rightPressed = false; }, 150);
|
|
309
|
+
}
|
|
310
|
+
|
|
217
311
|
if (this.onRightClick) this.onRightClick();
|
|
218
312
|
return true;
|
|
219
313
|
}
|