@idealyst/theme 1.2.102 → 1.2.103
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/package.json +2 -2
- package/src/babel/plugin.js +65 -1
- package/src/colorScheme.ts +99 -4
- package/src/defaults.ts +33 -0
- package/src/index.ts +3 -0
- package/src/theme/extensions.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/theme",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.103",
|
|
4
4
|
"description": "Theming system for Idealyst Framework",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"publish:npm": "npm publish"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@idealyst/tooling": "^1.2.
|
|
66
|
+
"@idealyst/tooling": "^1.2.103"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"react-native-unistyles": ">=3.0.0"
|
package/src/babel/plugin.js
CHANGED
|
@@ -66,10 +66,18 @@ function getOrCreateEntry(componentName) {
|
|
|
66
66
|
// AST Deep Merge - Merges style object ASTs at build time
|
|
67
67
|
// ============================================================================
|
|
68
68
|
|
|
69
|
+
// Platform-specific keys used by Unistyles
|
|
70
|
+
const PLATFORM_KEYS = new Set(['_web', '_ios', '_android']);
|
|
71
|
+
|
|
69
72
|
/**
|
|
70
73
|
* Deep merge two ObjectExpression ASTs.
|
|
71
74
|
* Source properties override target properties.
|
|
72
75
|
* Nested objects are recursively merged.
|
|
76
|
+
*
|
|
77
|
+
* Also propagates extension properties into platform-specific blocks (_web, _ios, _android)
|
|
78
|
+
* when those blocks already contain the same key. This ensures that e.g. setting
|
|
79
|
+
* `fontFamily: 'MyFont'` in an extension properly overrides `_web: { fontFamily: 'inherit' }`
|
|
80
|
+
* in the base styles.
|
|
73
81
|
*/
|
|
74
82
|
function mergeObjectExpressions(t, target, source) {
|
|
75
83
|
if (!t.isObjectExpression(target) || !t.isObjectExpression(source)) {
|
|
@@ -90,6 +98,9 @@ function mergeObjectExpressions(t, target, source) {
|
|
|
90
98
|
|
|
91
99
|
const resultProps = [...target.properties];
|
|
92
100
|
|
|
101
|
+
// Collect non-platform source property keys and values for propagation
|
|
102
|
+
const sourceNonPlatformProps = new Map();
|
|
103
|
+
|
|
93
104
|
for (const prop of source.properties) {
|
|
94
105
|
if (!t.isObjectProperty(prop)) continue;
|
|
95
106
|
|
|
@@ -97,6 +108,10 @@ function mergeObjectExpressions(t, target, source) {
|
|
|
97
108
|
t.isStringLiteral(prop.key) ? prop.key.value : null;
|
|
98
109
|
if (!key) continue;
|
|
99
110
|
|
|
111
|
+
if (!PLATFORM_KEYS.has(key)) {
|
|
112
|
+
sourceNonPlatformProps.set(key, prop);
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
const existingProp = targetProps.get(key);
|
|
101
116
|
|
|
102
117
|
if (existingProp) {
|
|
@@ -128,6 +143,48 @@ function mergeObjectExpressions(t, target, source) {
|
|
|
128
143
|
}
|
|
129
144
|
}
|
|
130
145
|
|
|
146
|
+
// Propagate extension properties into platform-specific blocks.
|
|
147
|
+
// If the base has _web: { fontFamily: 'inherit' } and the extension sets
|
|
148
|
+
// fontFamily: 'MyFont' at the top level, we need to also override fontFamily
|
|
149
|
+
// inside the _web block so the platform-specific value doesn't shadow the extension.
|
|
150
|
+
if (sourceNonPlatformProps.size > 0) {
|
|
151
|
+
for (let i = 0; i < resultProps.length; i++) {
|
|
152
|
+
const prop = resultProps[i];
|
|
153
|
+
if (!t.isObjectProperty(prop)) continue;
|
|
154
|
+
|
|
155
|
+
const key = t.isIdentifier(prop.key) ? prop.key.name :
|
|
156
|
+
t.isStringLiteral(prop.key) ? prop.key.value : null;
|
|
157
|
+
if (!key || !PLATFORM_KEYS.has(key)) continue;
|
|
158
|
+
if (!t.isObjectExpression(prop.value)) continue;
|
|
159
|
+
|
|
160
|
+
// Check if any source properties conflict with keys in this platform block
|
|
161
|
+
const platformProps = prop.value.properties;
|
|
162
|
+
let modified = false;
|
|
163
|
+
const newPlatformProps = [...platformProps];
|
|
164
|
+
|
|
165
|
+
for (let j = 0; j < newPlatformProps.length; j++) {
|
|
166
|
+
const platProp = newPlatformProps[j];
|
|
167
|
+
if (!t.isObjectProperty(platProp)) continue;
|
|
168
|
+
|
|
169
|
+
const platKey = t.isIdentifier(platProp.key) ? platProp.key.name :
|
|
170
|
+
t.isStringLiteral(platProp.key) ? platProp.key.value : null;
|
|
171
|
+
if (!platKey) continue;
|
|
172
|
+
|
|
173
|
+
const extProp = sourceNonPlatformProps.get(platKey);
|
|
174
|
+
if (extProp) {
|
|
175
|
+
// Extension has a property that conflicts with this platform block key.
|
|
176
|
+
// Override the platform block value with the extension value.
|
|
177
|
+
newPlatformProps[j] = t.objectProperty(platProp.key, t.cloneDeep(extProp.value));
|
|
178
|
+
modified = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (modified) {
|
|
183
|
+
resultProps[i] = t.objectProperty(prop.key, t.objectExpression(newPlatformProps));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
131
188
|
return t.objectExpression(resultProps);
|
|
132
189
|
}
|
|
133
190
|
|
|
@@ -873,7 +930,10 @@ module.exports = function idealystStylesPlugin({ types: t }) {
|
|
|
873
930
|
opts.processAll ||
|
|
874
931
|
(opts.autoProcessPaths?.some(p => filename.includes(p)));
|
|
875
932
|
|
|
876
|
-
|
|
933
|
+
// extendStyle/overrideStyle must ALWAYS be processed regardless of
|
|
934
|
+
// shouldProcess, since they are called from user code (not just from
|
|
935
|
+
// @idealyst/* packages). Only defineStyle and StyleSheet.create
|
|
936
|
+
// $iterator expansion are gated by autoProcessPaths.
|
|
877
937
|
|
|
878
938
|
// ============================================================
|
|
879
939
|
// Handle extendStyle - Store extension AST for later merging
|
|
@@ -965,6 +1025,10 @@ module.exports = function idealystStylesPlugin({ types: t }) {
|
|
|
965
1025
|
return;
|
|
966
1026
|
}
|
|
967
1027
|
|
|
1028
|
+
// defineStyle and StyleSheet.create are only processed for files
|
|
1029
|
+
// matching autoProcessPaths (i.e., framework packages)
|
|
1030
|
+
if (!shouldProcess) return;
|
|
1031
|
+
|
|
968
1032
|
// ============================================================
|
|
969
1033
|
// Handle defineStyle - Merge with extensions and output StyleSheet.create
|
|
970
1034
|
// ============================================================
|
package/src/colorScheme.ts
CHANGED
|
@@ -1,10 +1,71 @@
|
|
|
1
|
-
import { UnistylesRuntime } from 'react-native-unistyles';
|
|
1
|
+
import { StyleSheet, UnistylesRuntime } from 'react-native-unistyles';
|
|
2
|
+
import type { UnistylesThemes } from 'react-native-unistyles';
|
|
3
|
+
import type { BuiltTheme } from './builder';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Color scheme preference type.
|
|
5
7
|
*/
|
|
6
8
|
export type ColorScheme = 'light' | 'dark';
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Any theme produced by the builder system (fromTheme().build() or createTheme().build()).
|
|
12
|
+
*/
|
|
13
|
+
type AnyBuiltTheme = BuiltTheme<string, string, string, string, string, string, string, string, string>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for configureThemes().
|
|
17
|
+
*/
|
|
18
|
+
export interface ConfigureThemesOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Theme instances keyed by name. Must include at least `light` and `dark`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* { light: customLightTheme, dark: customDarkTheme }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
themes: { light: AnyBuiltTheme; dark: AnyBuiltTheme } & Record<string, AnyBuiltTheme>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Which theme to activate on startup (default: 'light').
|
|
31
|
+
*/
|
|
32
|
+
initialTheme?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configure the theme system. Call this **once** at app startup,
|
|
37
|
+
* before any component renders.
|
|
38
|
+
*
|
|
39
|
+
* This replaces the need to import `StyleSheet` from `react-native-unistyles` directly.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { configureThemes, lightTheme, darkTheme, fromTheme } from '@idealyst/theme';
|
|
44
|
+
*
|
|
45
|
+
* const light = fromTheme(lightTheme).build();
|
|
46
|
+
* const dark = fromTheme(darkTheme).build();
|
|
47
|
+
*
|
|
48
|
+
* configureThemes({ themes: { light, dark } });
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // With a custom initial theme
|
|
54
|
+
* configureThemes({
|
|
55
|
+
* themes: { light, dark, midnight },
|
|
56
|
+
* initialTheme: 'midnight',
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function configureThemes(options: ConfigureThemesOptions): void {
|
|
61
|
+
StyleSheet.configure({
|
|
62
|
+
themes: options.themes as Record<string, object> as UnistylesThemes,
|
|
63
|
+
settings: {
|
|
64
|
+
initialTheme: (options.initialTheme ?? 'light') as keyof UnistylesThemes,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
8
69
|
/**
|
|
9
70
|
* Get the current system/device color scheme preference.
|
|
10
71
|
*
|
|
@@ -27,19 +88,21 @@ export function getColorScheme(): ColorScheme | null {
|
|
|
27
88
|
}
|
|
28
89
|
|
|
29
90
|
/**
|
|
30
|
-
* Theme settings controller
|
|
91
|
+
* Theme settings controller — wraps Unistyles runtime for theme management.
|
|
92
|
+
*
|
|
93
|
+
* Use this instead of importing `UnistylesRuntime` directly.
|
|
31
94
|
*/
|
|
32
95
|
export const ThemeSettings = {
|
|
33
96
|
/**
|
|
34
97
|
* Set the active theme by name with content color scheme.
|
|
35
98
|
*
|
|
36
|
-
* @param themeName - The theme name to activate
|
|
99
|
+
* @param themeName - The theme name to activate (e.g. 'light', 'dark')
|
|
37
100
|
* @param contentColor - The content color scheme ('light' or 'dark')
|
|
38
101
|
* @param animated - Whether to animate the status bar transition (default: false)
|
|
39
102
|
*
|
|
40
103
|
* @example
|
|
41
104
|
* ```typescript
|
|
42
|
-
* ThemeSettings.setTheme('
|
|
105
|
+
* ThemeSettings.setTheme('dark', 'dark');
|
|
43
106
|
* ThemeSettings.setTheme('light', 'light', true); // animated
|
|
44
107
|
* ```
|
|
45
108
|
*/
|
|
@@ -50,4 +113,36 @@ export const ThemeSettings = {
|
|
|
50
113
|
);
|
|
51
114
|
UnistylesRuntime.statusBar.setStyle((contentColor === 'dark' ? 'light' : 'dark') as any, animated);
|
|
52
115
|
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the name of the currently active theme.
|
|
119
|
+
*
|
|
120
|
+
* @returns The current theme name (e.g. 'light', 'dark')
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const current = ThemeSettings.getThemeName(); // 'dark'
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
getThemeName(): string {
|
|
128
|
+
return String(UnistylesRuntime.themeName);
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Enable or disable adaptive (system-following) themes.
|
|
133
|
+
*
|
|
134
|
+
* When enabled, the theme automatically switches to match the device's
|
|
135
|
+
* light/dark mode setting.
|
|
136
|
+
*
|
|
137
|
+
* @param enabled - Whether to follow the system theme
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* ThemeSettings.setAdaptiveThemes(true); // follow system
|
|
142
|
+
* ThemeSettings.setAdaptiveThemes(false); // manual control
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
setAdaptiveThemes(enabled: boolean): void {
|
|
146
|
+
UnistylesRuntime.setAdaptiveThemes(enabled);
|
|
147
|
+
},
|
|
53
148
|
};
|
package/src/defaults.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global component defaults.
|
|
3
|
+
*
|
|
4
|
+
* These values are used as fallbacks when neither the component prop
|
|
5
|
+
* nor a component-specific default is set.
|
|
6
|
+
* Call the setter once at app startup (e.g., in App.tsx).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { setDefaultMaxFontSizeMultiplier } from '@idealyst/theme';
|
|
11
|
+
*
|
|
12
|
+
* setDefaultMaxFontSizeMultiplier(1.5);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
let _defaultMaxFontSizeMultiplier: number | undefined = undefined;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set the global default `maxFontSizeMultiplier` for all text-rendering components.
|
|
20
|
+
* Any component without an explicit prop or component-level default will use this value.
|
|
21
|
+
* Pass `undefined` to clear (no limit).
|
|
22
|
+
*/
|
|
23
|
+
export function setDefaultMaxFontSizeMultiplier(value: number | undefined): void {
|
|
24
|
+
_defaultMaxFontSizeMultiplier = value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the current global default `maxFontSizeMultiplier`.
|
|
29
|
+
* Returns `undefined` if no default has been set.
|
|
30
|
+
*/
|
|
31
|
+
export function getDefaultMaxFontSizeMultiplier(): number | undefined {
|
|
32
|
+
return _defaultMaxFontSizeMultiplier;
|
|
33
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,9 @@ export { useStyleProps, type StyleProps } from './useStyleProps';
|
|
|
41
41
|
// Shadow utility (platform-specific via .native.ts)
|
|
42
42
|
export { shadow, type ShadowOptions, type ShadowStyle } from './shadow';
|
|
43
43
|
|
|
44
|
+
// Component defaults
|
|
45
|
+
export { setDefaultMaxFontSizeMultiplier, getDefaultMaxFontSizeMultiplier } from './defaults';
|
|
46
|
+
|
|
44
47
|
// Animation tokens and utilities
|
|
45
48
|
// Note: Use '@idealyst/theme/animation' for full animation API
|
|
46
49
|
export { durations, easings, presets } from './animation/tokens';
|
package/src/theme/extensions.ts
CHANGED