@idealyst/theme 1.2.101 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/theme",
3
- "version": "1.2.101",
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.101"
66
+ "@idealyst/tooling": "^1.2.103"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "react-native-unistyles": ">=3.0.0"
@@ -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
- if (!shouldProcess) return;
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
  // ============================================================
@@ -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 - wraps Unistyles runtime for theme management.
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('darkBlue', 'dark');
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
  };
@@ -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';
@@ -15,7 +15,6 @@ export interface DefaultTheme {
15
15
  surface: Record<string, ColorValue>;
16
16
  text: Record<string, ColorValue>;
17
17
  border: Record<string, ColorValue>;
18
- card: Record<string, ColorValue>;
19
18
  };
20
19
  sizes: {
21
20
  button: Record<string, ButtonSizeValue>;