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,331 +1,151 @@
1
- import Component from '../core/Component.js';
2
- /**
3
- * Champ de texte avancé avec label flottant, validation et messages d'erreur
4
- * @class
5
- * @extends Component
6
- * @param {Framework} framework - Instance du framework
7
- * @param {Object} [options={}] - Options de configuration
8
- * @param {string} [options.label=''] - Label du champ
9
- * @param {string} [options.value=''] - Valeur initiale
10
- * @param {string} [options.placeholder=''] - Placeholder
11
- * @param {string} [options.helperText=''] - Texte d'aide
12
- * @param {string} [options.errorText=''] - Texte d'erreur
13
- * @param {boolean} [options.error=false] - État d'erreur
14
- * @param {number} [options.fontSize=16] - Taille de police
15
- * @param {Function} [options.onChange] - Callback lors du changement
16
- * @param {number} [options.height=80] - Hauteur totale (inclut label + input + helper)
17
- * @example
18
- * const textField = new TextField(framework, {
19
- * label: 'Email',
20
- * placeholder: 'Entrez votre email',
21
- * helperText: 'Nous ne partagerons jamais votre email',
22
- * onChange: (value) => validateEmail(value)
23
- * });
24
- */
25
- class TextField extends Component {
26
- /**
27
- * @constructs TextField
28
- */
1
+ import Input from './Input.js';
2
+
3
+ class TextField extends Input {
29
4
  constructor(framework, options = {}) {
30
5
  super(framework, options);
31
- /** @type {string} */
6
+
32
7
  this.label = options.label || '';
33
- /** @type {string} */
34
- this.value = options.value || '';
35
- /** @type {string} */
36
- this.placeholder = options.placeholder || '';
37
- /** @type {string} */
38
8
  this.helperText = options.helperText || '';
39
- /** @type {string} */
40
9
  this.errorText = options.errorText || '';
41
- /** @type {boolean} */
42
10
  this.error = options.error || false;
43
- /** @type {string} */
44
- this.platform = framework.platform;
45
- /** @type {boolean} */
46
- this.focused = false;
47
- /** @type {number} */
48
- this.fontSize = options.fontSize || 16;
49
- /** @type {Function|undefined} */
50
- this.onChange = options.onChange;
51
- /** @type {number} */
52
- this.labelY = this.value ? -10 : 20; // Position du label
53
- /** @type {number} */
54
- this.labelFontSize = this.value ? 12 : 16;
55
- /** @type {boolean} */
56
- this.cursorVisible = true;
57
-
58
- // Hauteur pour inclure label + input + helper
59
- this.height = options.height || 80;
60
-
61
- this.onFocus = this.handleFocus.bind(this);
62
- this.onBlur = this.handleBlur.bind(this);
63
-
64
- this.setupHiddenInput();
65
-
66
- // Animation du curseur
67
- /** @type {number} */
68
- this.cursorInterval = setInterval(() => {
69
- if (this.focused) this.cursorVisible = !this.cursorVisible;
70
- }, 500);
71
- }
72
-
73
- /**
74
- * Configure l'input caché dans le DOM
75
- * @private
76
- */
77
- setupHiddenInput() {
78
- let hiddenInput = document.getElementById('hidden-textfield-input');
79
- if (!hiddenInput) {
80
- hiddenInput = document.createElement('input');
81
- hiddenInput.id = 'hidden-textfield-input';
82
- hiddenInput.type = 'text';
83
- hiddenInput.style.position = 'fixed';
84
- hiddenInput.style.opacity = '0';
85
- hiddenInput.style.pointerEvents = 'none';
86
- hiddenInput.style.top = '-100px';
87
- document.body.appendChild(hiddenInput);
88
-
89
- hiddenInput.addEventListener('input', (e) => {
90
- if (this.focused) {
91
- this.value = e.target.value;
92
- if (this.onChange) this.onChange(this.value);
93
- this.animateLabel();
94
- }
95
- });
96
-
97
- hiddenInput.addEventListener('blur', () => {
98
- this.handleBlur();
99
- });
100
- }
101
- /** @type {HTMLInputElement} */
102
- this.hiddenInput = hiddenInput;
11
+
12
+ // Label position (PLUS HAUT que placeholder)
13
+ this.labelRestY = 14; // label quand vide / pas focus, légèrement au-dessus du placeholder
14
+ this.labelFloatY = 4; // label flottant quand focus / value
15
+ this.labelY = this.value ? this.labelFloatY : this.labelRestY;
16
+ this.labelFontSize = this.value ? 12 : 16;
17
+
18
+
19
+ this.labelRestSize = 15;
20
+ this.labelFontSize = this.value
21
+ ? this.labelFloatSize
22
+ : this.labelRestSize;
103
23
  }
104
-
105
- /**
106
- * Anime le label (flottant)
107
- * @private
108
- */
24
+
109
25
  animateLabel() {
110
- const shouldFloat = this.focused || this.value;
111
- const targetY = shouldFloat ? -10 : 20;
112
- const targetSize = shouldFloat ? 12 : 16;
113
-
114
- const animate = () => {
115
- const diffY = targetY - this.labelY;
116
- const diffSize = targetSize - this.labelFontSize;
117
-
118
- if (Math.abs(diffY) < 0.5 && Math.abs(diffSize) < 0.5) {
119
- this.labelY = targetY;
120
- this.labelFontSize = targetSize;
121
- return;
122
- }
123
-
124
- this.labelY += diffY * 0.2;
125
- this.labelFontSize += diffSize * 0.2;
126
-
127
- requestAnimationFrame(animate);
128
- };
129
-
130
- animate();
26
+ const float = this.focused || this.value;
27
+
28
+ this.labelY = float ? this.labelFloatY : this.labelRestY;
29
+ this.labelFontSize = float
30
+ ? this.labelFloatSize
31
+ : this.labelRestSize;
131
32
  }
132
-
133
- /**
134
- * Gère le focus sur le champ
135
- */
136
- handleFocus() {
137
- this.focused = true;
138
- this.cursorVisible = true;
139
- if (this.hiddenInput) {
140
- this.hiddenInput.value = this.value;
141
- const adjustedY = this.y + this.framework.scrollOffset;
142
- this.hiddenInput.style.top = `${adjustedY}px`;
143
- this.hiddenInput.focus();
144
- }
33
+
34
+ onFocus() {
35
+ super.onFocus();
145
36
  this.animateLabel();
146
37
  }
147
-
148
- /**
149
- * Gère la perte de focus
150
- */
151
- handleBlur() {
152
- this.focused = false;
153
- this.cursorVisible = false;
38
+
39
+ onBlur() {
40
+ super.onBlur();
154
41
  this.animateLabel();
155
42
  }
156
-
157
- /**
158
- * Gère le clic sur le champ
159
- */
160
- onClick() {
161
- this.handleFocus();
162
- }
163
-
164
- /**
165
- * Dessine le champ de texte
166
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
167
- */
43
+
168
44
  draw(ctx) {
169
45
  ctx.save();
170
-
171
- const inputY = this.y + 30;
172
- const inputHeight = 40;
173
-
46
+
47
+ const inputY = this.y + 28;
48
+ const inputHeight = 42;
49
+
50
+ /* ================= MATERIAL ================= */
174
51
  if (this.platform === 'material') {
175
- // Material Design TextField
176
-
177
- // Label flottant
178
- const labelColor = this.error ? '#B00020' :
179
- (this.focused ? '#6200EE' : '#757575');
180
- ctx.fillStyle = labelColor;
52
+ // Label
53
+ ctx.fillStyle = this.error
54
+ ? '#B00020'
55
+ : this.focused
56
+ ? '#6200EE'
57
+ : '#757575';
58
+
181
59
  ctx.font = `${this.labelFontSize}px Roboto, sans-serif`;
182
- ctx.textAlign = 'left';
183
- ctx.textBaseline = 'middle';
184
- ctx.fillText(this.label, this.x, this.y + 20 + this.labelY);
185
-
186
- // Ligne de soulignement
187
- const lineColor = this.error ? '#B00020' :
188
- (this.focused ? '#6200EE' : '#CCCCCC');
189
- ctx.strokeStyle = lineColor;
60
+ ctx.textBaseline = 'top';
61
+ ctx.fillText(this.label, this.x, this.y + this.labelY);
62
+
63
+ // Ligne
64
+ ctx.strokeStyle = this.error
65
+ ? '#B00020'
66
+ : this.focused
67
+ ? '#6200EE'
68
+ : '#CCCCCC';
69
+
190
70
  ctx.lineWidth = this.focused ? 2 : 1;
191
71
  ctx.beginPath();
192
72
  ctx.moveTo(this.x, inputY + inputHeight);
193
73
  ctx.lineTo(this.x + this.width, inputY + inputHeight);
194
74
  ctx.stroke();
195
-
196
- // Valeur ou placeholder
197
- const displayText = this.value || (this.focused ? '' : this.placeholder);
198
- ctx.fillStyle = this.value ? '#000000' : '#999999';
75
+
76
+ // Texte / placeholder
77
+ const showPlaceholder = !this.value && !this.focused;
78
+ ctx.fillStyle = showPlaceholder ? '#9E9E9E' : '#000';
199
79
  ctx.font = `${this.fontSize}px Roboto, sans-serif`;
200
80
  ctx.textBaseline = 'middle';
201
- ctx.fillText(displayText, this.x, inputY + inputHeight / 2);
202
-
81
+
82
+ ctx.fillText(
83
+ showPlaceholder ? this.placeholder : this.value,
84
+ this.x,
85
+ inputY + inputHeight / 2
86
+ );
87
+
203
88
  // Curseur
204
89
  if (this.focused && this.cursorVisible) {
205
- const textWidth = ctx.measureText(this.value).width;
90
+ const w = ctx.measureText(this.value).width;
206
91
  ctx.fillStyle = '#6200EE';
207
- ctx.fillRect(this.x + textWidth + 2, inputY + 10, 2, inputHeight - 20);
208
- }
209
-
210
- // Helper text ou error text
211
- const helperColor = this.error ? '#B00020' : '#757575';
212
- const helperMessage = this.error ? this.errorText : this.helperText;
213
-
214
- if (helperMessage) {
215
- ctx.fillStyle = helperColor;
216
- ctx.font = '12px Roboto, sans-serif';
217
- ctx.fillText(helperMessage, this.x, inputY + inputHeight + 20);
218
- }
219
-
220
- } else {
221
- // Cupertino style (label au-dessus)
222
-
223
- if (this.label) {
224
- ctx.fillStyle = '#000000';
225
- ctx.font = 'bold 14px -apple-system, sans-serif';
226
- ctx.textAlign = 'left';
227
- ctx.textBaseline = 'top';
228
- ctx.fillText(this.label, this.x, this.y);
92
+ ctx.fillRect(this.x + w + 2, inputY + 10, 2, inputHeight - 20);
229
93
  }
230
-
231
- // Input box
232
- ctx.strokeStyle = this.error ? '#FF3B30' :
233
- (this.focused ? '#007AFF' : '#C7C7CC');
94
+ }
95
+
96
+ /* ================= CUPERTINO ================= */
97
+ else {
98
+ // Label (toujours visible)
99
+ ctx.fillStyle = '#6D6D72';
100
+ ctx.font = '12px -apple-system, sans-serif';
101
+ ctx.textBaseline = 'top';
102
+ ctx.fillText(this.label, this.x, this.y);
103
+
104
+ // Box
105
+ ctx.strokeStyle = this.error
106
+ ? '#FF3B30'
107
+ : this.focused
108
+ ? '#007AFF'
109
+ : '#C7C7CC';
110
+
234
111
  ctx.lineWidth = 1;
235
112
  ctx.beginPath();
236
- this.roundRect(ctx, this.x, inputY, this.width, inputHeight, 8);
113
+ this.roundRect(
114
+ ctx,
115
+ this.x,
116
+ inputY,
117
+ this.width,
118
+ inputHeight,
119
+ 10
120
+ );
237
121
  ctx.stroke();
238
-
239
- // Valeur ou placeholder
240
- const displayText = this.value || this.placeholder;
241
- ctx.fillStyle = this.value ? '#000000' : '#999999';
122
+
123
+ // Texte / placeholder
124
+ ctx.fillStyle = this.value ? '#000' : '#8E8E93';
242
125
  ctx.font = `${this.fontSize}px -apple-system, sans-serif`;
243
- ctx.textAlign = 'left';
244
126
  ctx.textBaseline = 'middle';
245
- ctx.fillText(displayText, this.x + 10, inputY + inputHeight / 2);
246
-
127
+
128
+ ctx.fillText(
129
+ this.value || this.placeholder,
130
+ this.x + 12,
131
+ inputY + inputHeight / 2
132
+ );
133
+
247
134
  // Curseur
248
135
  if (this.focused && this.cursorVisible) {
249
- const textWidth = ctx.measureText(this.value).width;
136
+ const w = ctx.measureText(this.value).width;
250
137
  ctx.fillStyle = '#007AFF';
251
- ctx.fillRect(this.x + 10 + textWidth + 2, inputY + 10, 2, inputHeight - 20);
252
- }
253
-
254
- // Helper/Error text
255
- if (this.error && this.errorText) {
256
- ctx.fillStyle = '#FF3B30';
257
- ctx.font = '12px -apple-system, sans-serif';
258
- ctx.fillText(this.errorText, this.x, inputY + inputHeight + 8);
259
- } else if (this.helperText) {
260
- ctx.fillStyle = '#8E8E93';
261
- ctx.font = '12px -apple-system, sans-serif';
262
- ctx.fillText(this.helperText, this.x, inputY + inputHeight + 8);
138
+ ctx.fillRect(
139
+ this.x + 12 + w + 2,
140
+ inputY + 10,
141
+ 2,
142
+ inputHeight - 20
143
+ );
263
144
  }
264
145
  }
265
-
146
+
266
147
  ctx.restore();
267
148
  }
268
-
269
- /**
270
- * Dessine un rectangle avec des coins arrondis
271
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
272
- * @param {number} x - Position X
273
- * @param {number} y - Position Y
274
- * @param {number} width - Largeur
275
- * @param {number} height - Hauteur
276
- * @param {number} radius - Rayon des coins
277
- * @private
278
- */
279
- roundRect(ctx, x, y, width, height, radius) {
280
- ctx.beginPath();
281
- ctx.moveTo(x + radius, y);
282
- ctx.lineTo(x + width - radius, y);
283
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
284
- ctx.lineTo(x + width, y + height - radius);
285
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
286
- ctx.lineTo(x + radius, y + height);
287
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
288
- ctx.lineTo(x, y + radius);
289
- ctx.quadraticCurveTo(x, y, x + radius, y);
290
- ctx.closePath();
291
- }
292
-
293
- /**
294
- * Vérifie si un point est à l'intérieur du composant
295
- * @param {number} x - Position X
296
- * @param {number} y - Position Y
297
- * @returns {boolean} True si le point est à l'intérieur
298
- */
299
- isPointInside(x, y) {
300
- return x >= this.x && x <= this.x + this.width &&
301
- y >= this.y && y <= this.y + this.height;
302
- }
303
-
304
- /**
305
- * Définit une erreur sur le champ
306
- * @param {string} errorText - Texte d'erreur à afficher
307
- */
308
- setError(errorText) {
309
- this.error = true;
310
- this.errorText = errorText;
311
- }
312
-
313
- /**
314
- * Efface l'erreur du champ
315
- */
316
- clearError() {
317
- this.error = false;
318
- this.errorText = '';
319
- }
320
-
321
- /**
322
- * Nettoie les ressources (arrête l'animation du curseur)
323
- */
324
- destroy() {
325
- if (this.cursorInterval) {
326
- clearInterval(this.cursorInterval);
327
- }
328
- }
329
149
  }
330
150
 
331
- export default TextField;
151
+ export default TextField;
@@ -66,6 +66,7 @@ import FetchClient from '../utils/FetchClient.js';
66
66
  import GeoLocationService from '../utils/GeoLocationService.js';
67
67
  import WebSocketClient from '../utils/WebSocketClient.js';
68
68
  import AnimationEngine from '../utils/AnimationEngine.js';
69
+ import CryptoManager from '../utils/CryptoManager.js';
69
70
 
70
71
  // Features
71
72
  import PullToRefresh from '../features/PullToRefresh.js';
@@ -825,6 +826,7 @@ class CanvasFramework {
825
826
  comp instanceof Dialog ||
826
827
  comp instanceof Modal ||
827
828
  comp instanceof FAB ||
829
+ comp instanceof Toast ||
828
830
  comp instanceof BottomSheet ||
829
831
  comp instanceof ContextMenu ||
830
832
  comp instanceof OpenStreetMap ||
@@ -1255,6 +1257,7 @@ class CanvasFramework {
1255
1257
  comp instanceof Dialog ||
1256
1258
  comp instanceof Modal ||
1257
1259
  comp instanceof FAB ||
1260
+ comp instanceof Toast ||
1258
1261
  comp instanceof BottomSheet ||
1259
1262
  comp instanceof ContextMenu ||
1260
1263
  comp instanceof OpenStreetMap ||
@@ -1274,10 +1277,3 @@ class CanvasFramework {
1274
1277
  }
1275
1278
 
1276
1279
  export default CanvasFramework;
1277
-
1278
-
1279
-
1280
-
1281
-
1282
-
1283
-
package/core/Component.js CHANGED
@@ -46,8 +46,98 @@ class Component {
46
46
 
47
47
  // Pour détecter les updates
48
48
  this._prevProps = { ...options };
49
+
50
+ // Système de listeners
51
+ this._listeners = new Map();
49
52
  }
50
53
 
54
+ /**
55
+ * Ajoute un listener pour un événement
56
+ * @param {string} event - Nom de l'événement
57
+ * @param {Function} handler - Fonction callback
58
+ * @returns {Component} - Pour le chaînage
59
+ */
60
+ on(event, handler) {
61
+ if (!this._listeners.has(event)) {
62
+ this._listeners.set(event, []);
63
+ }
64
+ this._listeners.get(event).push(handler);
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Retire un listener
70
+ * @param {string} event - Nom de l'événement
71
+ * @param {Function} handler - Fonction à retirer
72
+ * @returns {Component}
73
+ */
74
+ off(event, handler) {
75
+ if (!this._listeners.has(event)) return this;
76
+
77
+ const handlers = this._listeners.get(event);
78
+ const index = handlers.indexOf(handler);
79
+ if (index > -1) {
80
+ handlers.splice(index, 1);
81
+ }
82
+ return this;
83
+ }
84
+
85
+ /**
86
+ * Ajoute un listener qui s'exécute une seule fois
87
+ * @param {string} event - Nom de l'événement
88
+ * @param {Function} handler - Fonction callback
89
+ * @returns {Component}
90
+ */
91
+ once(event, handler) {
92
+ const wrapper = (...args) => {
93
+ handler(...args);
94
+ this.off(event, wrapper);
95
+ };
96
+ return this.on(event, wrapper);
97
+ }
98
+
99
+ /**
100
+ * Émet un événement
101
+ * @param {string} event - Nom de l'événement
102
+ * @param {...any} args - Arguments à passer aux handlers
103
+ * @returns {Component}
104
+ */
105
+ emit(event, ...args) {
106
+ if (!this._listeners.has(event)) return this;
107
+
108
+ const handlers = this._listeners.get(event);
109
+ for (let handler of handlers) {
110
+ try {
111
+ handler(...args);
112
+ } catch (error) {
113
+ console.error(`Error in ${event} handler:`, error);
114
+ }
115
+ }
116
+ return this;
117
+ }
118
+
119
+ /**
120
+ * Retire tous les listeners d'un événement (ou tous)
121
+ * @param {string} [event] - Nom de l'événement (optionnel)
122
+ * @returns {Component}
123
+ */
124
+ removeAllListeners(event) {
125
+ if (event) {
126
+ this._listeners.delete(event);
127
+ } else {
128
+ this._listeners.clear();
129
+ }
130
+ return this;
131
+ }
132
+
133
+ /**
134
+ * Retourne le nombre de listeners pour un événement
135
+ * @param {string} event - Nom de l'événement
136
+ * @returns {number}
137
+ */
138
+ listenerCount(event) {
139
+ return this._listeners.has(event) ? this._listeners.get(event).length : 0;
140
+ }
51
141
  /* =======================
52
142
  LIFECYCLE HOOKS
53
143
  ======================= */
package/index.js CHANGED
@@ -74,6 +74,7 @@ export { default as FetchClient } from './utils/FetchClient.js';
74
74
  export { default as GeoLocationService } from './utils/GeoLocationService.js';
75
75
  export { default as WebSocketClient } from './utils/WebSocketClient.js';
76
76
  export { default as AnimationEngine } from './utils/AnimationEngine.js';
77
+ export { default as CryptoManager } from './utils/CryptoManager.js';
77
78
 
78
79
  // Features
79
80
  export { default as PullToRefresh } from './features/PullToRefresh.js';
@@ -97,7 +98,7 @@ export { default as FeatureFlags } from './manager/FeatureFlags.js';
97
98
 
98
99
  // Version du framework
99
100
 
100
- export const VERSION = '0.3.10';
101
+ export const VERSION = '0.3.16';
101
102
 
102
103
 
103
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "description": "Canvas-based cross-platform UI framework (Material & Cupertino)",
5
5
  "type": "module",
6
6
  "main": "./index.js",