canvasframework 0.4.6 → 0.4.7

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.
@@ -81,7 +81,6 @@ import DevTools from '../utils/DevTools.js';
81
81
  import InspectionOverlay from '../utils/InspectionOverlay.js';
82
82
  import DevToolsConsole from '../utils/DevToolsConsole.js';
83
83
 
84
-
85
84
  // Features
86
85
  import PullToRefresh from '../features/PullToRefresh.js';
87
86
  import Skeleton from '../features/Skeleton.js';
@@ -105,27 +104,8 @@ import FeatureFlags from '../manager/FeatureFlags.js';
105
104
  // WebGL Adapter
106
105
  import WebGLCanvasAdapter from './WebGLCanvasAdapter.js';
107
106
  import ui, { createRef } from './UIBuilder.js';
107
+ import ThemeManager from './ThemeManager.js';
108
108
 
109
- // theme
110
- export const lightTheme = {
111
- background: '#FFFFFF',
112
- text: '#000000',
113
- primary: '#6200EE',
114
- secondary: '#03DAC6',
115
- buttonText: '#FFFFFF',
116
- buttonBackground: '#6200EE',
117
- border: '#E0E0E0'
118
- };
119
-
120
- export const darkTheme = {
121
- background: '#121212',
122
- text: '#FFFFFF',
123
- primary: '#BB86FC',
124
- secondary: '#03DAC6',
125
- buttonText: '#000000',
126
- buttonBackground: '#BB86FC',
127
- border: '#333333'
128
- };
129
109
 
130
110
  const FIXED_COMPONENT_TYPES = new Set([
131
111
  AppBar,
@@ -179,9 +159,7 @@ class CanvasFramework {
179
159
 
180
160
  this.platform = this.detectPlatform();
181
161
 
182
- // Thèmes
183
- this.lightTheme = lightTheme;
184
- this.darkTheme = darkTheme;
162
+
185
163
  // État actuel + préférence
186
164
  this.themeMode = options.themeMode || 'system'; // 'light', 'dark', 'system'
187
165
  this.userThemeOverride = null; // null = suit system, sinon 'light' ou 'dark'
@@ -197,7 +175,6 @@ class CanvasFramework {
197
175
  }
198
176
 
199
177
  this.components = [];
200
- this.theme = lightTheme; // thème par défaut
201
178
  // ✅ AJOUTER ICI :
202
179
  this._cachedMaxScroll = 0;
203
180
  this._maxScrollDirty = true;
@@ -287,30 +264,18 @@ class CanvasFramework {
287
264
  this.enableDevTools();
288
265
  console.log('DevTools enabled. Press Ctrl+Shift+D to toggle.');
289
266
  }
267
+
268
+ // Initialiser le ThemeManager
269
+ this.themeManager = new ThemeManager(this, {
270
+ lightTheme: options.lightTheme,
271
+ darkTheme: options.darkTheme,
272
+ storageKey: options.themeStorageKey || 'app-theme-mode'
273
+ });
274
+
275
+ // Raccourci pour accéder au thème actuel
276
+ this.theme = this.themeManager.getTheme();
290
277
  }
291
278
 
292
- /**
293
- * Détecte le thème système et applique si mode = 'system'
294
- */
295
- applyThemeFromSystem() {
296
- // ✅ Vérifier que tout est initialisé
297
- if (!this.lightTheme || !this.darkTheme) {
298
- console.warn('Thèmes non initialisés');
299
- return;
300
- }
301
-
302
- if (this.themeMode === 'system') {
303
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
304
- const newTheme = prefersDark ? this.darkTheme : this.lightTheme;
305
- this.setTheme(newTheme);
306
- } else {
307
- // Mode forcé
308
- this.setTheme(
309
- this.themeMode === 'dark' ? this.darkTheme : this.lightTheme
310
- );
311
- }
312
- }
313
-
314
279
  /**
315
280
  * Écoute les changements système (ex: utilisateur bascule dark mode)
316
281
  */
@@ -339,24 +304,34 @@ class CanvasFramework {
339
304
  * @param {'light'|'dark'|'system'} mode - Mode à appliquer
340
305
  * @param {boolean} [save=true] - Sauvegarder le choix utilisateur ?
341
306
  */
342
- setThemeMode(mode, save = true) {
343
- if (!['light', 'dark', 'system'].includes(mode)) {
344
- console.warn('Mode invalide, valeurs acceptées: light, dark, system');
345
- return;
346
- }
347
-
348
- this.themeMode = mode;
349
-
350
- if (save && mode !== 'system') {
351
- this.userThemeOverride = mode;
352
- // Sauvegarde (ex: localStorage ou ton SecureStorage)
353
- localStorage.setItem('themeOverride', mode);
354
- } else if (mode === 'system') {
355
- this.userThemeOverride = null;
356
- localStorage.removeItem('themeOverride');
357
- }
358
-
359
- this.applyThemeFromSystem();
307
+ setThemeMode(mode) {
308
+ this.themeManager.setMode(mode);
309
+ this.theme = this.themeManager.getTheme();
310
+ }
311
+
312
+ /**
313
+ * Obtient une couleur du thème
314
+ */
315
+ getColor(colorName) {
316
+ return this.themeManager.getColor(colorName);
317
+ }
318
+
319
+ /**
320
+ * Ajoute un listener de changement de thème
321
+ */
322
+ onThemeChange(callback) {
323
+ this.themeManager.addListener((theme) => {
324
+ this.theme = theme;
325
+ callback(theme);
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Bascule entre light et dark
331
+ */
332
+ toggleTheme() {
333
+ this.themeManager.toggle();
334
+ this.theme = this.themeManager.getTheme();
360
335
  }
361
336
 
362
337
  /**
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Gestionnaire de thèmes indépendant et fiable
3
+ * Gère automatiquement le light/dark mode avec persistance
4
+ */
5
+ class ThemeManager {
6
+ constructor(framework, options = {}) {
7
+ this.framework = framework;
8
+
9
+ // Thèmes par défaut (peuvent être overridés)
10
+ this.themes = {
11
+ light: options.lightTheme || {
12
+ // Couleurs de base
13
+ background: '#FFFFFF',
14
+ surface: '#F5F5F5',
15
+ surfaceVariant: '#E7E0EC',
16
+
17
+ // Texte
18
+ text: '#1C1B1F',
19
+ textSecondary: '#49454F',
20
+ textDisabled: '#79747E',
21
+
22
+ // Primaire
23
+ primary: '#6750A4',
24
+ onPrimary: '#FFFFFF',
25
+ primaryContainer: '#EADDFF',
26
+ onPrimaryContainer: '#21005D',
27
+
28
+ // Secondaire
29
+ secondary: '#625B71',
30
+ onSecondary: '#FFFFFF',
31
+ secondaryContainer: '#E8DEF8',
32
+ onSecondaryContainer: '#1D192B',
33
+
34
+ // Tertiaire
35
+ tertiary: '#7D5260',
36
+ onTertiary: '#FFFFFF',
37
+ tertiaryContainer: '#FFD8E4',
38
+ onTertiaryContainer: '#31111D',
39
+
40
+ // Erreur
41
+ error: '#B3261E',
42
+ onError: '#FFFFFF',
43
+ errorContainer: '#F9DEDC',
44
+ onErrorContainer: '#410E0B',
45
+
46
+ // Bordures et dividers
47
+ border: '#CAC4D0',
48
+ divider: '#E0E0E0',
49
+ outline: '#79747E',
50
+ outlineVariant: '#CAC4D0',
51
+
52
+ // États
53
+ hover: 'rgba(103, 80, 164, 0.08)',
54
+ pressed: 'rgba(103, 80, 164, 0.12)',
55
+ focus: 'rgba(103, 80, 164, 0.12)',
56
+ disabled: 'rgba(28, 27, 31, 0.12)',
57
+
58
+ // Ombres
59
+ shadow: 'rgba(0, 0, 0, 0.2)',
60
+ elevation1: 'rgba(0, 0, 0, 0.05)',
61
+ elevation2: 'rgba(0, 0, 0, 0.08)',
62
+ elevation3: 'rgba(0, 0, 0, 0.12)',
63
+ },
64
+
65
+ dark: options.darkTheme || {
66
+ // Couleurs de base
67
+ background: '#1C1B1F',
68
+ surface: '#2B2930',
69
+ surfaceVariant: '#49454F',
70
+
71
+ // Texte
72
+ text: '#E6E1E5',
73
+ textSecondary: '#CAC4D0',
74
+ textDisabled: '#938F99',
75
+
76
+ // Primaire
77
+ primary: '#D0BCFF',
78
+ onPrimary: '#381E72',
79
+ primaryContainer: '#4F378B',
80
+ onPrimaryContainer: '#EADDFF',
81
+
82
+ // Secondaire
83
+ secondary: '#CCC2DC',
84
+ onSecondary: '#332D41',
85
+ secondaryContainer: '#4A4458',
86
+ onSecondaryContainer: '#E8DEF8',
87
+
88
+ // Tertiaire
89
+ tertiary: '#EFB8C8',
90
+ onTertiary: '#492532',
91
+ tertiaryContainer: '#633B48',
92
+ onTertiaryContainer: '#FFD8E4',
93
+
94
+ // Erreur
95
+ error: '#F2B8B5',
96
+ onError: '#601410',
97
+ errorContainer: '#8C1D18',
98
+ onErrorContainer: '#F9DEDC',
99
+
100
+ // Bordures et dividers
101
+ border: '#938F99',
102
+ divider: '#3D3D3D',
103
+ outline: '#938F99',
104
+ outlineVariant: '#49454F',
105
+
106
+ // États
107
+ hover: 'rgba(208, 188, 255, 0.08)',
108
+ pressed: 'rgba(208, 188, 255, 0.12)',
109
+ focus: 'rgba(208, 188, 255, 0.12)',
110
+ disabled: 'rgba(230, 225, 229, 0.12)',
111
+
112
+ // Ombres
113
+ shadow: 'rgba(0, 0, 0, 0.8)',
114
+ elevation1: 'rgba(0, 0, 0, 0.3)',
115
+ elevation2: 'rgba(0, 0, 0, 0.4)',
116
+ elevation3: 'rgba(0, 0, 0, 0.5)',
117
+ }
118
+ };
119
+
120
+ // État actuel
121
+ this.currentMode = null; // 'light', 'dark', ou null (system)
122
+ this.currentTheme = null;
123
+
124
+ // Callbacks
125
+ this.listeners = [];
126
+
127
+ // Storage key
128
+ this.storageKey = options.storageKey || 'app-theme-mode';
129
+
130
+ // Initialisation
131
+ this.init();
132
+ }
133
+
134
+ /**
135
+ * Initialise le ThemeManager
136
+ */
137
+ init() {
138
+ // 1. Charger la préférence sauvegardée
139
+ const savedMode = this.loadPreference();
140
+
141
+ // 2. Écouter les changements système
142
+ this.setupSystemListener();
143
+
144
+ // 3. Appliquer le thème
145
+ if (savedMode) {
146
+ this.setMode(savedMode, false); // false = ne pas re-sauvegarder
147
+ } else {
148
+ this.setMode('system', false);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Charge la préférence depuis le localStorage
154
+ */
155
+ loadPreference() {
156
+ try {
157
+ const saved = localStorage.getItem(this.storageKey);
158
+ if (saved && ['light', 'dark', 'system'].includes(saved)) {
159
+ return saved;
160
+ }
161
+ } catch (e) {
162
+ console.warn('Impossible de charger les préférences de thème:', e);
163
+ }
164
+ return null;
165
+ }
166
+
167
+ /**
168
+ * Sauvegarde la préférence dans le localStorage
169
+ */
170
+ savePreference(mode) {
171
+ try {
172
+ localStorage.setItem(this.storageKey, mode);
173
+ } catch (e) {
174
+ console.warn('Impossible de sauvegarder les préférences de thème:', e);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Configure l'écoute des changements système
180
+ */
181
+ setupSystemListener() {
182
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
183
+
184
+ const handleChange = (e) => {
185
+ // Ne réagir que si on est en mode 'system'
186
+ if (this.currentMode === 'system') {
187
+ this.applySystemTheme();
188
+ }
189
+ };
190
+
191
+ // Méthode moderne
192
+ if (mediaQuery.addEventListener) {
193
+ mediaQuery.addEventListener('change', handleChange);
194
+ } else {
195
+ // Fallback pour anciens navigateurs
196
+ mediaQuery.addListener(handleChange);
197
+ }
198
+
199
+ // Sauvegarder pour cleanup
200
+ this.systemMediaQuery = mediaQuery;
201
+ this.systemChangeHandler = handleChange;
202
+ }
203
+
204
+ /**
205
+ * Applique le thème système
206
+ */
207
+ applySystemTheme() {
208
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
209
+ const theme = prefersDark ? this.themes.dark : this.themes.light;
210
+ this.applyTheme(theme);
211
+ }
212
+
213
+ /**
214
+ * Définit le mode de thème
215
+ * @param {'light'|'dark'|'system'} mode
216
+ * @param {boolean} save - Sauvegarder la préférence
217
+ */
218
+ setMode(mode, save = true) {
219
+ if (!['light', 'dark', 'system'].includes(mode)) {
220
+ console.warn(`Mode invalide: ${mode}. Utilisez 'light', 'dark' ou 'system'.`);
221
+ return;
222
+ }
223
+
224
+ this.currentMode = mode;
225
+
226
+ // Sauvegarder si demandé
227
+ if (save) {
228
+ this.savePreference(mode);
229
+ }
230
+
231
+ // Appliquer le thème correspondant
232
+ if (mode === 'system') {
233
+ this.applySystemTheme();
234
+ } else {
235
+ const theme = this.themes[mode];
236
+ this.applyTheme(theme);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Applique un thème au framework
242
+ */
243
+ applyTheme(theme) {
244
+ this.currentTheme = theme;
245
+
246
+ // Mettre à jour le framework
247
+ if (this.framework) {
248
+ this.framework.theme = theme;
249
+
250
+ // Forcer le rendu de tous les composants
251
+ if (this.framework.components) {
252
+ this.framework.components.forEach(comp => {
253
+ if (comp.markDirty) {
254
+ comp.markDirty();
255
+ }
256
+ });
257
+ }
258
+ }
259
+
260
+ // Notifier les listeners
261
+ this.notifyListeners(theme);
262
+ }
263
+
264
+ /**
265
+ * Obtient le mode actuel
266
+ */
267
+ getMode() {
268
+ return this.currentMode;
269
+ }
270
+
271
+ /**
272
+ * Obtient le thème actuel
273
+ */
274
+ getTheme() {
275
+ return this.currentTheme;
276
+ }
277
+
278
+ /**
279
+ * Obtient une couleur spécifique
280
+ */
281
+ getColor(colorName) {
282
+ return this.currentTheme?.[colorName] || '#000000';
283
+ }
284
+
285
+ /**
286
+ * Bascule entre light et dark
287
+ */
288
+ toggle() {
289
+ if (this.currentMode === 'system') {
290
+ // Si en mode system, basculer vers le mode opposé
291
+ const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
292
+ this.setMode(isDark ? 'light' : 'dark');
293
+ } else {
294
+ // Basculer entre light et dark
295
+ this.setMode(this.currentMode === 'light' ? 'dark' : 'light');
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Ajoute un listener de changement de thème
301
+ */
302
+ addListener(callback) {
303
+ if (typeof callback === 'function') {
304
+ this.listeners.push(callback);
305
+ // Appeler immédiatement avec le thème actuel
306
+ callback(this.currentTheme);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Retire un listener
312
+ */
313
+ removeListener(callback) {
314
+ const index = this.listeners.indexOf(callback);
315
+ if (index > -1) {
316
+ this.listeners.splice(index, 1);
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Notifie tous les listeners
322
+ */
323
+ notifyListeners(theme) {
324
+ this.listeners.forEach(callback => {
325
+ try {
326
+ callback(theme);
327
+ } catch (e) {
328
+ console.error('Erreur dans un listener de thème:', e);
329
+ }
330
+ });
331
+ }
332
+
333
+ /**
334
+ * Définit un thème personnalisé
335
+ */
336
+ setCustomTheme(name, theme) {
337
+ this.themes[name] = theme;
338
+ }
339
+
340
+ /**
341
+ * Nettoie les ressources
342
+ */
343
+ destroy() {
344
+ // Retirer l'écoute système
345
+ if (this.systemMediaQuery && this.systemChangeHandler) {
346
+ if (this.systemMediaQuery.removeEventListener) {
347
+ this.systemMediaQuery.removeEventListener('change', this.systemChangeHandler);
348
+ } else {
349
+ this.systemMediaQuery.removeListener(this.systemChangeHandler);
350
+ }
351
+ }
352
+
353
+ // Vider les listeners
354
+ this.listeners = [];
355
+ }
356
+ }
357
+
358
+ export default ThemeManager;
package/index.js CHANGED
@@ -93,7 +93,6 @@ export { default as FirebaseRealtimeDB } from './utils/FirebaseRealtimeDB.js';
93
93
  export { default as PayPalPayment } from './utils/PayPalPayment.js';
94
94
  export { default as StripePayment } from './utils/StripePayment.js';
95
95
 
96
-
97
96
  // Features
98
97
  export { default as PullToRefresh } from './features/PullToRefresh.js';
99
98
  export { default as Skeleton } from './features/Skeleton.js';
@@ -116,7 +115,7 @@ export { default as FeatureFlags } from './manager/FeatureFlags.js';
116
115
 
117
116
  // Version du framework
118
117
 
119
- export const VERSION = '0.4.4';
118
+ export const VERSION = '0.4.7';
120
119
 
121
120
 
122
121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Canvas-based cross-platform UI framework (Material & Cupertino)",
5
5
  "type": "module",
6
6
  "main": "./index.js",