canvasframework 0.4.5 → 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.
- package/core/CanvasFramework.js +101 -76
- package/core/ThemeManager.js +358 -0
- package/index.js +1 -2
- package/package.json +1 -1
package/core/CanvasFramework.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
/**
|
|
@@ -687,6 +662,9 @@ class CanvasFramework {
|
|
|
687
662
|
beforeEnter: options.beforeEnter,
|
|
688
663
|
afterEnter: options.afterEnter,
|
|
689
664
|
beforeLeave: options.beforeLeave,
|
|
665
|
+
afterLeave: options.afterLeave, // ✅ NOUVEAU
|
|
666
|
+
onEnter: options.onEnter, // ✅ NOUVEAU (alias de afterEnter)
|
|
667
|
+
onLeave: options.onLeave, // ✅ NOUVEAU (alias de beforeLeave)
|
|
690
668
|
transition: options.transition || 'slide'
|
|
691
669
|
};
|
|
692
670
|
|
|
@@ -783,28 +761,55 @@ class CanvasFramework {
|
|
|
783
761
|
|
|
784
762
|
const { route, params, query, pathname } = match;
|
|
785
763
|
|
|
786
|
-
//
|
|
764
|
+
// ===== LIFECYCLE: AVANT DE QUITTER L'ANCIENNE ROUTE =====
|
|
765
|
+
|
|
766
|
+
// Hook beforeLeave de la route actuelle (peut bloquer la navigation)
|
|
787
767
|
const currentRouteData = this.routes.get(this.currentRoute);
|
|
788
768
|
if (currentRouteData?.beforeLeave) {
|
|
789
769
|
const canLeave = await currentRouteData.beforeLeave(this.currentParams, this.currentQuery);
|
|
790
|
-
if (canLeave === false)
|
|
770
|
+
if (canLeave === false) {
|
|
771
|
+
console.log('Navigation cancelled by beforeLeave hook');
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ✅ NOUVEAU : Hook onLeave (alias plus intuitif de beforeLeave, mais ne bloque pas)
|
|
777
|
+
if (currentRouteData?.onLeave) {
|
|
778
|
+
await currentRouteData.onLeave(this.currentParams, this.currentQuery);
|
|
791
779
|
}
|
|
792
780
|
|
|
793
|
-
//
|
|
781
|
+
// ===== LIFECYCLE: AVANT D'ENTRER DANS LA NOUVELLE ROUTE =====
|
|
782
|
+
|
|
783
|
+
// Hook beforeEnter de la nouvelle route (peut bloquer la navigation)
|
|
794
784
|
if (route.beforeEnter) {
|
|
795
785
|
const canEnter = await route.beforeEnter(params, query);
|
|
796
|
-
if (canEnter === false)
|
|
786
|
+
if (canEnter === false) {
|
|
787
|
+
console.log('Navigation cancelled by beforeEnter hook');
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// ✅ NOUVEAU : Hook onEnter (appelé juste avant de créer les composants)
|
|
793
|
+
if (route.onEnter) {
|
|
794
|
+
await route.onEnter(params, query);
|
|
797
795
|
}
|
|
798
796
|
|
|
799
|
-
//
|
|
797
|
+
// ===== SAUVEGARDER L'ÉTAT ACTUEL =====
|
|
798
|
+
|
|
799
|
+
// Sauvegarder l'ancienne route pour l'animation et les hooks
|
|
800
800
|
const oldComponents = [...this.components];
|
|
801
|
+
const oldRoute = this.currentRoute;
|
|
802
|
+
const oldParams = { ...this.currentParams };
|
|
803
|
+
const oldQuery = { ...this.currentQuery };
|
|
801
804
|
|
|
802
|
-
//
|
|
805
|
+
// ===== METTRE À JOUR L'ÉTAT =====
|
|
806
|
+
|
|
803
807
|
this.currentRoute = pathname;
|
|
804
808
|
this.currentParams = params;
|
|
805
809
|
this.currentQuery = query;
|
|
806
810
|
|
|
807
|
-
//
|
|
811
|
+
// ===== GÉRER L'HISTORIQUE =====
|
|
812
|
+
|
|
808
813
|
if (!replace) {
|
|
809
814
|
this.historyIndex++;
|
|
810
815
|
this.history = this.history.slice(0, this.historyIndex);
|
|
@@ -819,28 +824,48 @@ class CanvasFramework {
|
|
|
819
824
|
} else {
|
|
820
825
|
this.history[this.historyIndex] = { path, params, query, state };
|
|
821
826
|
window.history.replaceState(
|
|
822
|
-
|
|
827
|
+
{ route: path, params, query, state },
|
|
823
828
|
'',
|
|
824
829
|
path
|
|
825
830
|
);
|
|
826
831
|
}
|
|
827
832
|
|
|
828
|
-
//
|
|
833
|
+
// ===== CRÉER LES NOUVEAUX COMPOSANTS =====
|
|
834
|
+
|
|
829
835
|
this.components = [];
|
|
830
836
|
if (typeof route.component === 'function') {
|
|
831
837
|
route.component(this, params, query);
|
|
832
838
|
}
|
|
833
839
|
|
|
834
|
-
//
|
|
840
|
+
// ===== LANCER L'ANIMATION DE TRANSITION =====
|
|
841
|
+
|
|
835
842
|
if (animate && !this.transitionState.isTransitioning) {
|
|
836
843
|
const transitionType = transition || route.transition || 'slide';
|
|
837
844
|
this.startTransition(oldComponents, this.components, transitionType, direction);
|
|
838
845
|
}
|
|
839
846
|
|
|
840
|
-
//
|
|
847
|
+
// ===== LIFECYCLE: APRÈS ÊTRE ENTRÉ DANS LA NOUVELLE ROUTE =====
|
|
848
|
+
|
|
849
|
+
// Hook afterEnter (appelé immédiatement après la création des composants)
|
|
841
850
|
if (route.afterEnter) {
|
|
842
851
|
route.afterEnter(params, query);
|
|
843
852
|
}
|
|
853
|
+
|
|
854
|
+
// ✅ NOUVEAU : Hook afterLeave de l'ancienne route (après transition complète)
|
|
855
|
+
if (currentRouteData?.afterLeave) {
|
|
856
|
+
// Si animation, attendre la fin de la transition
|
|
857
|
+
if (animate && this.transitionState.isTransitioning) {
|
|
858
|
+
setTimeout(() => {
|
|
859
|
+
currentRouteData.afterLeave(oldParams, oldQuery);
|
|
860
|
+
}, this.transitionState.duration || 300);
|
|
861
|
+
} else {
|
|
862
|
+
// Pas d'animation, appeler immédiatement
|
|
863
|
+
currentRouteData.afterLeave(oldParams, oldQuery);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// ✅ OPTIONNEL : Marquer les composants comme "dirty" pour forcer le rendu
|
|
868
|
+
this._maxScrollDirty = true;
|
|
844
869
|
}
|
|
845
870
|
|
|
846
871
|
/**
|
|
@@ -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.
|
|
118
|
+
export const VERSION = '0.4.7';
|
|
120
119
|
|
|
121
120
|
|
|
122
121
|
|