canvasframework 0.5.46 → 0.5.47
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/Accordion.js +2 -1
- package/components/Banner.js +140 -183
- package/components/Chip.js +126 -145
- package/components/RadioButton.js +132 -77
- package/components/Stepper.js +255 -230
- package/package.json +1 -1
package/components/Chip.js
CHANGED
|
@@ -1,192 +1,185 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
-
|
|
3
|
-
* Chip (étiquette cliquable)
|
|
4
|
-
* @class
|
|
5
|
-
* @extends Component
|
|
6
|
-
* @property {string} text - Texte
|
|
7
|
-
* @property {string|null} icon - Icône
|
|
8
|
-
* @property {boolean} closable - Peut être fermé
|
|
9
|
-
* @property {string} platform - Plateforme
|
|
10
|
-
* @property {string} bgColor - Couleur de fond
|
|
11
|
-
* @property {string} textColor - Couleur du texte
|
|
12
|
-
* @property {Function} onClose - Callback à la fermeture
|
|
13
|
-
* @property {number} borderRadius - Rayon des coins
|
|
14
|
-
* @property {Object|null} closeButtonRect - Rectangle du bouton fermer
|
|
15
|
-
*/
|
|
2
|
+
|
|
16
3
|
class Chip extends Component {
|
|
17
|
-
/**
|
|
18
|
-
* Crée une instance de Chip
|
|
19
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
20
|
-
* @param {Object} [options={}] - Options de configuration
|
|
21
|
-
* @param {string} [options.text=''] - Texte
|
|
22
|
-
* @param {string} [options.icon] - Icône
|
|
23
|
-
* @param {boolean} [options.closable=true] - Peut être fermé
|
|
24
|
-
* @param {string} [options.bgColor] - Couleur de fond (auto selon platform)
|
|
25
|
-
* @param {string} [options.textColor='#000000'] - Couleur du texte
|
|
26
|
-
* @param {Function} [options.onClose] - Callback à la fermeture
|
|
27
|
-
* @param {number} [options.height=32] - Hauteur
|
|
28
|
-
*/
|
|
29
4
|
constructor(framework, options = {}) {
|
|
30
5
|
super(framework, options);
|
|
6
|
+
|
|
31
7
|
this.text = options.text || '';
|
|
32
8
|
this.icon = options.icon || null;
|
|
33
9
|
this.closable = options.closable !== false;
|
|
34
|
-
this.platform = framework.platform;
|
|
35
|
-
this.bgColor = options.bgColor || (framework.platform === 'material' ? '#E0E0E0' : '#F0F0F0');
|
|
36
|
-
this.textColor = options.textColor || '#000000';
|
|
10
|
+
this.platform = framework.platform; // 'material' ou 'cupertino'
|
|
37
11
|
this.onClose = options.onClose;
|
|
38
|
-
|
|
39
|
-
//
|
|
12
|
+
|
|
13
|
+
// Couleurs par défaut selon platform
|
|
14
|
+
if (this.platform === 'material') {
|
|
15
|
+
this.bgColor = options.bgColor || '#E0E0E0';
|
|
16
|
+
this.textColor = options.textColor || '#1F1F1F';
|
|
17
|
+
this.rippleColor = options.rippleColor || 'rgba(0,0,0,0.12)';
|
|
18
|
+
} else { // Cupertino
|
|
19
|
+
this.bgColor = options.bgColor || 'rgba(242,242,247,0.95)';
|
|
20
|
+
this.textColor = options.textColor || '#000';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Dimensions
|
|
40
24
|
const ctx = framework.ctx;
|
|
41
25
|
ctx.font = '14px -apple-system, sans-serif';
|
|
42
26
|
const textWidth = ctx.measureText(this.text).width;
|
|
43
|
-
const iconWidth = this.icon ?
|
|
27
|
+
const iconWidth = this.icon ? 20 : 0;
|
|
44
28
|
const closeWidth = this.closable ? 24 : 0;
|
|
45
|
-
this.width = iconWidth + textWidth + closeWidth + 24;
|
|
29
|
+
this.width = iconWidth + textWidth + closeWidth + 24;
|
|
46
30
|
this.height = options.height || 32;
|
|
47
31
|
this.borderRadius = this.height / 2;
|
|
48
|
-
|
|
32
|
+
|
|
33
|
+
// Ripple pour Material
|
|
34
|
+
this.ripples = [];
|
|
35
|
+
this.pressed = false;
|
|
36
|
+
|
|
49
37
|
this.closeButtonRect = null;
|
|
50
38
|
this.onPress = this.handlePress.bind(this);
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
addRipple(x, y) {
|
|
42
|
+
const ripple = {
|
|
43
|
+
x, y,
|
|
44
|
+
radius: 0,
|
|
45
|
+
maxRadius: Math.max(this.width, this.height) * 1.5,
|
|
46
|
+
opacity: 0.3
|
|
47
|
+
};
|
|
48
|
+
this.ripples.push(ripple);
|
|
49
|
+
|
|
50
|
+
const animate = () => {
|
|
51
|
+
let active = false;
|
|
52
|
+
for (let r of this.ripples) {
|
|
53
|
+
if (r.radius < r.maxRadius) {
|
|
54
|
+
r.radius += r.maxRadius / 15;
|
|
55
|
+
r.opacity -= 0.03;
|
|
56
|
+
active = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
60
|
+
if (active) requestAnimationFrame(animate);
|
|
61
|
+
};
|
|
62
|
+
animate();
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
draw(ctx) {
|
|
58
66
|
ctx.save();
|
|
59
|
-
|
|
67
|
+
|
|
60
68
|
// Background
|
|
61
|
-
ctx.fillStyle = this.pressed
|
|
69
|
+
ctx.fillStyle = this.pressed && this.platform === 'cupertino'
|
|
70
|
+
? this.darkenColor(this.bgColor, 0.1)
|
|
71
|
+
: this.bgColor;
|
|
72
|
+
|
|
62
73
|
ctx.beginPath();
|
|
63
74
|
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
64
75
|
ctx.fill();
|
|
65
|
-
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
//
|
|
76
|
+
|
|
77
|
+
let offsetX = this.x + 12;
|
|
78
|
+
|
|
79
|
+
// Icon
|
|
69
80
|
if (this.icon) {
|
|
70
81
|
ctx.font = '16px sans-serif';
|
|
82
|
+
ctx.fillStyle = this.textColor;
|
|
71
83
|
ctx.textAlign = 'left';
|
|
72
84
|
ctx.textBaseline = 'middle';
|
|
73
|
-
ctx.
|
|
74
|
-
|
|
75
|
-
currentX += 20;
|
|
85
|
+
ctx.fillText(this.icon, offsetX, this.y + this.height / 2);
|
|
86
|
+
offsetX += 20;
|
|
76
87
|
}
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
ctx.font =
|
|
88
|
+
|
|
89
|
+
// Text
|
|
90
|
+
ctx.font = this.platform === 'material'
|
|
91
|
+
? '500 14px Roboto, sans-serif'
|
|
92
|
+
: '400 14px -apple-system';
|
|
80
93
|
ctx.fillStyle = this.textColor;
|
|
81
|
-
ctx.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
ctx.fillText(this.text, offsetX, this.y + this.height / 2);
|
|
95
|
+
|
|
96
|
+
// Ripple (Material)
|
|
97
|
+
if (this.platform === 'material') {
|
|
98
|
+
ctx.save();
|
|
99
|
+
ctx.beginPath();
|
|
100
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
101
|
+
ctx.clip();
|
|
102
|
+
|
|
103
|
+
this.ripples.forEach(r => {
|
|
104
|
+
ctx.globalAlpha = r.opacity;
|
|
105
|
+
ctx.fillStyle = this.rippleColor;
|
|
106
|
+
ctx.beginPath();
|
|
107
|
+
ctx.arc(this.x + r.x, this.y + r.y, r.radius, 0, Math.PI * 2);
|
|
108
|
+
ctx.fill();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
ctx.restore();
|
|
112
|
+
ctx.globalAlpha = 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Close button
|
|
86
116
|
if (this.closable) {
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
y: closeY - 8,
|
|
93
|
-
width: 16,
|
|
94
|
-
height: 16
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Cercle du bouton (optionnel)
|
|
98
|
-
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
117
|
+
const cx = this.x + this.width - 20;
|
|
118
|
+
const cy = this.y + this.height / 2;
|
|
119
|
+
this.closeButtonRect = { x: cx - 8, y: cy - 8, width: 16, height: 16 };
|
|
120
|
+
|
|
121
|
+
ctx.fillStyle = 'rgba(0,0,0,0.1)';
|
|
99
122
|
ctx.beginPath();
|
|
100
|
-
ctx.arc(
|
|
123
|
+
ctx.arc(cx, cy, 8, 0, Math.PI * 2);
|
|
101
124
|
ctx.fill();
|
|
102
|
-
|
|
103
|
-
// Croix (X)
|
|
125
|
+
|
|
104
126
|
ctx.strokeStyle = this.textColor;
|
|
105
127
|
ctx.lineWidth = 1.5;
|
|
106
128
|
ctx.lineCap = 'round';
|
|
107
129
|
ctx.beginPath();
|
|
108
|
-
ctx.moveTo(
|
|
109
|
-
ctx.lineTo(
|
|
110
|
-
ctx.
|
|
111
|
-
|
|
112
|
-
ctx.beginPath();
|
|
113
|
-
ctx.moveTo(closeX + 4, closeY - 4);
|
|
114
|
-
ctx.lineTo(closeX - 4, closeY + 4);
|
|
130
|
+
ctx.moveTo(cx - 4, cy - 4);
|
|
131
|
+
ctx.lineTo(cx + 4, cy + 4);
|
|
132
|
+
ctx.moveTo(cx + 4, cy - 4);
|
|
133
|
+
ctx.lineTo(cx - 4, cy + 4);
|
|
115
134
|
ctx.stroke();
|
|
116
135
|
}
|
|
117
|
-
|
|
136
|
+
|
|
118
137
|
ctx.restore();
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
/**
|
|
122
|
-
* Gère la pression (clic)
|
|
123
|
-
* @param {number} x - Coordonnée X
|
|
124
|
-
* @param {number} y - Coordonnée Y
|
|
125
|
-
* @private
|
|
126
|
-
*/
|
|
127
140
|
handlePress(x, y) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Vérifier si on clique sur le bouton de fermeture
|
|
141
|
+
// Vérifie bouton close
|
|
131
142
|
if (this.closable && this.closeButtonRect) {
|
|
132
|
-
if (x >= this.closeButtonRect.x &&
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
adjustedY <= this.closeButtonRect.y + this.closeButtonRect.height) {
|
|
136
|
-
if (this.onClose) this.onClose();
|
|
143
|
+
if (x >= this.closeButtonRect.x && x <= this.closeButtonRect.x + this.closeButtonRect.width &&
|
|
144
|
+
y >= this.closeButtonRect.y && y <= this.closeButtonRect.y + this.closeButtonRect.height) {
|
|
145
|
+
this.onClose?.();
|
|
137
146
|
return;
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
if (this.
|
|
149
|
+
|
|
150
|
+
// Ripple Material
|
|
151
|
+
if (this.platform === 'material') {
|
|
152
|
+
this.addRipple(x - this.x, y - this.y);
|
|
153
|
+
} else {
|
|
154
|
+
// Feedback press Cupertino
|
|
155
|
+
this.pressed = true;
|
|
156
|
+
setTimeout(() => { this.pressed = false; this.markDirty(); }, 100);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.onClick?.();
|
|
143
160
|
}
|
|
144
161
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
roundRect(ctx, x, y, width, height, radius) {
|
|
156
|
-
ctx.beginPath();
|
|
157
|
-
ctx.moveTo(x + radius, y);
|
|
158
|
-
ctx.lineTo(x + width - radius, y);
|
|
159
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
160
|
-
ctx.lineTo(x + width, y + height - radius);
|
|
161
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
162
|
-
ctx.lineTo(x + radius, y + height);
|
|
163
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
164
|
-
ctx.lineTo(x, y + radius);
|
|
165
|
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
162
|
+
roundRect(ctx, x, y, w, h, r) {
|
|
163
|
+
ctx.moveTo(x + r, y);
|
|
164
|
+
ctx.lineTo(x + w - r, y);
|
|
165
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
166
|
+
ctx.lineTo(x + w, y + h - r);
|
|
167
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
168
|
+
ctx.lineTo(x + r, y + h);
|
|
169
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
170
|
+
ctx.lineTo(x, y + r);
|
|
171
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
166
172
|
ctx.closePath();
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
* Assombrit une couleur
|
|
171
|
-
* @param {string} color - Couleur
|
|
172
|
-
* @returns {string} Couleur assombrie
|
|
173
|
-
* @private
|
|
174
|
-
*/
|
|
175
|
-
darkenColor(color) {
|
|
176
|
-
// Utiliser la même méthode que Button
|
|
175
|
+
darkenColor(color, factor = 0.2) {
|
|
177
176
|
if (color.startsWith('#')) {
|
|
178
|
-
const
|
|
179
|
-
return `rgb(${Math.
|
|
177
|
+
const { r, g, b } = this.hexToRgb(color);
|
|
178
|
+
return `rgb(${Math.floor(r * (1 - factor))}, ${Math.floor(g * (1 - factor))}, ${Math.floor(b * (1 - factor))})`;
|
|
180
179
|
}
|
|
181
180
|
return color;
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
/**
|
|
185
|
-
* Convertit une couleur hex en RGB
|
|
186
|
-
* @param {string} hex - Couleur hexadécimale
|
|
187
|
-
* @returns {{r: number, g: number, b: number}} Objet RGB
|
|
188
|
-
* @private
|
|
189
|
-
*/
|
|
190
183
|
hexToRgb(hex) {
|
|
191
184
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
192
185
|
return result ? {
|
|
@@ -195,18 +188,6 @@ class Chip extends Component {
|
|
|
195
188
|
b: parseInt(result[3], 16)
|
|
196
189
|
} : { r: 0, g: 0, b: 0 };
|
|
197
190
|
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Vérifie si un point est dans les limites
|
|
201
|
-
* @param {number} x - Coordonnée X
|
|
202
|
-
* @param {number} y - Coordonnée Y
|
|
203
|
-
* @returns {boolean} True si le point est dans le chip
|
|
204
|
-
*/
|
|
205
|
-
isPointInside(x, y) {
|
|
206
|
-
const adjustedY = y - this.framework.scrollOffset;
|
|
207
|
-
return x >= this.x && x <= this.x + this.width &&
|
|
208
|
-
adjustedY >= this.y && adjustedY <= this.y + this.height;
|
|
209
|
-
}
|
|
210
191
|
}
|
|
211
192
|
|
|
212
|
-
export default Chip;
|
|
193
|
+
export default Chip;
|
|
@@ -1,151 +1,206 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* RadioButton Material You & Cupertino
|
|
5
5
|
* @class
|
|
6
6
|
* @extends Component
|
|
7
|
-
* @property {string} group - Groupe de boutons radio
|
|
8
|
-
* @property {boolean} checked - État sélectionné
|
|
9
|
-
* @property {string} label - Texte du label
|
|
10
|
-
* @property {string} platform - Plateforme
|
|
11
|
-
* @property {number} circleSize - Taille du cercle
|
|
12
|
-
* @property {number} circleRadius - Rayon du cercle
|
|
13
|
-
* @property {Function} onChange - Callback au changement
|
|
14
7
|
*/
|
|
15
8
|
class RadioButton extends Component {
|
|
16
|
-
/**
|
|
17
|
-
* Crée une instance de RadioButton
|
|
18
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
19
|
-
* @param {Object} [options={}] - Options de configuration
|
|
20
|
-
* @param {string} [options.group='default'] - Groupe de boutons
|
|
21
|
-
* @param {boolean} [options.checked=false] - État initial
|
|
22
|
-
* @param {string} [options.label=''] - Texte du label
|
|
23
|
-
* @param {Function} [options.onChange] - Callback au changement
|
|
24
|
-
*/
|
|
25
9
|
constructor(framework, options = {}) {
|
|
26
10
|
super(framework, options);
|
|
11
|
+
|
|
27
12
|
this.group = options.group || 'default';
|
|
28
13
|
this.checked = options.checked || false;
|
|
29
14
|
this.label = options.label || '';
|
|
30
|
-
|
|
15
|
+
this.labelColor = options.labelColor || (framework.platform === 'material' ? '#1F1F1F' : '#000000');
|
|
31
16
|
this.platform = framework.platform;
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
|
|
18
|
+
// Cercle
|
|
19
|
+
this.circleSize = 24; // Taille du bouton
|
|
20
|
+
this.circleRadius = 12; // Rayon du cercle extérieur
|
|
21
|
+
this.innerRadius = this.circleRadius * 0.5; // Cercle intérieur proportionnel
|
|
22
|
+
|
|
23
|
+
// Animation feedback
|
|
24
|
+
this.tapAlpha = 0; // Pour iOS
|
|
25
|
+
this.ripple = null; // Pour Material
|
|
26
|
+
this.rippleSpeed = 0.15;
|
|
27
|
+
|
|
34
28
|
this.onChange = options.onChange;
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Définir onClick
|
|
29
|
+
|
|
30
|
+
// Calcul largeur totale avec label
|
|
31
|
+
this.width = this.label ? this.circleSize + 8 + this.getTextWidth(this.label) : this.circleSize;
|
|
32
|
+
this.height = this.circleSize;
|
|
33
|
+
|
|
42
34
|
this.onClick = this.handleClick.bind(this);
|
|
35
|
+
this.selectionProgress = this.checked ? 1 : 0; // 0 = non sélectionné, 1 = sélectionné
|
|
36
|
+
this.animSpeed = 0.2; // vitesse animation
|
|
43
37
|
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Calcule la largeur du texte
|
|
47
|
-
* @param {string} text - Texte à mesurer
|
|
48
|
-
* @returns {number} Largeur du texte
|
|
49
|
-
* @private
|
|
50
|
-
*/
|
|
38
|
+
|
|
51
39
|
getTextWidth(text) {
|
|
52
|
-
// Utiliser le contexte temporaire pour mesurer le texte
|
|
53
40
|
const ctx = this.framework.ctx;
|
|
54
41
|
ctx.save();
|
|
55
|
-
ctx.font = '16px -apple-system
|
|
42
|
+
ctx.font = this.platform === 'material' ? '16px Roboto' : '16px -apple-system';
|
|
56
43
|
const width = ctx.measureText(text).width;
|
|
57
44
|
ctx.restore();
|
|
58
45
|
return width;
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
handleClick() {
|
|
66
|
-
// Décocher les autres du même groupe
|
|
48
|
+
handleClick(x, y) {
|
|
49
|
+
// Décocher les autres du groupe
|
|
67
50
|
for (let comp of this.framework.components) {
|
|
68
51
|
if (comp instanceof RadioButton && comp.group === this.group && comp !== this) {
|
|
69
52
|
comp.checked = false;
|
|
70
53
|
}
|
|
71
54
|
}
|
|
72
55
|
this.checked = true;
|
|
56
|
+
|
|
57
|
+
// Feedback
|
|
58
|
+
if (this.platform === 'material') {
|
|
59
|
+
this.ripple = { x: x - this.x, y: y - this.y, radius: 0, opacity: 0.3 };
|
|
60
|
+
this.animateRipple();
|
|
61
|
+
} else {
|
|
62
|
+
this.tapAlpha = 0.3;
|
|
63
|
+
this.animateTap();
|
|
64
|
+
}
|
|
65
|
+
|
|
73
66
|
if (this.onChange) this.onChange(this.checked);
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
animateRipple() {
|
|
70
|
+
if (!this.ripple) return;
|
|
71
|
+
const step = () => {
|
|
72
|
+
if (!this.ripple) return;
|
|
73
|
+
this.ripple.radius += this.circleSize * this.rippleSpeed;
|
|
74
|
+
this.ripple.opacity -= 0.02;
|
|
75
|
+
|
|
76
|
+
if (this.ripple.opacity <= 0) this.ripple = null;
|
|
77
|
+
else requestAnimationFrame(step);
|
|
78
|
+
this.markDirty();
|
|
79
|
+
};
|
|
80
|
+
step();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
animateTap() {
|
|
84
|
+
const step = () => {
|
|
85
|
+
this.tapAlpha -= 0.03;
|
|
86
|
+
if (this.tapAlpha <= 0) this.tapAlpha = 0;
|
|
87
|
+
else requestAnimationFrame(step);
|
|
88
|
+
this.markDirty();
|
|
89
|
+
};
|
|
90
|
+
step();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
update() {
|
|
94
|
+
const target = this.checked ? 1 : 0;
|
|
95
|
+
this.selectionProgress += (target - this.selectionProgress) * this.animSpeed;
|
|
96
|
+
this.selectionProgress = Math.max(0, Math.min(1, this.selectionProgress));
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
80
101
|
draw(ctx) {
|
|
81
102
|
ctx.save();
|
|
82
|
-
|
|
103
|
+
|
|
83
104
|
const centerX = this.x + this.circleSize / 2;
|
|
84
105
|
const centerY = this.y + this.circleSize / 2;
|
|
85
|
-
|
|
106
|
+
|
|
86
107
|
if (this.platform === 'material') {
|
|
87
|
-
//
|
|
88
|
-
ctx.strokeStyle = this.checked ? '#
|
|
108
|
+
// Cercle extérieur
|
|
109
|
+
ctx.strokeStyle = this.checked ? '#6750A4' : '#666666';
|
|
89
110
|
ctx.lineWidth = 2;
|
|
90
111
|
ctx.beginPath();
|
|
91
112
|
ctx.arc(centerX, centerY, this.circleRadius, 0, Math.PI * 2);
|
|
92
113
|
ctx.stroke();
|
|
93
|
-
|
|
94
|
-
//
|
|
114
|
+
|
|
115
|
+
// Cercle intérieur
|
|
95
116
|
if (this.checked) {
|
|
96
|
-
ctx.fillStyle = '#
|
|
117
|
+
ctx.fillStyle = '#6750A4';
|
|
97
118
|
ctx.beginPath();
|
|
98
|
-
ctx.arc(centerX, centerY,
|
|
119
|
+
ctx.arc(centerX, centerY, this.innerRadius, 0, Math.PI * 2);
|
|
99
120
|
ctx.fill();
|
|
100
121
|
}
|
|
101
|
-
|
|
102
|
-
//
|
|
122
|
+
|
|
123
|
+
// Ripple
|
|
124
|
+
if (this.ripple) {
|
|
125
|
+
ctx.globalAlpha = this.ripple.opacity;
|
|
126
|
+
ctx.fillStyle = '#6750A4';
|
|
127
|
+
ctx.beginPath();
|
|
128
|
+
ctx.arc(this.x + this.ripple.x, this.y + this.ripple.y, this.ripple.radius, 0, Math.PI * 2);
|
|
129
|
+
ctx.fill();
|
|
130
|
+
ctx.globalAlpha = 1;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
// Cupertino
|
|
103
134
|
if (this.checked) {
|
|
104
|
-
// Cercle bleu rempli
|
|
105
135
|
ctx.fillStyle = '#007AFF';
|
|
106
136
|
ctx.beginPath();
|
|
107
137
|
ctx.arc(centerX, centerY, this.circleRadius, 0, Math.PI * 2);
|
|
108
138
|
ctx.fill();
|
|
109
|
-
|
|
110
|
-
// Point blanc
|
|
139
|
+
|
|
140
|
+
// Point central blanc
|
|
111
141
|
ctx.fillStyle = '#FFFFFF';
|
|
112
142
|
ctx.beginPath();
|
|
113
|
-
ctx.arc(centerX, centerY,
|
|
143
|
+
ctx.arc(centerX, centerY, this.innerRadius, 0, Math.PI * 2);
|
|
114
144
|
ctx.fill();
|
|
115
145
|
} else {
|
|
116
|
-
// Cercle gris clair
|
|
117
146
|
ctx.strokeStyle = '#D1D1D6';
|
|
118
147
|
ctx.lineWidth = 1.5;
|
|
119
148
|
ctx.beginPath();
|
|
120
149
|
ctx.arc(centerX, centerY, this.circleRadius, 0, Math.PI * 2);
|
|
121
150
|
ctx.stroke();
|
|
122
151
|
}
|
|
152
|
+
|
|
153
|
+
// Tap overlay
|
|
154
|
+
if (this.tapAlpha > 0) {
|
|
155
|
+
ctx.fillStyle = `rgba(0,0,0,${this.tapAlpha})`;
|
|
156
|
+
ctx.beginPath();
|
|
157
|
+
ctx.arc(centerX, centerY, this.circleRadius, 0, Math.PI * 2);
|
|
158
|
+
ctx.fill();
|
|
159
|
+
}
|
|
123
160
|
}
|
|
124
|
-
|
|
161
|
+
|
|
162
|
+
this.update();
|
|
163
|
+
|
|
164
|
+
// Cercle intérieur avec animation Material
|
|
165
|
+
if (this.platform === 'material') {
|
|
166
|
+
if (this.selectionProgress > 0) {
|
|
167
|
+
ctx.fillStyle = '#6200EE';
|
|
168
|
+
ctx.beginPath();
|
|
169
|
+
ctx.arc(centerX, centerY, 5 * this.selectionProgress, 0, Math.PI*2);
|
|
170
|
+
ctx.fill();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Cercle bleu pour Cupertino
|
|
175
|
+
if (this.platform === 'cupertino' && this.selectionProgress > 0) {
|
|
176
|
+
ctx.fillStyle = '#007AFF';
|
|
177
|
+
ctx.beginPath();
|
|
178
|
+
ctx.arc(centerX, centerY, this.circleRadius * this.selectionProgress, 0, Math.PI*2);
|
|
179
|
+
ctx.fill();
|
|
180
|
+
|
|
181
|
+
// Point blanc au centre
|
|
182
|
+
ctx.fillStyle = '#FFFFFF';
|
|
183
|
+
ctx.beginPath();
|
|
184
|
+
ctx.arc(centerX, centerY, 4 * this.selectionProgress, 0, Math.PI*2);
|
|
185
|
+
ctx.fill();
|
|
186
|
+
}
|
|
187
|
+
|
|
125
188
|
// Label
|
|
126
189
|
if (this.label) {
|
|
127
|
-
ctx.fillStyle = this.labelColor;
|
|
128
|
-
ctx.font = '16px -apple-system
|
|
190
|
+
ctx.fillStyle = this.labelColor;
|
|
191
|
+
ctx.font = this.platform === 'material' ? '16px Roboto' : '16px -apple-system';
|
|
129
192
|
ctx.textAlign = 'left';
|
|
130
193
|
ctx.textBaseline = 'middle';
|
|
131
194
|
ctx.fillText(this.label, this.x + this.circleSize + 8, centerY);
|
|
132
195
|
}
|
|
133
|
-
|
|
196
|
+
|
|
134
197
|
ctx.restore();
|
|
135
198
|
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Vérifie si un point est dans les limites
|
|
139
|
-
* @param {number} x - Coordonnée X
|
|
140
|
-
* @param {number} y - Coordonnée Y
|
|
141
|
-
* @returns {boolean} True si le point est dans le bouton
|
|
142
|
-
*/
|
|
199
|
+
|
|
143
200
|
isPointInside(x, y) {
|
|
144
|
-
return x >= this.x &&
|
|
145
|
-
|
|
146
|
-
y >= this.y &&
|
|
147
|
-
y <= this.y + this.height;
|
|
201
|
+
return x >= this.x && x <= this.x + this.width &&
|
|
202
|
+
y >= this.y && y <= this.y + this.height;
|
|
148
203
|
}
|
|
149
204
|
}
|
|
150
205
|
|
|
151
|
-
export default RadioButton;
|
|
206
|
+
export default RadioButton;
|