@shohojdhara/atomix 0.3.6 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +3 -3
  2. package/dist/charts.js +50 -142
  3. package/dist/charts.js.map +1 -1
  4. package/dist/core.js +179 -274
  5. package/dist/core.js.map +1 -1
  6. package/dist/forms.js +50 -142
  7. package/dist/forms.js.map +1 -1
  8. package/dist/heavy.js +179 -274
  9. package/dist/heavy.js.map +1 -1
  10. package/dist/index.d.ts +669 -703
  11. package/dist/index.esm.js +966 -1649
  12. package/dist/index.esm.js.map +1 -1
  13. package/dist/index.js +1211 -1890
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.min.js +1 -1
  16. package/dist/index.min.js.map +1 -1
  17. package/dist/theme.d.ts +163 -334
  18. package/dist/theme.js +774 -1473
  19. package/dist/theme.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/AtomixGlass/AtomixGlass.tsx +128 -356
  22. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +1 -1
  23. package/src/components/Button/Button.tsx +85 -167
  24. package/src/lib/composables/useAtomixGlass.ts +7 -7
  25. package/src/lib/config/loader.ts +2 -3
  26. package/src/lib/constants/components.ts +7 -0
  27. package/src/lib/hooks/usePerformanceMonitor.ts +1 -1
  28. package/src/lib/hooks/useThemeTokens.ts +105 -0
  29. package/src/lib/theme/config/configLoader.ts +60 -219
  30. package/src/lib/theme/config/loader.ts +15 -21
  31. package/src/lib/theme/constants/constants.ts +1 -1
  32. package/src/lib/theme/core/ThemeRegistry.ts +75 -279
  33. package/src/lib/theme/core/composeTheme.ts +14 -64
  34. package/src/lib/theme/core/createTheme.ts +54 -40
  35. package/src/lib/theme/core/createThemeObject.ts +2 -2
  36. package/src/lib/theme/core/index.ts +15 -1
  37. package/src/lib/theme/errors/errors.ts +1 -1
  38. package/src/lib/theme/generators/generateCSSNested.ts +130 -0
  39. package/src/lib/theme/generators/index.ts +6 -0
  40. package/src/lib/theme/index.ts +35 -10
  41. package/src/lib/theme/runtime/ThemeApplicator.ts +1 -1
  42. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +4 -4
  43. package/src/lib/theme/runtime/ThemeProvider.tsx +261 -554
  44. package/src/lib/theme/runtime/index.ts +1 -0
  45. package/src/lib/theme/runtime/useThemeTokens.ts +131 -0
  46. package/src/lib/theme/utils/componentTheming.ts +132 -0
  47. package/src/lib/theme/utils/naming.ts +100 -0
  48. package/src/lib/theme/utils/themeUtils.ts +6 -6
  49. package/src/lib/utils/componentUtils.ts +1 -1
  50. package/src/lib/utils/memoryMonitor.ts +3 -3
  51. package/src/lib/utils/themeNaming.ts +135 -0
package/dist/theme.js CHANGED
@@ -849,13 +849,6 @@ const _includesInstanceProperty = getDefaultExportFromCjs((function(it) {
849
849
  * @param coefficient - Amount to emphasize (0-1), default 0.15
850
850
  * @returns Emphasized hex color
851
851
  */
852
- /**
853
- * Check if a theme is a JS theme (created with createTheme)
854
- */
855
- function isJSTheme(theme) {
856
- return theme && "object" == typeof theme && !0 === theme.__isJSTheme;
857
- }
858
-
859
852
  /**
860
853
  * Theme Adapter
861
854
  *
@@ -875,7 +868,8 @@ function isJSTheme(theme) {
875
868
  * const tokens = themeToDesignTokens(theme);
876
869
  * // Returns: { 'primary': '#7c3aed', ... }
877
870
  * ```
878
- */ function themeToDesignTokens(theme) {
871
+ */
872
+ function themeToDesignTokens(theme) {
879
873
  const tokens = {};
880
874
  // Convert palette colors
881
875
  if (theme.palette) {
@@ -1015,158 +1009,10 @@ function isJSTheme(theme) {
1015
1009
  };
1016
1010
  }
1017
1011
 
1018
- /**
1019
- * Config Loader
1020
- *
1021
- * Load design tokens from atomix.config.ts and convert to flat token format.
1022
- */
1023
- /**
1024
- * Convert nested config tokens to flat DesignTokens format
1025
- *
1026
- * Handles conversion from atomix.config.ts structure to flat tokens.
1027
- * Maps config structure to actual DesignTokens key names.
1028
- */ function flattenConfigTokens(tokens, prefix = "atomix") {
1029
- const flat = {};
1030
- // Colors
1031
- return tokens.colors && Object.entries(tokens.colors).forEach((([key, value]) => {
1032
- var _context;
1033
- // Simple color: 'primary': '#7AFFD7'
1034
- // Map directly to DesignTokens keys (e.g., 'primary', 'secondary', 'error')
1035
- // Only map if it's a valid semantic color key
1036
- if ("string" == typeof value) _includesInstanceProperty(_context = [ "primary", "secondary", "success", "info", "warning", "error", "light", "dark" ]).call(_context, key) && (flat[key] = value); else if (value && "object" == typeof value)
1037
- // Color scale or palette
1038
- if ("main" in value) {
1039
- var _context2;
1040
- // PaletteColorOptions: { main: '#7AFFD7', light?: '#...', dark?: '#...' }
1041
- const palette = value, baseKey = key;
1042
- // Map main to base color (e.g., primary, secondary, error)
1043
- _includesInstanceProperty(_context2 = [ "primary", "secondary", "success", "info", "warning", "error", "light", "dark" ]).call(_context2, key) && (flat[baseKey] = palette.main,
1044
- // Map light/dark to appropriate scale values
1045
- // light typically maps to step 3, dark to step 9
1046
- palette.light && (flat[`${key}-3`] = palette.light), palette.dark && (flat[`${key}-9`] = palette.dark));
1047
- } else
1048
- // Color scale: { 1: '#fff', 2: '#eee', ..., 6: '#main', ... }
1049
- // Or full scale: { 1: '#...', 2: '#...', ..., 10: '#...' }
1050
- Object.entries(value).forEach((([scaleKey, scaleValue]) => {
1051
- if ("string" == typeof scaleValue)
1052
- // Map scale keys to DesignTokens format
1053
- if ("main" === scaleKey || "6" === scaleKey) {
1054
- var _context3, _context4;
1055
- // Main color maps to base key (e.g., 'primary')
1056
- _includesInstanceProperty(_context3 = [ "primary", "secondary", "success", "info", "warning", "error", "light", "dark" ]).call(_context3, key) && (flat[key] = scaleValue),
1057
- // Also map to step 6 if it's a scale color
1058
- _includesInstanceProperty(_context4 = [ "primary", "red", "green", "blue", "yellow", "gray" ]).call(_context4, key) && (flat[`${key}-6`] = scaleValue);
1059
- } else {
1060
- // Map scale numbers (1-10) to DesignTokens format
1061
- const scaleNum = parseInt(scaleKey, 10);
1062
- !isNaN(scaleNum) && scaleNum >= 1 && scaleNum <= 10 && (flat[`${key}-${scaleKey}`] = scaleValue);
1063
- }
1064
- }));
1065
- })),
1066
- // Spacing
1067
- tokens.spacing && Object.entries(tokens.spacing).forEach((([key, value]) => {
1068
- flat[`spacing-${key}`] = String(value);
1069
- })),
1070
- // Border Radius
1071
- tokens.borderRadius && Object.entries(tokens.borderRadius).forEach((([key, value]) => {
1072
- // Map to DesignTokens format
1073
- "sm" === key || "base" === key || "" === key ? flat["border-radius-sm"] = String(value) : "md" === key || "default" === key ? flat["border-radius"] = String(value) : "lg" === key ? flat["border-radius-lg"] = String(value) : flat[`border-radius-${key}`] = String(value);
1074
- })),
1075
- // Typography
1076
- tokens.typography && (
1077
- // Font Families
1078
- tokens.typography.fontFamilies && Object.entries(tokens.typography.fontFamilies).forEach((([key, value]) => {
1079
- // Map to DesignTokens format
1080
- "sans" === key || "base" === key ? (flat["font-sans-serif"] = String(value), flat["body-font-family"] = String(value)) : "mono" === key ? flat["font-monospace"] = String(value) : flat[`font-family-${key}`] = String(value);
1081
- })),
1082
- // Font Sizes
1083
- tokens.typography.fontSizes && Object.entries(tokens.typography.fontSizes).forEach((([key, value]) => {
1084
- flat[`font-size-${key}`] = String(value);
1085
- })),
1086
- // Font Weights
1087
- tokens.typography.fontWeights && Object.entries(tokens.typography.fontWeights).forEach((([key, value]) => {
1088
- flat[`font-weight-${key}`] = String(value);
1089
- })),
1090
- // Line Heights
1091
- tokens.typography.lineHeights && Object.entries(tokens.typography.lineHeights).forEach((([key, value]) => {
1092
- "base" === key || "default" === key ? flat["line-height-base"] = String(value) : flat[`line-height-${key}`] = String(value);
1093
- }))),
1094
- // Shadows
1095
- tokens.shadows && Object.entries(tokens.shadows).forEach((([key, value]) => {
1096
- flat[`shadow-${key}`] = String(value);
1097
- })),
1098
- // Z-Index
1099
- tokens.zIndex && Object.entries(tokens.zIndex).forEach((([key, value]) => {
1100
- flat[`z-index-${key}`] = String(value);
1101
- })),
1102
- // Transitions
1103
- tokens.transitions && tokens.transitions.durations && Object.entries(tokens.transitions.durations).forEach((([key, value]) => {
1104
- flat[`transition-${key}`] = String(value);
1105
- })), flat;
1106
- }
1107
-
1108
- /**
1109
- * Load theme tokens from atomix.config.ts
1110
- *
1111
- * Loads atomix.config.ts and extracts theme tokens.
1112
- * Config file is required - throws error if not found.
1113
- *
1114
- * @param configPath - Optional custom config path (default: 'atomix.config.ts')
1115
- * @returns Partial DesignTokens from config
1116
- * @throws Error if config file is not found or cannot be loaded
1117
- *
1118
- * @example
1119
- * ```typescript
1120
- * const tokens = await loadThemeFromConfig();
1121
- * const css = createTheme(tokens);
1122
- * injectTheme(css);
1123
- * ```
1124
- */ async function loadThemeFromConfig(configPath = "atomix.config.ts") {
1125
- // In browser environments, config loading is not supported
1126
- if ("undefined" != typeof window) throw new Error("loadThemeFromConfig: Not available in browser environment. Config loading requires Node.js/SSR environment.");
1127
- // Load config using the existing loader (required)
1128
- const {loadAtomixConfig: loadAtomixConfig} = await Promise.resolve().then((() => loader)), config = loadAtomixConfig({
1129
- configPath: configPath,
1130
- required: !0
1131
- });
1132
- if (!config || !config.theme) throw new Error(`Config file ${configPath} does not contain theme configuration.`);
1133
- // Extract tokens from config
1134
- const tokens = config.theme.tokens || config.theme.extend || {};
1135
- if (0 === Object.keys(tokens).length) throw new Error(`Config file ${configPath} has empty theme configuration.`);
1136
- // Convert nested structure to flat tokens
1137
- return flattenConfigTokens(tokens, config.prefix || "atomix");
1138
- }
1139
-
1140
- /**
1141
- * Load theme tokens from atomix.config.ts (synchronous version)
1142
- *
1143
- * Synchronous version that uses require() instead of dynamic import.
1144
- * Only works in Node.js environment.
1145
- * Config file is required - throws error if not found.
1146
- *
1147
- * @param configPath - Optional custom config path
1148
- * @returns Partial DesignTokens from config
1149
- * @throws Error if config file is not found or cannot be loaded
1150
- */ function loadThemeFromConfigSync(configPath = "atomix.config.ts") {
1151
- // In browser environments, config loading is not supported
1152
- if ("undefined" != typeof window) throw new Error("loadThemeFromConfigSync: Not available in browser environment. Config loading requires Node.js/SSR environment.");
1153
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1154
- const {loadAtomixConfig: loadAtomixConfig} = require("../../config/loader"), config = loadAtomixConfig({
1155
- configPath: configPath,
1156
- required: !0
1157
- });
1158
- if (!config || !config.theme) throw new Error(`Config file ${configPath} does not contain theme configuration.`);
1159
- // Extract tokens from config
1160
- const tokens = config.theme.tokens || config.theme.extend || {};
1161
- if (0 === Object.keys(tokens).length) throw new Error(`Config file ${configPath} has empty theme configuration.`);
1162
- // Convert nested structure to flat tokens
1163
- return flattenConfigTokens(tokens, config.prefix || "atomix");
1164
- }
1165
-
1166
1012
  /**
1167
1013
  * Core Theme Functions
1168
1014
  *
1169
- * Unified theme system that handles both DesignTokens and Theme objects.
1015
+ * Simplified theme system that handles both DesignTokens and Theme objects.
1170
1016
  * Config-first approach: loads from atomix.config.ts when no input is provided.
1171
1017
  * Config file is required for automatic loading.
1172
1018
  */
@@ -1200,29 +1046,53 @@ function isJSTheme(theme) {
1200
1046
  * const css = createTheme(undefined, { prefix: 'myapp', selector: ':root' });
1201
1047
  * ```
1202
1048
  */ function createTheme(input, options) {
1203
- let tokens, configPrefix;
1204
- // If no input provided, load from config (required)
1049
+ // Determine tokens based on input
1050
+ let tokens;
1205
1051
  if (input)
1206
- // Convert Theme to DesignTokens
1207
- tokens = !0 === input.__isJSTheme || input.palette && input.typography ? themeToDesignTokens(input) : input; else {
1208
- const configTokens = loadThemeFromConfigSync();
1209
- // Get prefix from config
1210
- try {
1211
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1212
- const {loadAtomixConfig: loadAtomixConfig} = require("../../config/loader"), config = loadAtomixConfig({
1213
- configPath: "atomix.config.ts",
1214
- required: !0
1215
- });
1216
- configPrefix = config?.prefix;
1052
+ // Use DesignTokens directly
1053
+ tokens =
1054
+ // Helper functions to simplify main function
1055
+ function(input) {
1056
+ return !0 === input?.__isJSTheme || input?.palette && input?.typography;
1057
+ }(input) ? themeToDesignTokens(input) : input;
1058
+ // Merge with defaults and generate CSS
1059
+ else {
1060
+ // Check if we're in a browser environment
1061
+ if ("undefined" != typeof window) throw new Error("createTheme: No input provided and config loading is not available in browser environment. Please provide tokens explicitly or use Node.js/SSR environment.");
1062
+ // Load from config when no input provided
1063
+ // Using dynamic import in a way that's more compatible with bundlers
1064
+ let loadThemeFromConfigSync, loadAtomixConfig;
1065
+ try {
1066
+ // Use dynamic require but only in Node.js environments
1067
+ // This approach allows bundlers to properly handle external dependencies
1068
+ const configLoaderModule = require("../config/configLoader"), loaderModule = require("../../config/loader");
1069
+ // Get prefix from config if needed
1070
+ if (loadThemeFromConfigSync = configLoaderModule.loadThemeFromConfigSync, loadAtomixConfig = loaderModule.loadAtomixConfig,
1071
+ tokens = loadThemeFromConfigSync(), !options?.prefix) try {
1072
+ const config = loadAtomixConfig({
1073
+ configPath: "atomix.config.ts",
1074
+ required: !1
1075
+ });
1076
+ options = {
1077
+ ...options,
1078
+ prefix: config?.prefix || "atomix"
1079
+ };
1080
+ } catch (error) {
1081
+ // If config loading fails, use default prefix
1082
+ options = {
1083
+ ...options,
1084
+ prefix: "atomix"
1085
+ };
1086
+ }
1217
1087
  } catch (error) {
1218
- // Prefix loading failed, but tokens were loaded, so continue
1088
+ throw new Error("createTheme: No input provided and config loading is not available in this environment. Please provide tokens explicitly.");
1219
1089
  }
1220
- tokens = configTokens;
1221
1090
  }
1222
- // Merge with defaults and generate CSS
1223
- return generateCSSVariables$1(createTokens(tokens), {
1091
+ const allTokens = createTokens(tokens), prefix = options?.prefix ?? "atomix";
1092
+ // Get prefix from options or use default
1093
+ return generateCSSVariables$1(allTokens, {
1224
1094
  ...options,
1225
- prefix: options?.prefix ?? configPrefix ?? "atomix"
1095
+ prefix: prefix
1226
1096
  });
1227
1097
  }
1228
1098
 
@@ -1283,7 +1153,7 @@ const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
1283
1153
  /**
1284
1154
  * Theme Composition Utilities
1285
1155
  *
1286
- * Utilities for composing, merging, and extending themes.
1156
+ * Simplified utilities for composing, merging, and extending themes.
1287
1157
  */
1288
1158
  // ============================================================================
1289
1159
  // Deep Merge Utility
@@ -1349,60 +1219,14 @@ const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
1349
1219
  * });
1350
1220
  * ```
1351
1221
  */ function extendTheme(baseTheme, extension) {
1352
- /**
1353
- * Extract theme options from a complete Theme object
1354
- */
1355
- var theme;
1356
- // ============================================================================
1357
- // Default Theme Values
1358
- // ============================================================================
1359
- return createThemeObject(mergeTheme(baseTheme.__isJSTheme ? {
1360
- name: (theme = baseTheme).name,
1361
- class: theme.class,
1362
- description: theme.description,
1363
- author: theme.author,
1364
- version: theme.version,
1365
- tags: theme.tags,
1366
- supportsDarkMode: theme.supportsDarkMode,
1367
- status: theme.status,
1368
- a11y: theme.a11y,
1369
- color: theme.color,
1370
- features: theme.features,
1371
- dependencies: theme.dependencies,
1372
- palette: {
1373
- primary: theme.palette.primary,
1374
- secondary: theme.palette.secondary,
1375
- error: theme.palette.error,
1376
- warning: theme.palette.warning,
1377
- info: theme.palette.info,
1378
- success: theme.palette.success,
1379
- background: theme.palette.background,
1380
- text: theme.palette.text
1381
- },
1382
- typography: {
1383
- fontFamily: theme.typography.fontFamily,
1384
- fontSize: theme.typography.fontSize,
1385
- fontWeightLight: theme.typography.fontWeightLight,
1386
- fontWeightRegular: theme.typography.fontWeightRegular,
1387
- fontWeightMedium: theme.typography.fontWeightMedium,
1388
- fontWeightSemiBold: theme.typography.fontWeightSemiBold,
1389
- fontWeightBold: theme.typography.fontWeightBold,
1390
- h1: theme.typography.h1,
1391
- h2: theme.typography.h2,
1392
- h3: theme.typography.h3,
1393
- h4: theme.typography.h4,
1394
- h5: theme.typography.h5,
1395
- h6: theme.typography.h6,
1396
- body1: theme.typography.body1,
1397
- body2: theme.typography.body2
1398
- },
1399
- shadows: theme.shadows,
1400
- transitions: theme.transitions,
1401
- zIndex: theme.zIndex,
1402
- custom: theme.custom
1222
+ return createThemeObject(mergeTheme(baseTheme.__isJSTheme ? {
1223
+ ...baseTheme
1403
1224
  } : baseTheme, extension));
1404
1225
  }
1405
1226
 
1227
+ // ============================================================================
1228
+ // Default Theme Values
1229
+ // ============================================================================
1406
1230
  const DEFAULT_PALETTE = {
1407
1231
  primary: {
1408
1232
  main: "#7c3aed",
@@ -1664,7 +1488,10 @@ function createThemeObject(...options) {
1664
1488
  "number" == typeof spacingInput ? (...values) => 0 === values.length ? "0px" : values.map((value => value * spacingInput + "px")).join(" ") :
1665
1489
  // If it's an array, use it as a scale
1666
1490
  Array.isArray(spacingInput) ? (...values) => 0 === values.length ? "0px" : values.map((value => `${spacingInput[value] || value}px`)).join(" ") : (...values) => 0 === values.length ? "0px" : values.map((value => 4 * value + "px")).join(" ");
1667
- }(mergedOptions.spacing), breakpoints = function(breakpointsInput) {
1491
+ }
1492
+ /**
1493
+ * Check if a theme is a JS theme (created with createTheme)
1494
+ */ (mergedOptions.spacing), breakpoints = function(breakpointsInput) {
1668
1495
  const values = {
1669
1496
  xs: 0,
1670
1497
  sm: 576,
@@ -1724,736 +1551,113 @@ function createThemeObject(...options) {
1724
1551
  }
1725
1552
 
1726
1553
  /**
1727
- * Theme Configuration Validator
1728
- *
1729
- * Validates theme configuration structure and values
1730
- */
1731
- /**
1732
- * Validate theme configuration
1733
- *
1734
- * @param config - Configuration to validate
1735
- * @returns Validation result with errors and warnings
1736
- */
1737
- /**
1738
- * Validate CSS theme
1739
- */
1740
- function validateCSSTheme(themeId, theme) {
1741
- const errors = [];
1742
- // CSS themes don't require createTheme function
1743
- // But can have optional cssPath
1744
- return {
1745
- valid: 0 === errors.length,
1746
- errors: errors,
1747
- warnings: []
1748
- };
1554
+ * Create a new theme registry
1555
+ */ function createThemeRegistry() {
1556
+ return {};
1749
1557
  }
1750
1558
 
1751
1559
  /**
1752
- * Validate JS theme
1753
- */ function validateJSTheme(themeId, theme) {
1754
- const errors = [];
1755
- // JS themes must have createTheme function
1756
- return theme.createTheme && "function" == typeof theme.createTheme || errors.push(`JS theme "${themeId}" must have a "createTheme" function`),
1757
- {
1758
- valid: 0 === errors.length,
1759
- errors: errors,
1760
- warnings: []
1761
- };
1560
+ * Register a theme
1561
+ * @param registry - Theme registry object
1562
+ * @param id - Theme identifier
1563
+ * @param metadata - Theme metadata
1564
+ */ function registerTheme(registry, id, metadata) {
1565
+ registry[id] = metadata;
1762
1566
  }
1763
1567
 
1764
1568
  /**
1765
- * Validate build configuration
1766
- */
1767
- /**
1768
- * Theme System Error Handling
1769
- *
1770
- * Centralized error handling for the Atomix theme system.
1771
- * Provides custom error classes and logging utilities.
1772
- */
1773
- /**
1774
- * Theme error codes
1775
- */
1776
- var ThemeErrorCode, LogLevel;
1777
-
1778
- !function(ThemeErrorCode) {
1779
- /** Theme not found in registry */
1780
- ThemeErrorCode.THEME_NOT_FOUND = "THEME_NOT_FOUND",
1781
- /** Theme failed to load */
1782
- ThemeErrorCode.THEME_LOAD_FAILED = "THEME_LOAD_FAILED",
1783
- /** Theme validation failed */
1784
- ThemeErrorCode.THEME_VALIDATION_FAILED = "THEME_VALIDATION_FAILED",
1785
- /** Configuration loading failed */
1786
- ThemeErrorCode.CONFIG_LOAD_FAILED = "CONFIG_LOAD_FAILED",
1787
- /** Configuration validation failed */
1788
- ThemeErrorCode.CONFIG_VALIDATION_FAILED = "CONFIG_VALIDATION_FAILED",
1789
- /** Circular dependency detected */
1790
- ThemeErrorCode.CIRCULAR_DEPENDENCY = "CIRCULAR_DEPENDENCY",
1791
- /** Missing dependency */
1792
- ThemeErrorCode.MISSING_DEPENDENCY = "MISSING_DEPENDENCY",
1793
- /** Storage operation failed */
1794
- ThemeErrorCode.STORAGE_ERROR = "STORAGE_ERROR",
1795
- /** Invalid theme name */
1796
- ThemeErrorCode.INVALID_THEME_NAME = "INVALID_THEME_NAME",
1797
- /** CSS injection failed */
1798
- ThemeErrorCode.CSS_INJECTION_FAILED = "CSS_INJECTION_FAILED",
1799
- /** Unknown error */
1800
- ThemeErrorCode.UNKNOWN_ERROR = "UNKNOWN_ERROR";
1801
- }(ThemeErrorCode || (ThemeErrorCode = {}));
1802
-
1803
- /**
1804
- * Custom error class for theme-related errors
1805
- */
1806
- class ThemeError extends Error {
1807
- constructor(message, code = ThemeErrorCode.UNKNOWN_ERROR, context) {
1808
- super(message), this.name = "ThemeError", this.code = code, this.context = context,
1809
- this.timestamp = Date.now(),
1810
- // Maintains proper stack trace for where our error was thrown (only available on V8)
1811
- Error.captureStackTrace && Error.captureStackTrace(this, ThemeError);
1812
- }
1813
- /**
1814
- * Convert error to JSON for logging
1815
- */ toJSON() {
1816
- return {
1817
- name: this.name,
1818
- message: this.message,
1819
- code: this.code,
1820
- context: this.context,
1821
- timestamp: this.timestamp,
1822
- stack: this.stack
1823
- };
1824
- }
1569
+ * Unregister a theme
1570
+ * @param registry - Theme registry object
1571
+ * @param id - Theme identifier
1572
+ */ function unregisterTheme(registry, id) {
1573
+ const exists = id in registry;
1574
+ return delete registry[id], exists;
1825
1575
  }
1826
1576
 
1827
1577
  /**
1828
- * Log level
1829
- */ !function(LogLevel) {
1830
- LogLevel[LogLevel.ERROR = 0] = "ERROR", LogLevel[LogLevel.WARN = 1] = "WARN", LogLevel[LogLevel.INFO = 2] = "INFO",
1831
- LogLevel[LogLevel.DEBUG = 3] = "DEBUG";
1832
- }(LogLevel || (LogLevel = {}));
1833
-
1834
- /**
1835
- * Theme Logger
1836
- *
1837
- * Centralized logging for the theme system.
1838
- * Replaces console statements with structured logging.
1839
- */
1840
- class ThemeLogger {
1841
- constructor(config = {}) {
1842
- this.config = {
1843
- level: config.level ?? ("production" === process.env.NODE_ENV ? LogLevel.WARN : LogLevel.INFO),
1844
- enableConsole: config.enableConsole ?? !0,
1845
- onError: config.onError,
1846
- onWarn: config.onWarn,
1847
- onInfo: config.onInfo,
1848
- onDebug: config.onDebug
1849
- };
1850
- }
1851
- /**
1852
- * Log an error
1853
- */ error(message, error, context) {
1854
- if (this.config.level < LogLevel.ERROR) return;
1855
- const errorObj = error instanceof Error ? error : new Error(message), themeError = error instanceof ThemeError ? error : new ThemeError(message, ThemeErrorCode.UNKNOWN_ERROR, context);
1856
- this.config.enableConsole && console.error(`[ThemeError] ${message}`, {
1857
- error: errorObj,
1858
- context: {
1859
- ...context,
1860
- ...themeError.context
1861
- },
1862
- code: themeError.code
1863
- }), this.config.onError?.(themeError, context);
1864
- }
1865
- /**
1866
- * Log a warning
1867
- */ warn(message, context) {
1868
- this.config.level < LogLevel.WARN || (this.config.enableConsole && console.warn(`[ThemeWarning] ${message}`, context || {}),
1869
- this.config.onWarn?.(message, context));
1870
- }
1871
- /**
1872
- * Log an info message
1873
- */ info(message, context) {
1874
- this.config.level < LogLevel.INFO || (this.config.enableConsole && console.info(`[ThemeInfo] ${message}`, context || {}),
1875
- this.config.onInfo?.(message, context));
1876
- }
1877
- /**
1878
- * Log a debug message
1879
- */ debug(message, context) {
1880
- this.config.level < LogLevel.DEBUG || (this.config.enableConsole, this.config.onDebug?.(message, context));
1881
- }
1578
+ * Check if a theme is registered
1579
+ * @param registry - Theme registry object
1580
+ * @param id - Theme identifier
1581
+ */ function hasTheme(registry, id) {
1582
+ return id in registry;
1882
1583
  }
1883
1584
 
1884
1585
  /**
1885
- * Default logger instance
1886
- */ let defaultLogger = null;
1586
+ * Get theme metadata
1587
+ * @param registry - Theme registry object
1588
+ * @param id - Theme identifier
1589
+ */ function getTheme(registry, id) {
1590
+ return registry[id];
1591
+ }
1887
1592
 
1888
1593
  /**
1889
- * Get or create default logger
1890
- */ function getLogger() {
1891
- return defaultLogger || (defaultLogger = new ThemeLogger), defaultLogger;
1594
+ * Get all registered theme metadata
1595
+ * @param registry - Theme registry object
1596
+ */ function getAllThemes(registry) {
1597
+ return Object.values(registry);
1892
1598
  }
1893
1599
 
1894
1600
  /**
1895
- * Theme System Constants
1896
- *
1897
- * Centralized constants for the theme system to avoid magic numbers and strings.
1898
- */
1899
- /**
1900
- * Default storage key for theme persistence
1901
- */ const DEFAULT_ATOMIX_CONFIG_PATH = "atomix.config.ts";
1601
+ * Get all registered theme IDs
1602
+ * @param registry - Theme registry object
1603
+ */ function getThemeIds(registry) {
1604
+ return Object.keys(registry);
1605
+ }
1902
1606
 
1903
1607
  /**
1904
- * Default data attribute name for theme
1905
- */ process.env.NODE_ENV;
1608
+ * Clear all registered themes
1609
+ * @param registry - Theme registry object
1610
+ */ function clearThemes(registry) {
1611
+ Object.keys(registry).forEach((key => delete registry[key]));
1612
+ }
1906
1613
 
1907
1614
  /**
1908
- * Integration default class names
1909
- */
1910
- const DEFAULT_INTEGRATION_CLASS_NAMES = {
1911
- theme: "data-theme",
1912
- colorMode: "data-atomix-color-mode"
1913
- }, DEFAULT_INTEGRATION_CSS_VARIABLES = {
1914
- colorMode: "--storybook-color-mode"
1915
- }, DEFAULT_SASS_CONFIG = {
1916
- style: "expanded",
1917
- sourceMap: !0,
1918
- loadPaths: [ "src" ]
1919
- };
1615
+ * Get the number of registered themes
1616
+ * @param registry - Theme registry object
1617
+ */ function getThemeCount(registry) {
1618
+ return Object.keys(registry).length;
1619
+ }
1920
1620
 
1921
1621
  /**
1922
- * Integration default CSS variables
1923
- */
1924
- /**
1925
- * Theme Configuration Loader
1622
+ * CSS Injection Utilities
1926
1623
  *
1927
- * Loads and validates the theme configuration from atomix.config.ts
1928
- */
1929
- /**
1930
- * Cache for loaded configuration
1624
+ * Inject CSS into HTML head via <style> element.
1931
1625
  */
1932
- let cachedConfig = null;
1933
-
1934
1626
  /**
1935
- * Logger instance
1936
- */ const logger = getLogger();
1627
+ * Check if running in browser environment
1628
+ */ function isBrowser$1() {
1629
+ return "undefined" != typeof document;
1630
+ }
1937
1631
 
1938
1632
  /**
1939
- * Load theme configuration from atomix.config.ts
1633
+ * Inject CSS into HTML head via <style> element
1940
1634
  *
1941
- * @param options - Loader options
1942
- * @returns Loaded and validated theme configuration
1635
+ * Creates or updates a style element in the document head.
1636
+ * If an element with the same ID exists, it will be updated.
1637
+ *
1638
+ * @param css - CSS string to inject
1639
+ * @param id - Style element ID (default: 'atomix-theme')
1943
1640
  *
1944
1641
  * @example
1945
1642
  * ```typescript
1946
- * import { loadThemeConfig } from '@shohojdhara/atomix/theme/config';
1947
- * const config = loadThemeConfig();
1643
+ * const css = ':root { --atomix-color-primary: #7AFFD7; }';
1644
+ * injectCSS(css);
1645
+ *
1646
+ * // With custom ID
1647
+ * injectCSS(css, 'my-custom-theme');
1948
1648
  * ```
1949
- */
1649
+ */ function injectCSS$1(css, id = "atomix-theme") {
1650
+ if (!isBrowser$1()) return void console.warn("injectCSS: Not in browser environment, CSS not injected");
1651
+ let styleElement = document.getElementById(id);
1652
+ styleElement || (styleElement = document.createElement("style"), styleElement.id = id,
1653
+ styleElement.setAttribute("data-atomix-theme", "true"), document.head.appendChild(styleElement)),
1654
+ styleElement.textContent = css;
1655
+ }
1656
+
1950
1657
  /**
1951
- * Theme Registry
1658
+ * Remove injected CSS from DOM
1952
1659
  *
1953
- * Manages theme registration, discovery, and dependency resolution
1954
- */
1955
- class ThemeRegistry {
1956
- constructor() {
1957
- this.entries = new Map, this.config = null, this.initialized = !1;
1958
- }
1959
- /**
1960
- * Initialize registry from config
1961
- */ async initialize(config) {
1962
- if (!this.initialized) {
1963
- // Load config if not provided
1964
- if (config) this.config = config; else try {
1965
- this.config = function(options = {}) {
1966
- const {configPath: configPath = DEFAULT_ATOMIX_CONFIG_PATH, validate: validate = !0, env: env = ("undefined" != typeof process && process.env && "production" === process.env.NODE_ENV ? "production" : "development")} = options;
1967
- // Return cached config if available
1968
- if (cachedConfig) return cachedConfig;
1969
- // Try to load config dynamically
1970
- let config;
1971
- try {
1972
- // In browser/Vite environment, we can't load config dynamically
1973
- if ("undefined" != typeof window) throw new Error("Theme config loading not supported in browser environment");
1974
- // In ESM environments, require might be undefined.
1975
- let nodeRequire, configModule;
1976
- try {
1977
- nodeRequire = require;
1978
- } catch {
1979
- // require is not defined
1980
- }
1981
- if (!nodeRequire) throw new Error("Theme config loading not supported in this environment (require is undefined)");
1982
- // Try require (Node.js/CommonJS)
1983
- try {
1984
- // Try relative path first
1985
- try {
1986
- configModule = nodeRequire("../../../../atomix.config");
1987
- } catch {
1988
- // If relative path fails, try to resolve from process.cwd()
1989
- const path = nodeRequire("path"), fs = nodeRequire("fs");
1990
- let configFilePath = path.resolve(process.cwd(), configPath);
1991
- // If atomix.config.ts not found at the root, use the default path
1992
- if (fs.existsSync(configFilePath) || configPath !== DEFAULT_ATOMIX_CONFIG_PATH || (configFilePath = path.resolve(process.cwd(), DEFAULT_ATOMIX_CONFIG_PATH)),
1993
- !fs.existsSync(configFilePath)) throw new Error(`Config file not found: ${configFilePath}`);
1994
- {
1995
- const resolvedPath = nodeRequire.resolve(configFilePath);
1996
- nodeRequire.cache && nodeRequire.cache[resolvedPath] && delete nodeRequire.cache[resolvedPath],
1997
- configModule = nodeRequire(configFilePath);
1998
- }
1999
- }
2000
- } catch (requireError) {
2001
- const errorMessage = requireError instanceof Error ? requireError.message : String(requireError);
2002
- throw new ThemeError(`Cannot load config: ${errorMessage}`, ThemeErrorCode.CONFIG_LOAD_FAILED, {
2003
- configPath: configPath,
2004
- error: errorMessage
2005
- });
2006
- }
2007
- const rawConfig = configModule.default || configModule, finalConfig =
2008
- /**
2009
- * Apply environment-specific overrides to configuration
2010
- *
2011
- * @param config - Base configuration
2012
- * @param env - Environment name
2013
- * @returns Configuration with environment overrides applied
2014
- */
2015
- function(config, env) {
2016
- const overridden = {
2017
- ...config
2018
- };
2019
- // Production overrides
2020
- return "production" === env && overridden.runtime && (overridden.runtime = {
2021
- ...overridden.runtime,
2022
- useMinified: !0,
2023
- lazy: !0
2024
- }),
2025
- // Development overrides
2026
- "development" === env && (overridden.runtime && (overridden.runtime = {
2027
- ...overridden.runtime,
2028
- useMinified: !1,
2029
- lazy: !1
2030
- }), overridden.build && (overridden.build = {
2031
- ...overridden.build,
2032
- sass: {
2033
- ...overridden.build.sass,
2034
- sourceMap: !0
2035
- }
2036
- })),
2037
- // Test overrides
2038
- "test" === env && overridden.runtime && (overridden.runtime = {
2039
- ...overridden.runtime,
2040
- enablePersistence: !1,
2041
- preload: []
2042
- }), overridden;
2043
- }({
2044
- themes: rawConfig.theme?.themes || {},
2045
- build: rawConfig.build || {},
2046
- runtime: rawConfig.runtime || {},
2047
- integration: rawConfig.integration || {},
2048
- dependencies: rawConfig.dependencies || {},
2049
- validated: !1,
2050
- // Will be set after validation
2051
- // Store tokens for generator
2052
- __tokens: rawConfig.theme?.tokens,
2053
- __extend: rawConfig.theme?.extend
2054
- }, env);
2055
- // Process the AtomixConfig structure
2056
- // Validate if requested
2057
- let validationResult = null;
2058
- validate && (validationResult = function(config) {
2059
- const errors = [], warnings = [];
2060
- // Validate top-level structure
2061
- if (!config || "object" != typeof config) return errors.push("Configuration must be an object"),
2062
- {
2063
- valid: !1,
2064
- errors: errors,
2065
- warnings: warnings
2066
- };
2067
- // Validate themes
2068
- if (config.themes && "object" == typeof config.themes) {
2069
- const themeErrors =
2070
- /**
2071
- * Validate themes object
2072
- */
2073
- function(themes) {
2074
- const errors = [], warnings = [];
2075
- 0 === Object.keys(themes).length && warnings.push("No themes defined in configuration");
2076
- for (const [themeId, theme] of Object.entries(themes))
2077
- // Validate theme ID
2078
- if (themeId && "string" == typeof themeId)
2079
- // Validate theme object
2080
- if (theme && "object" == typeof theme)
2081
- // Validate theme type
2082
- if (!theme.type || "css" !== theme.type && "js" !== theme.type) errors.push(`Theme "${themeId}" must have type "css" or "js"`); else {
2083
- // Validate CSS theme
2084
- if (
2085
- // Validate required fields
2086
- theme.name && "string" == typeof theme.name || errors.push(`Theme "${themeId}" must have a "name" string`),
2087
- "css" === theme.type) {
2088
- const cssErrors = validateCSSTheme();
2089
- errors.push(...cssErrors.errors), warnings.push(...cssErrors.warnings);
2090
- }
2091
- // Validate JS theme
2092
- if ("js" === theme.type) {
2093
- const jsErrors = validateJSTheme(themeId, theme);
2094
- errors.push(...jsErrors.errors), warnings.push(...jsErrors.warnings);
2095
- }
2096
- // Validate accessibility (only critical checks)
2097
- theme.a11y?.contrastTarget && ("number" != typeof theme.a11y.contrastTarget || theme.a11y.contrastTarget < 1 || theme.a11y.contrastTarget > 21) && warnings.push(`Theme "${themeId}" has invalid contrast target: ${theme.a11y.contrastTarget}`);
2098
- } else errors.push(`Theme "${themeId}" must be an object`); else errors.push(`Invalid theme ID: ${themeId}`);
2099
- return {
2100
- valid: 0 === errors.length,
2101
- errors: errors,
2102
- warnings: warnings
2103
- };
2104
- }(config.themes);
2105
- errors.push(...themeErrors.errors), warnings.push(...themeErrors.warnings);
2106
- }
2107
- // Validate build config (only if provided)
2108
- else errors.push('Configuration must have a "themes" object');
2109
- if (config.build) {
2110
- const buildErrors = function(build) {
2111
- const errors = [];
2112
- // Only validate structure if provided
2113
- return build.output && "object" != typeof build.output && errors.push("Build output must be an object"),
2114
- build.sass && "object" != typeof build.sass && errors.push("Build sass config must be an object"),
2115
- {
2116
- valid: 0 === errors.length,
2117
- errors: errors,
2118
- warnings: []
2119
- };
2120
- }
2121
- /**
2122
- * Validate runtime configuration
2123
- */ (config.build);
2124
- errors.push(...buildErrors.errors), warnings.push(...buildErrors.warnings);
2125
- }
2126
- // Validate runtime config (only if provided)
2127
- if (config.runtime) {
2128
- const runtimeErrors = function(runtime) {
2129
- const errors = [];
2130
- return runtime.basePath && "string" != typeof runtime.basePath && errors.push("Runtime basePath must be a string"),
2131
- runtime.defaultTheme && "string" != typeof runtime.defaultTheme && errors.push("Runtime defaultTheme must be a string"),
2132
- runtime.preload && !Array.isArray(runtime.preload) && errors.push("Runtime preload must be an array"),
2133
- runtime.storageKey && "string" != typeof runtime.storageKey && errors.push("Runtime storageKey must be a string"),
2134
- {
2135
- valid: 0 === errors.length,
2136
- errors: errors,
2137
- warnings: []
2138
- };
2139
- }
2140
- /**
2141
- * Validate integration configuration
2142
- */ (config.runtime);
2143
- errors.push(...runtimeErrors.errors), warnings.push(...runtimeErrors.warnings);
2144
- }
2145
- // Validate integration config (only if provided)
2146
- if (config.integration) {
2147
- const integrationErrors = function(integration) {
2148
- const errors = [];
2149
- // Only validate structure if provided
2150
- return integration.classNames && "object" != typeof integration.classNames && errors.push("Integration classNames must be an object"),
2151
- {
2152
- valid: 0 === errors.length,
2153
- errors: errors,
2154
- warnings: []
2155
- };
2156
- }
2157
- /**
2158
- * Validate dependencies
2159
- */ (config.integration);
2160
- errors.push(...integrationErrors.errors), warnings.push(...integrationErrors.warnings);
2161
- }
2162
- // Validate dependencies
2163
- if (config.dependencies) {
2164
- const depErrors = function(dependencies, themes) {
2165
- const errors = [], warnings = [];
2166
- for (const [themeId, deps] of Object.entries(dependencies))
2167
- // Check if theme exists
2168
- if (themes[themeId])
2169
- // Validate dependencies array
2170
- if (Array.isArray(deps))
2171
- // Check if all dependencies exist
2172
- for (const dep of deps) themes[dep] || errors.push(`Theme "${themeId}" depends on non-existent theme: ${dep}`); else errors.push(`Dependencies for "${themeId}" must be an array`); else warnings.push(`Dependencies defined for non-existent theme: ${themeId}`);
2173
- return {
2174
- valid: 0 === errors.length,
2175
- errors: errors,
2176
- warnings: warnings
2177
- };
2178
- }(config.dependencies, config.themes || {});
2179
- errors.push(...depErrors.errors), warnings.push(...depErrors.warnings);
2180
- }
2181
- return {
2182
- valid: 0 === errors.length,
2183
- errors: errors,
2184
- warnings: warnings
2185
- };
2186
- }(finalConfig)), config = {
2187
- ...finalConfig,
2188
- validated: validate,
2189
- errors: validationResult?.errors,
2190
- warnings: validationResult?.warnings
2191
- };
2192
- } catch (error) {
2193
- // Fallback: return default config structure
2194
- const errorMessage = error instanceof Error ? error.message : String(error);
2195
- logger.warn(`Failed to load theme config from ${configPath}`, {
2196
- configPath: configPath,
2197
- error: errorMessage
2198
- }), config = {
2199
- themes: {},
2200
- build: {
2201
- output: {
2202
- directory: "themes",
2203
- formats: {
2204
- expanded: ".css",
2205
- compressed: ".min.css"
2206
- }
2207
- },
2208
- sass: {
2209
- ...DEFAULT_SASS_CONFIG,
2210
- loadPaths: [ ...DEFAULT_SASS_CONFIG.loadPaths ]
2211
- }
2212
- },
2213
- runtime: {
2214
- basePath: "/themes",
2215
- cdnPath: null,
2216
- preload: [],
2217
- lazy: !0,
2218
- defaultTheme: "",
2219
- // No default - use built-in styles (empty string instead of undefined)
2220
- storageKey: "atomix-theme",
2221
- dataAttribute: "data-theme",
2222
- enablePersistence: !0,
2223
- useMinified: "production" === env
2224
- },
2225
- integration: {
2226
- cssVariables: DEFAULT_INTEGRATION_CSS_VARIABLES,
2227
- classNames: DEFAULT_INTEGRATION_CLASS_NAMES
2228
- },
2229
- dependencies: {},
2230
- validated: !1,
2231
- errors: [ `Failed to load config: ${error instanceof Error ? error.message : String(error)}` ],
2232
- warnings: [],
2233
- __tokens: {},
2234
- __extend: {}
2235
- };
2236
- }
2237
- // Cache the loaded config
2238
- return cachedConfig = config, config;
2239
- }();
2240
- } catch (error) {
2241
- // In browser environments, config loading will fail
2242
- // Use empty config as fallback
2243
- this.config = {
2244
- themes: {},
2245
- build: {
2246
- output: {
2247
- directory: "themes",
2248
- formats: {
2249
- expanded: ".css",
2250
- compressed: ".min.css"
2251
- }
2252
- },
2253
- sass: {
2254
- style: "expanded",
2255
- sourceMap: !0,
2256
- loadPaths: [ "src" ]
2257
- }
2258
- },
2259
- runtime: {
2260
- basePath: "",
2261
- defaultTheme: void 0
2262
- },
2263
- integration: {
2264
- cssVariables: {
2265
- colorMode: "--color-mode"
2266
- },
2267
- classNames: {
2268
- theme: "data-theme",
2269
- colorMode: "data-color-mode"
2270
- }
2271
- },
2272
- dependencies: {},
2273
- validated: !1,
2274
- errors: [],
2275
- warnings: [],
2276
- __tokens: {},
2277
- __extend: {}
2278
- };
2279
- }
2280
- // Register all themes from config
2281
- for (const [themeId, definition] of Object.entries(this.config.themes)) this.register(themeId, definition);
2282
- // Resolve dependencies
2283
- this.resolveDependencies(), this.initialized = !0;
2284
- }
2285
- }
2286
- /**
2287
- * Register a theme
2288
- */ register(themeId, definition) {
2289
- // Get dependencies from config or definition
2290
- const entry = {
2291
- id: themeId,
2292
- definition: definition,
2293
- loaded: !1,
2294
- dependencies: [ ...this.config?.dependencies?.[themeId] || definition.dependencies || [] ],
2295
- dependents: []
2296
- };
2297
- this.entries.set(themeId, entry);
2298
- }
2299
- /**
2300
- * Get theme entry
2301
- */ get(themeId) {
2302
- return this.entries.get(themeId);
2303
- }
2304
- /**
2305
- * Check if theme exists
2306
- */ has(themeId) {
2307
- return this.entries.has(themeId);
2308
- }
2309
- /**
2310
- * Get all theme IDs
2311
- */ getAllIds() {
2312
- return Array.from(this.entries.keys());
2313
- }
2314
- /**
2315
- * Get all theme metadata
2316
- */ getAllMetadata() {
2317
- return Array.from(this.entries.values()).map((entry => ({
2318
- id: entry.id,
2319
- name: entry.definition.name,
2320
- type: entry.definition.type,
2321
- class: entry.definition.class,
2322
- description: entry.definition.description,
2323
- author: entry.definition.author,
2324
- version: entry.definition.version,
2325
- tags: entry.definition.tags,
2326
- supportsDarkMode: entry.definition.supportsDarkMode,
2327
- status: entry.definition.status,
2328
- a11y: entry.definition.a11y,
2329
- color: entry.definition.color,
2330
- features: entry.definition.features,
2331
- dependencies: entry.dependencies
2332
- })));
2333
- }
2334
- /**
2335
- * Get theme definition
2336
- */ getDefinition(themeId) {
2337
- return this.entries.get(themeId)?.definition;
2338
- }
2339
- /**
2340
- * Check if a theme is loaded
2341
- */ isThemeLoaded(themeId) {
2342
- const entry = this.entries.get(themeId);
2343
- return !!entry && entry.loaded;
2344
- }
2345
- /**
2346
- * Mark a theme as loaded
2347
- */ markLoaded(themeId, theme) {
2348
- const entry = this.entries.get(themeId);
2349
- entry && (entry.loaded = !0, theme && (entry.theme = theme));
2350
- }
2351
- /**
2352
- * Get theme object (for JS themes)
2353
- */ getTheme(themeId) {
2354
- const entry = this.entries.get(themeId);
2355
- return entry?.loaded ? entry.theme : void 0;
2356
- }
2357
- /**
2358
- * Get dependencies for a theme
2359
- */ getDependencies(themeId) {
2360
- return this.entries.get(themeId)?.dependencies || [];
2361
- }
2362
- /**
2363
- * Get dependents for a theme (themes that depend on this one)
2364
- */ getDependents(themeId) {
2365
- return this.entries.get(themeId)?.dependents || [];
2366
- }
2367
- /**
2368
- * Resolve all dependencies in correct order
2369
- */ resolveDependencyOrder(themeId) {
2370
- const resolved = [], visited = new Set, visiting = new Set, visit = id => {
2371
- if (visiting.has(id)) throw new Error(`Circular dependency detected involving theme: ${id}`);
2372
- if (visited.has(id)) return;
2373
- visiting.add(id);
2374
- const entry = this.entries.get(id);
2375
- if (entry) for (const dep of entry.dependencies) {
2376
- if (!this.has(dep)) throw new Error(`Theme "${id}" depends on non-existent theme: ${dep}`);
2377
- visit(dep);
2378
- }
2379
- visiting.delete(id), visited.add(id), resolved.push(id);
2380
- };
2381
- return visit(themeId), resolved;
2382
- }
2383
- /**
2384
- * Resolve dependencies and build dependency graph
2385
- */ resolveDependencies() {
2386
- // Build dependents map
2387
- for (const entry of this.entries.values()) for (const dep of entry.dependencies) {
2388
- const depEntry = this.entries.get(dep);
2389
- var _context;
2390
- depEntry && (_includesInstanceProperty(_context = depEntry.dependents).call(_context, entry.id) || depEntry.dependents.push(entry.id));
2391
- }
2392
- }
2393
- /**
2394
- * Validate all themes
2395
- */ validate() {
2396
- const errors = [];
2397
- // Check for circular dependencies
2398
- for (const themeId of this.entries.keys()) try {
2399
- this.resolveDependencyOrder(themeId);
2400
- } catch (error) {
2401
- errors.push(error instanceof Error ? error.message : String(error));
2402
- }
2403
- // Check for missing dependencies
2404
- for (const [themeId, entry] of this.entries.entries()) for (const dep of entry.dependencies) this.has(dep) || errors.push(`Theme "${themeId}" depends on non-existent theme: ${dep}`);
2405
- return {
2406
- valid: 0 === errors.length,
2407
- errors: errors
2408
- };
2409
- }
2410
- /**
2411
- * Clear registry
2412
- */ clear() {
2413
- this.entries.clear(), this.config = null, this.initialized = !1;
2414
- }
2415
- }
2416
-
2417
- /**
2418
- * CSS Injection Utilities
2419
- *
2420
- * Inject CSS into HTML head via <style> element.
2421
- */
2422
- /**
2423
- * Check if running in browser environment
2424
- */ function isBrowser$1() {
2425
- return "undefined" != typeof document;
2426
- }
2427
-
2428
- /**
2429
- * Inject CSS into HTML head via <style> element
2430
- *
2431
- * Creates or updates a style element in the document head.
2432
- * If an element with the same ID exists, it will be updated.
2433
- *
2434
- * @param css - CSS string to inject
2435
- * @param id - Style element ID (default: 'atomix-theme')
2436
- *
2437
- * @example
2438
- * ```typescript
2439
- * const css = ':root { --atomix-color-primary: #7AFFD7; }';
2440
- * injectCSS(css);
2441
- *
2442
- * // With custom ID
2443
- * injectCSS(css, 'my-custom-theme');
2444
- * ```
2445
- */ function injectCSS$1(css, id = "atomix-theme") {
2446
- if (!isBrowser$1()) return void console.warn("injectCSS: Not in browser environment, CSS not injected");
2447
- let styleElement = document.getElementById(id);
2448
- styleElement || (styleElement = document.createElement("style"), styleElement.id = id,
2449
- styleElement.setAttribute("data-atomix-theme", "true"), document.head.appendChild(styleElement)),
2450
- styleElement.textContent = css;
2451
- }
2452
-
2453
- /**
2454
- * Remove injected CSS from DOM
2455
- *
2456
- * Removes the style element with the given ID from the document head.
1660
+ * Removes the style element with the given ID from the document head.
2457
1661
  *
2458
1662
  * @param id - Style element ID to remove (default: 'atomix-theme')
2459
1663
  *
@@ -2496,71 +1700,18 @@ class ThemeRegistry {
2496
1700
  * const css = ':root { --atomix-color-primary: #7AFFD7; }';
2497
1701
  * await saveCSSFile(css, './themes/custom.css');
2498
1702
  * ```
2499
- */ async function saveCSSFile(css, filePath) {
2500
- // Check if in browser environment
2501
- if ("undefined" != typeof window) throw new Error("saveCSSFile can only be used in Node.js environment. Use injectCSS() for browser environments.");
2502
- // Dynamic import to avoid bundling Node.js modules in browser builds
2503
- const fs = await import("fs/promises"), dir = (await import("path")).dirname(filePath);
2504
- await fs.mkdir(dir, {
2505
- recursive: !0
2506
- }),
2507
- // Write file
2508
- await fs.writeFile(filePath, css, "utf8");
2509
- }
2510
-
2511
- /**
2512
- * Save CSS to file (synchronous version)
2513
- *
2514
- * Synchronous version of saveCSSFile. Only works in Node.js environment.
2515
- *
2516
- * @param css - CSS string to save
2517
- * @param filePath - Output file path
2518
- * @throws Error if called in browser environment
2519
- */ function saveCSSFileSync(css, filePath) {
2520
- // Check if in browser environment
2521
- if ("undefined" != typeof window) throw new Error("saveCSSFileSync can only be used in Node.js environment. Use injectCSS() for browser environments.");
2522
- // Use require for synchronous file operations
2523
- // eslint-disable-next-line @typescript-eslint/no-require-imports
2524
- const fs = require("fs"), dir = require("path").dirname(filePath);
2525
- // eslint-disable-next-line @typescript-eslint/no-require-imports
2526
- fs.existsSync(dir) || fs.mkdirSync(dir, {
2527
- recursive: !0
2528
- }),
2529
- // Write file
2530
- fs.writeFileSync(filePath, css, "utf8");
2531
- }
1703
+ */ "undefined" != typeof process && process.env;
2532
1704
 
2533
1705
  /**
2534
1706
  * Check if code is running in a browser environment
2535
- */ const isBrowser = () => "undefined" != typeof window && "undefined" != typeof document, isServer = () => !isBrowser(), getThemeLinkId = themeName => `atomix-theme-${themeName}`, buildThemePath = (themeName, basePath = "/themes", useMinified = !1, cdnPath = null) => {
1707
+ */
1708
+ const isBrowser = () => "undefined" != typeof window && "undefined" != typeof document, isServer = () => !isBrowser(), buildThemePath = (themeName, basePath = "/themes", useMinified = !1, cdnPath = null) => {
2536
1709
  // Validate theme name to prevent path injection
2537
1710
  if (!isValidThemeName(themeName)) throw new Error(`Invalid theme name: "${themeName}". Theme names must be lowercase alphanumeric with hyphens.`);
2538
1711
  const fileName = `${themeName}${useMinified ? ".min.css" : ".css"}`;
2539
1712
  return cdnPath ? `${cdnPath.replace(/[<>"']/g, "")}/${fileName}` : `${basePath.replace(/\/$/, "").replace(/[<>"']/g, "")}/${fileName.replace(/^\//, "")}`;
2540
1713
  // Ensure basePath doesn't end with slash and fileName doesn't start with slash
2541
1714
  // Also sanitize basePath to prevent path injection
2542
- }, loadThemeCSS = (fullPath, linkId) => isServer() ? Promise.resolve() : new Promise(((resolve, reject) => {
2543
- if (document.getElementById(linkId)) return void resolve();
2544
- // Create link element
2545
- const link = document.createElement("link");
2546
- link.id = linkId, link.rel = "stylesheet", link.type = "text/css", link.href = fullPath,
2547
- // Add data attribute for tracking
2548
- link.setAttribute("data-atomix-theme", "true"),
2549
- // Handle load success
2550
- link.onload = () => {
2551
- resolve();
2552
- },
2553
- // Handle load error
2554
- link.onerror = () => {
2555
- // Remove failed link element
2556
- link.remove(), reject(new Error(`Failed to load theme CSS: ${fullPath}`));
2557
- },
2558
- // Append to head
2559
- document.head.appendChild(link);
2560
- })), isThemeLoaded = themeName => {
2561
- if (isServer()) return !1;
2562
- const linkId = getThemeLinkId(themeName);
2563
- return null !== document.getElementById(linkId);
2564
1715
  }, isValidThemeName = themeName => !(!themeName || "string" != typeof themeName) && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(themeName);
2565
1716
 
2566
1717
  /**
@@ -3033,211 +2184,198 @@ function generateCSSVariables(theme, options = {}) {
3033
2184
  styleElement.textContent = css;
3034
2185
  }
3035
2186
  /**
3036
- * Theme Context
2187
+ * Naming Utilities
3037
2188
  *
3038
- * React context for theme management
2189
+ * Provides consistent naming conventions across the theme system
3039
2190
  */
3040
2191
  /**
3041
- * Theme context with default values
2192
+ * Generate consistent CSS class names following BEM methodology
3042
2193
  */ (css, styleId), css;
3043
2194
  }
3044
2195
 
3045
- const ThemeContext = createContext(null);
2196
+ function generateClassName(block, element, modifiers) {
2197
+ let className = block;
2198
+ return element && (className += `__${element}`), modifiers && Object.entries(modifiers).forEach((([key, value]) => {
2199
+ value && (className += `--${key}`, "string" == typeof value && value !== key && (className += `-${value}`));
2200
+ })), className;
2201
+ }
3046
2202
 
3047
- ThemeContext.displayName = "ThemeContext";
2203
+ /**
2204
+ * Generate consistent CSS variable names
2205
+ */ function generateCSSVariableName(property, options = {}) {
2206
+ const {prefix: prefix = "atomix", component: component, variant: variant, state: state} = options, parts = [ prefix ];
2207
+ return component && parts.push(component), variant && parts.push(variant), state && parts.push(state),
2208
+ parts.push(property), `--${parts.join("-")}`;
2209
+ }
3048
2210
 
3049
2211
  /**
3050
- * Theme Applicator
3051
- *
3052
- * Applies theme configurations to the DOM, including CSS variables,
3053
- * component overrides, typography, spacing, and color palettes.
3054
- *
3055
- * Uses the unified theme system for CSS generation and injection.
3056
- */
2212
+ * Normalize theme tokens to consistent naming convention
2213
+ */ function normalizeThemeTokens(tokens) {
2214
+ const normalized = {};
2215
+ for (const [key, value] of Object.entries(tokens))
2216
+ // Recursively normalize nested objects
2217
+ normalized[key] = "object" == typeof value && null !== value ? normalizeThemeTokens(value) : value;
2218
+ return normalized;
2219
+ }
2220
+
3057
2221
  /**
3058
- * Theme applicator class for runtime theme application
2222
+ * Convert camelCase to kebab-case for CSS custom properties
2223
+ */ function camelToKebab(str) {
2224
+ return str.replace(/[A-Z]/g, (match => `-${match.toLowerCase()}`));
2225
+ }
2226
+
2227
+ /**
2228
+ * Convert theme property to CSS variable name
2229
+ */ function themePropertyToCSSVar(propertyPath, prefix = "atomix") {
2230
+ return `--${prefix}-${propertyPath.split(".").map((part => camelToKebab(part))).join("-")}`;
2231
+ }
2232
+
2233
+ /**
2234
+ * Component Theming Utilities
3059
2235
  *
3060
- * Uses the unified theme system for efficient CSS variable generation and injection.
2236
+ * Provides consistent patterns for applying theme values to components
3061
2237
  */
3062
- class ThemeApplicator {
3063
- constructor(root = document.documentElement) {
3064
- this.styleId = "atomix-theme-applicator", this.root = root;
3065
- }
3066
- /**
3067
- * Apply a complete theme configuration
3068
- *
3069
- * Uses the unified theme system to convert Theme to DesignTokens and inject CSS.
3070
- * Automatically respects atomix.config.ts when using DesignTokens.
3071
- */ applyTheme(theme) {
3072
- // Clear previously applied variables
3073
- this.clearAppliedVars(),
3074
- // Check if it's DesignTokens
3075
- this.isDesignTokens(theme) ?
3076
- // Direct DesignTokens - use unified theme system (with config support)
3077
- this.applyDesignTokens(theme) : injectCSS$1(createTheme(theme, {
3078
- selector: ":root",
3079
- prefix: "atomix"
3080
- }), this.styleId),
3081
- // Apply component overrides (only for Theme objects)
3082
- !this.isDesignTokens(theme) && theme.components && this.applyComponentOverrides(theme.components);
3083
- }
3084
- /**
3085
- * Apply DesignTokens using unified theme system
3086
- *
3087
- * Uses createTheme() which automatically loads from atomix.config.ts
3088
- * if no tokens are provided, ensuring config is always respected.
3089
- */ applyDesignTokens(tokens) {
3090
- // Inject CSS into DOM
3091
- injectCSS$1(createTheme(tokens, {
3092
- selector: ":root",
3093
- prefix: "atomix"
3094
- }), this.styleId);
3095
- }
3096
- /**
3097
- * Check if object is DesignTokens
3098
- */ isDesignTokens(obj) {
3099
- // DesignTokens is a flat object with string keys, no nested structures
3100
- return null !== obj && "object" == typeof obj && !("palette" in obj) && !("typography" in obj) && !("__isJSTheme" in obj);
3101
- }
3102
- /**
3103
- * Apply global CSS variables (for component overrides)
3104
- */ applyGlobalCSSVars(vars) {
3105
- Object.entries(vars).forEach((([key, value]) => {
3106
- this.root.style.setProperty(key, String(value));
3107
- }));
3108
- }
3109
- /**
3110
- * Apply component-level overrides
3111
- */ applyComponentOverrides(overrides) {
3112
- Object.entries(overrides).forEach((([componentName, override]) => {
3113
- override && this.applyComponentOverride(componentName, override);
3114
- }));
3115
- }
3116
- /**
3117
- * Apply override for a specific component
3118
- */ applyComponentOverride(componentName, override) {
3119
- const vars = {}, componentKey = componentName.toLowerCase();
3120
- // Apply component-level CSS variables
3121
- override.cssVars && Object.entries(override.cssVars).forEach((([key, value]) => {
3122
- // If key doesn't start with --, add component prefix
3123
- const varKey = key.startsWith("--") ? key : `--atomix-${componentKey}-${key}`;
3124
- vars[varKey] = value;
3125
- })),
3126
- // Apply part-specific CSS variables
3127
- override.parts && Object.entries(override.parts).forEach((([partName, partOverride]) => {
3128
- partOverride.cssVars && Object.entries(partOverride.cssVars).forEach((([key, value]) => {
3129
- const varKey = key.startsWith("--") ? key : `--atomix-${componentKey}-${partName}-${key}`;
3130
- vars[varKey] = value;
3131
- }));
3132
- })),
3133
- // Apply variant-specific CSS variables
3134
- override.variants && Object.entries(override.variants).forEach((([variantName, variantOverride]) => {
3135
- variantOverride.cssVars && Object.entries(variantOverride.cssVars).forEach((([key, value]) => {
3136
- const varKey = key.startsWith("--") ? key : `--atomix-${componentKey}-${variantName}-${key}`;
3137
- vars[varKey] = value;
3138
- }));
3139
- })), this.applyGlobalCSSVars(vars);
3140
- }
3141
- /**
3142
- * Clear all applied CSS variables
3143
- */ clearAppliedVars() {
3144
- removeCSS(this.styleId);
3145
- }
3146
- /**
3147
- * Remove theme application
3148
- */ removeTheme() {
3149
- this.clearAppliedVars(), removeCSS(this.styleId);
3150
- }
3151
- /**
3152
- * Update specific CSS variables without clearing all
3153
- */ updateCSSVars(vars) {
3154
- this.applyGlobalCSSVars(vars);
3155
- }
2238
+ /**
2239
+ * Get a theme value for a specific component using CSS variables
2240
+ * This ensures all components access theme values consistently
2241
+ */ function getComponentThemeValue(component, property, variant, size) {
2242
+ // Build CSS variable name following consistent pattern
2243
+ const parts = [ "atomix", component ];
2244
+ // Return CSS variable reference with fallback
2245
+ return variant && parts.push(variant), size && parts.push(size), parts.push(property),
2246
+ `var(--${parts.join("-")}, var(--atomix-${property}, initial))`;
3156
2247
  }
3157
2248
 
3158
2249
  /**
3159
- * Global theme applicator instance
3160
- */ let globalApplicator = null;
2250
+ * Generate component-specific CSS variables from theme
2251
+ */ function generateComponentCSSVars(component, theme, variant, size) {
2252
+ const vars = {}, prefixParts = [ "atomix", component ];
2253
+ // This is a simplified implementation - in a real system you'd have more
2254
+ // sophisticated logic to extract component-specific values from the theme
2255
+ variant && prefixParts.push(variant), size && prefixParts.push(size);
2256
+ const prefix = prefixParts.join("-");
2257
+ // Add common component properties
2258
+ if (theme.palette && (vars[`--${prefix}-color`] = theme.palette.primary?.main || "#7c3aed",
2259
+ vars[`--${prefix}-color-hover`] = theme.palette.primary?.dark || "#5b21b6", vars[`--${prefix}-color-active`] = theme.palette.primary?.main || "#7c3aed",
2260
+ vars[`--${prefix}-color-disabled`] = theme.palette.text?.disabled || "#9ca3af"),
2261
+ theme.typography && (vars[`--${prefix}-font-family`] = theme.typography.fontFamily || "Inter, sans-serif",
2262
+ vars[`--${prefix}-font-size`] = theme.typography.fontSize ? `${theme.typography.fontSize}px` : "16px"),
2263
+ theme.spacing) {
2264
+ const spacing = "function" == typeof theme.spacing ? theme.spacing : val => 8 * val;
2265
+ vars[`--${prefix}-spacing-unit`] = `${spacing(1)}px`, vars[`--${prefix}-spacing-sm`] = `${spacing(.5)}px`,
2266
+ vars[`--${prefix}-spacing-md`] = `${spacing(1)}px`, vars[`--${prefix}-spacing-lg`] = `${spacing(2)}px`;
2267
+ }
2268
+ return vars;
2269
+ }
3161
2270
 
3162
2271
  /**
3163
- * Get or create global theme applicator
3164
- */ function getThemeApplicator() {
3165
- return globalApplicator || (globalApplicator = new ThemeApplicator), globalApplicator;
2272
+ * Apply consistent theme to component style object
2273
+ */ function applyComponentTheme(component, style = {}, variant, size, theme) {
2274
+ // If no theme provided, return original style
2275
+ return theme ? {
2276
+ ...generateComponentCSSVars(component, theme, variant, size),
2277
+ ...style
2278
+ } : style;
2279
+ // Generate component-specific CSS variables
3166
2280
  }
3167
2281
 
3168
2282
  /**
3169
- * Apply theme using global applicator
3170
- */ function applyTheme(theme) {
3171
- getThemeApplicator().applyTheme(theme);
2283
+ * Create a hook for consistent component theming
2284
+ */ function useComponentTheme(component, variant, size, theme) {
2285
+ return property => getComponentThemeValue(component, property, variant, size);
3172
2286
  }
3173
2287
 
3174
2288
  /**
3175
- * ThemeProvider component
3176
- *
3177
- * Provides theme context to child components and manages theme state.
2289
+ * Theme Configuration Loader
3178
2290
  *
3179
- * **Config-First Approach**: If `defaultTheme` is not provided, loads from `atomix.config.ts`.
3180
- * Config file is required when `defaultTheme` is not provided.
2291
+ * Provides functions to load theme configurations from atomix.config.ts
2292
+ * Includes both sync and async versions, with automatic fallbacks
2293
+ */
2294
+ /**
2295
+ * Load theme from config file (synchronous, Node.js only)
2296
+ * @param configPath - Path to config file (default: atomix.config.ts)
2297
+ * @returns DesignTokens from theme configuration
2298
+ * @throws Error if config loading is not available in browser environment
2299
+ */ function loadThemeFromConfigSync(options) {
2300
+ // Check if we're in a browser environment
2301
+ if ("undefined" != typeof window) throw new Error("loadThemeFromConfigSync: Not available in browser environment. Config loading requires Node.js/SSR environment.");
2302
+ // Use dynamic import to load the config loader
2303
+ // This allows bundlers to handle external dependencies properly
2304
+ let loadAtomixConfig;
2305
+ try {
2306
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
2307
+ const {loadAtomixConfig: loader} = require("../../config/loader");
2308
+ loadAtomixConfig = loader;
2309
+ } catch (error) {
2310
+ if (!1 !== options?.required) throw new Error("Config loader module not available");
2311
+ // Return empty tokens if config is not required
2312
+ return createTokens({});
2313
+ }
2314
+ const config = loadAtomixConfig({
2315
+ configPath: options?.configPath || "atomix.config.ts",
2316
+ required: !1 !== options?.required
2317
+ });
2318
+ return config?.theme ? isThemeObject$1(config.theme) ? createDesignTokensFromTheme(config.theme) : createTokens(config.theme) : createTokens({});
2319
+ }
2320
+
2321
+ /**
2322
+ * Load theme from config file (asynchronous)
2323
+ * @param configPath - Path to config file (default: atomix.config.ts)
2324
+ * @returns Promise resolving to DesignTokens from theme configuration
2325
+ */ async function loadThemeFromConfig(options) {
2326
+ // Check if we're in a browser environment
2327
+ if ("undefined" != typeof window) throw new Error("loadThemeFromConfig: Not available in browser environment. Config loading requires Node.js/SSR environment.");
2328
+ // Dynamic import for config loader
2329
+ const {loadAtomixConfig: loadAtomixConfig} = await import("../lib/config/loader"), config = await loadAtomixConfig({
2330
+ configPath: options?.configPath || "atomix.config.ts",
2331
+ required: !1 !== options?.required
2332
+ });
2333
+ return config?.theme ? isThemeObject$1(config.theme) ? createDesignTokensFromTheme(config.theme) : createTokens(config.theme) : createTokens({});
2334
+ }
2335
+
2336
+ /**
2337
+ * Check if the provided object is a Theme object
2338
+ * @param theme - Object to check
2339
+ * @returns True if the object is a Theme object, false otherwise
2340
+ */ function isThemeObject$1(theme) {
2341
+ return "object" == typeof theme && null !== theme;
2342
+ }
2343
+
2344
+ /**
2345
+ * Theme Context
3181
2346
  *
3182
- * @example
3183
- * ```tsx
3184
- * import { ThemeProvider } from '@shohojdhara/atomix/theme';
2347
+ * React context for theme management
2348
+ */
2349
+ /**
2350
+ * Theme context with default values
2351
+ */ const ThemeContext = createContext(null);
2352
+
2353
+ ThemeContext.displayName = "ThemeContext";
2354
+
2355
+ /**
2356
+ * Theme Provider
3185
2357
  *
3186
- * // Loads from atomix.config.ts (config file required)
3187
- * function App() {
3188
- * return (
3189
- * <ThemeProvider>
3190
- * <YourApp />
3191
- * </ThemeProvider>
3192
- * );
3193
- * }
2358
+ * React context provider for theme management with separated concerns.
2359
+ * Simplified version focusing on core functionality:
2360
+ * - String-based themes (CSS files)
2361
+ * - JS Theme objects
2362
+ * - Persistence via localStorage
3194
2363
  *
3195
- * // Provide explicit theme (bypasses config)
3196
- * function App() {
3197
- * return (
3198
- * <ThemeProvider defaultTheme="dark">
3199
- * <YourApp />
3200
- * </ThemeProvider>
3201
- * );
3202
- * }
3203
- * ```
3204
- */ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes: themes = {}, basePath: basePath = "/themes", cdnPath: cdnPath = null, preload: preload = [], lazy: lazy = !0, storageKey: storageKey = "atomix-theme", dataAttribute: dataAttribute = "data-theme", enablePersistence: enablePersistence = !0, useMinified: useMinified = !1, onThemeChange: onThemeChange, onError: onError}) => {
2364
+ * Falls back to 'default' theme if no configuration is found.
2365
+ */
2366
+ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes: themes = {}, basePath: basePath = "/themes", cdnPath: cdnPath = null, useMinified: useMinified = !1, storageKey: storageKey = "atomix-theme", dataAttribute: dataAttribute = "data-theme", enablePersistence: enablePersistence = !0, onThemeChange: onThemeChange, onError: onError}) => {
3205
2367
  // Store callbacks in refs to avoid recreating when they change
3206
2368
  const onThemeChangeRef = useRef(onThemeChange), onErrorRef = useRef(onError);
3207
- // Update refs when callbacks change
2369
+ // Update ref when callback changes
3208
2370
  useEffect((() => {
3209
2371
  onThemeChangeRef.current = onThemeChange, onErrorRef.current = onError;
3210
2372
  }), [ onThemeChange, onError ]);
3211
- // Create stable wrapper functions that read from refs
2373
+ // Create stable wrapper functions that read from ref
3212
2374
  const handleThemeChange = useCallback((theme => {
3213
2375
  onThemeChangeRef.current?.(theme);
3214
2376
  }), []), handleError = useCallback(((error, themeName) => {
3215
2377
  onErrorRef.current?.(error, themeName);
3216
- }), []), themesRef = useRef(themes), themesStable = useMemo((() => {
3217
- // Only update if themes object actually changed (shallow comparison)
3218
- const currentKeys = Object.keys(themes), prevKeys = Object.keys(themesRef.current);
3219
- return currentKeys.length !== prevKeys.length || currentKeys.some((key => themes[key] !== themesRef.current[key])) ? (themesRef.current = themes,
3220
- themes) : themesRef.current;
3221
- }), [ themes ]), logger = useMemo((() => getLogger()), []), registry = useMemo((() => {
3222
- const reg = new ThemeRegistry;
3223
- // Register themes from props
3224
- if (themesStable && Object.keys(themesStable).length > 0) for (const [themeId, metadata] of Object.entries(themesStable)) reg.has(themeId) || reg.register(themeId, {
3225
- type: "css",
3226
- name: metadata.name,
3227
- class: metadata.class || themeId,
3228
- description: metadata.description,
3229
- author: metadata.author,
3230
- version: metadata.version,
3231
- tags: metadata.tags,
3232
- supportsDarkMode: metadata.supportsDarkMode,
3233
- status: metadata.status,
3234
- a11y: metadata.a11y,
3235
- color: metadata.color,
3236
- features: metadata.features,
3237
- dependencies: metadata.dependencies
3238
- });
3239
- return reg;
3240
- }), [ themesStable ]), storageAdapter = useMemo((() => ({
2378
+ }), []), storageAdapter = useMemo((() => ({
3241
2379
  getItem: key => {
3242
2380
  if (isServer()) return null;
3243
2381
  try {
@@ -3269,7 +2407,7 @@ class ThemeApplicator {
3269
2407
  return !1;
3270
2408
  }
3271
2409
  }
3272
- })), []), themeApplicator = useMemo((() => isServer() ? null : new ThemeApplicator), []), initialDefaultTheme = useMemo((() => {
2410
+ })), []), initialDefaultTheme = useMemo((() => {
3273
2411
  // Check storage first
3274
2412
  if (enablePersistence && storageAdapter.isAvailable()) {
3275
2413
  const stored = storageAdapter.getItem(storageKey);
@@ -3277,236 +2415,368 @@ class ThemeApplicator {
3277
2415
  }
3278
2416
  // If defaultTheme is provided, use it
3279
2417
  if (null != defaultTheme) return defaultTheme;
3280
- // Load from atomix.config.ts (required)
3281
- const configTokens = loadThemeFromConfigSync();
3282
- return configTokens && Object.keys(configTokens).length > 0 ? configTokens : null;
3283
- // Config is required - this will be caught in useEffect
3284
- }), [ enablePersistence, storageAdapter, storageKey, defaultTheme ]), [currentTheme, setCurrentTheme] = useState((() => "string" == typeof initialDefaultTheme ? initialDefaultTheme : isJSTheme(initialDefaultTheme) ? initialDefaultTheme.name || "js-theme" : initialDefaultTheme && "object" == typeof initialDefaultTheme && !isJSTheme(initialDefaultTheme) ? "config-theme" : "" // No default theme - use built-in styles
3285
- )), [activeTheme, setActiveTheme] = useState((() => isJSTheme(initialDefaultTheme) ? initialDefaultTheme : null)), [availableThemes, setAvailableThemes] = useState((() => registry.getAllMetadata().map((meta => ({
3286
- name: meta.name || "",
3287
- class: meta.class,
3288
- description: meta.description,
3289
- author: meta.author,
3290
- version: meta.version,
3291
- tags: meta.tags,
3292
- supportsDarkMode: meta.supportsDarkMode,
3293
- status: meta.status,
3294
- a11y: meta.a11y,
3295
- color: meta.color,
3296
- features: meta.features,
3297
- dependencies: meta.dependencies
3298
- }))))), [isLoading, setIsLoading] = useState(!1), [error, setError] = useState(null), loadedThemesRef = useRef(new Set), previousThemeRef = useRef(null);
3299
- // Get default theme (with automatic config loading)
3300
- useCallback((() => {
3301
- // Check storage first
3302
- if (enablePersistence && storageAdapter.isAvailable()) {
3303
- const stored = storageAdapter.getItem(storageKey);
3304
- if (stored) return stored;
2418
+ // Try to load from atomix.config.ts as fallback, but only in Node.js/SSR environments
2419
+ if ("undefined" == typeof window) try {
2420
+ // Dynamically import the config loader to avoid bundling issues in browser
2421
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
2422
+ const {loadThemeFromConfigSync: loadThemeFromConfigSync} = require("../config/configLoader"), configTokens = loadThemeFromConfigSync();
2423
+ if (configTokens && Object.keys(configTokens).length > 0)
2424
+ // For simplicity, we'll treat config tokens as a special theme name
2425
+ return "config-theme";
2426
+ } catch (error) {
2427
+ console.warn("Failed to load theme from config, using default");
3305
2428
  }
3306
- // If defaultTheme is provided, use it
3307
- if (null != defaultTheme) return defaultTheme;
3308
- // Load from atomix.config.ts (required)
3309
- // Config file must exist - throws error if not found
3310
- const configTokens = loadThemeFromConfigSync();
3311
- if (configTokens && Object.keys(configTokens).length > 0)
3312
- // Return config tokens as Partial<DesignTokens>
3313
- return configTokens;
3314
- throw new Error("ThemeProvider: atomix.config.ts is required when defaultTheme is not provided.");
3315
- }), [ enablePersistence, storageAdapter, storageKey, defaultTheme ]);
3316
- // Apply JS theme (supports both Theme and DesignTokens)
3317
- const applyJSTheme = useCallback((async (theme, removePrevious = !0) => {
3318
- !isServer() && themeApplicator && (removePrevious && (
3319
- // Remove previous theme
3320
- removeCSS("atomix-theme"),
3321
- // Also remove any existing CSS variables
3322
- activeTheme && activeTheme.cssVars && Object.keys(activeTheme.cssVars).forEach((key => {
3323
- document.documentElement.style.removeProperty(key);
3324
- }))), null === theme || "object" != typeof theme || "palette" in theme || "typography" in theme || "__isJSTheme" in theme ?
3325
- // Use ThemeApplicator for Theme objects
3326
- themeApplicator?.applyTheme(theme) : injectCSS$1(createTheme(theme), "atomix-theme"));
3327
- }), [ activeTheme, themeApplicator ]), setTheme = useCallback((async (theme, options) => {
3328
- const {removePrevious: removePrevious = !0, fallbackOnError: fallbackOnError = !0, customPath: customPath} = options || {};
2429
+ // Default fallback
2430
+ return "default";
2431
+ }), [ defaultTheme, enablePersistence, storageKey ]), [currentTheme, setCurrentTheme] = useState((() => initialDefaultTheme)), [activeTheme, setActiveTheme] = useState(null), [isLoading, setIsLoading] = useState(!1), [error, setError] = useState(null), loadedThemesRef = useRef(new Set), themePromisesRef = useRef({});
2432
+ // Apply initial theme attributes to document element
2433
+ useEffect((() => {
2434
+ isServer() || ((dataAttribute, themeName) => {
2435
+ isServer() || (
2436
+ // Set data attribute on body
2437
+ document.body.setAttribute(dataAttribute, themeName),
2438
+ // Also set on documentElement for broader compatibility
2439
+ document.documentElement.setAttribute(dataAttribute, themeName));
2440
+ })(String(currentTheme), dataAttribute);
2441
+ }), [ currentTheme, dataAttribute ]),
2442
+ // Handle theme persistence
2443
+ useEffect((() => {
2444
+ enablePersistence && storageAdapter.isAvailable() && storageAdapter.setItem(storageKey, String(currentTheme));
2445
+ }), [ currentTheme, storageKey, enablePersistence ]);
2446
+ // Function to set theme with proper type handling
2447
+ const setTheme = useCallback((async (theme, options) => {
3329
2448
  setIsLoading(!0), setError(null);
3330
2449
  try {
3331
- // Handle Theme or DesignTokens object directly
3332
- if ("string" != typeof theme) {
3333
- if (null !== theme && "object" == typeof theme && !("palette" in theme) && !("typography" in theme) && !("__isJSTheme" in theme)) {
3334
- // Handle DesignTokens using unified theme system
3335
- await applyJSTheme(theme, removePrevious);
3336
- const themeName = "design-tokens-theme";
3337
- return previousThemeRef.current = currentTheme, setCurrentTheme(themeName), setActiveTheme(null),
3338
- previousThemeRef.current, Date.now(), handleThemeChange(themeName),
3339
- // Persist to storage
3340
- enablePersistence && storageAdapter.isAvailable() && storageAdapter.setItem(storageKey, themeName),
3341
- void setIsLoading(!1);
3342
- }
3343
- if (isJSTheme(theme)) {
3344
- // Handle Theme object
3345
- await applyJSTheme(theme, removePrevious);
3346
- const themeName = theme.name || "js-theme";
3347
- return previousThemeRef.current = currentTheme, setCurrentTheme(themeName), setActiveTheme(theme),
3348
- previousThemeRef.current, Date.now(), handleThemeChange(theme),
3349
- // Persist to storage
3350
- enablePersistence && storageAdapter.isAvailable() && storageAdapter.setItem(storageKey, themeName),
3351
- void setIsLoading(!1);
3352
- }
3353
- {
3354
- const error = new Error("Invalid theme object provided");
3355
- throw handleError(error, "js-theme"), setError(error), setIsLoading(!1), error;
3356
- }
3357
- }
3358
- // Check if theme exists
3359
- if (!registry.has(theme)) {
3360
- const error = new Error(`Theme "${theme}" not found in registry`);
3361
- if (handleError(error, theme), setError(error), fallbackOnError && currentTheme) return void setIsLoading(!1);
3362
- throw setIsLoading(!1), error;
3363
- }
3364
- // Load theme CSS if needed
3365
- const themePath = customPath || buildThemePath(theme, basePath, useMinified, cdnPath || void 0), linkId = getThemeLinkId(theme);
3366
- // Remove previous theme if requested
3367
- removePrevious && previousThemeRef.current && previousThemeRef.current !== theme && (themeNameOrLinkId => {
3368
- if (isServer()) return;
3369
- // Try as link ID first, then as theme name
3370
- let link = document.getElementById(themeNameOrLinkId);
3371
- if (!link) {
3372
- const linkId = getThemeLinkId(themeNameOrLinkId);
3373
- link = document.getElementById(linkId);
3374
- }
3375
- link && link.remove();
3376
- })(previousThemeRef.current),
3377
- // Load CSS if not already loaded
3378
- isThemeLoaded(theme) || (await loadThemeCSS(themePath, linkId), loadedThemesRef.current.add(theme)),
3379
- // Apply theme attributes
3380
- ((dataAttribute, themeName) => {
3381
- isServer() || (
3382
- // Set data attribute on body
3383
- document.body.setAttribute(dataAttribute, themeName),
3384
- // Also set on documentElement for broader compatibility
3385
- document.documentElement.setAttribute(dataAttribute, themeName));
3386
- })(dataAttribute, theme),
3387
- // Update state
3388
- previousThemeRef.current = currentTheme, setCurrentTheme(theme), setActiveTheme(null),
3389
- previousThemeRef.current, Date.now(), handleThemeChange(theme),
3390
- // Persist to storage
3391
- enablePersistence && storageAdapter.isAvailable() && storageAdapter.setItem(storageKey, theme),
3392
- setIsLoading(!1);
2450
+ let themeName, themeObj = null;
2451
+ // If it's a string theme name, load the associated CSS
2452
+ if ("string" == typeof theme ? themeName = theme :
2453
+ // If it's a Theme object or DesignTokens, we need to process it
2454
+ function(theme) {
2455
+ return "object" == typeof theme && null !== theme && "__isJSTheme" in theme && !0 === theme.__isJSTheme;
2456
+ }(theme) ? (themeObj = theme,
2457
+ // For JS themes, we use a generic name
2458
+ themeName = "js-theme", setActiveTheme(themeObj)) :
2459
+ // For DesignTokens, we might create a theme from tokens
2460
+ themeName = "tokens-theme", "string" == typeof theme && themes[theme]) {
2461
+ // Check if theme is already loading
2462
+ if (themePromisesRef.current[theme]) return await themePromisesRef.current[theme],
2463
+ setCurrentTheme(theme), setActiveTheme(null), void handleThemeChange(theme);
2464
+ // Load CSS theme
2465
+ const themeLoadPromise = new Promise((async (resolve, reject) => {
2466
+ try {
2467
+ if (!themes[theme]) throw new Error(`Theme metadata not found for theme: ${theme}`);
2468
+ {
2469
+ // Build CSS path using utility function
2470
+ const cssPath = buildThemePath(theme, basePath, useMinified, cdnPath);
2471
+ // Remove any previously loaded theme CSS
2472
+ removeCSS(`theme-${String(currentTheme)}`),
2473
+ // Inject new theme CSS
2474
+ await injectCSS$1(cssPath, `theme-${theme}`), loadedThemesRef.current.add(theme),
2475
+ setCurrentTheme(theme), setActiveTheme(null), handleThemeChange(theme), resolve();
2476
+ }
2477
+ } catch (err) {
2478
+ const error = err instanceof Error ? err : new Error(String(err));
2479
+ setError(error), handleError(error, String(theme)), reject(error);
2480
+ }
2481
+ }));
2482
+ themePromisesRef.current[theme] = themeLoadPromise, await themeLoadPromise;
2483
+ } else themeObj ? (
2484
+ // For JS themes, set them directly
2485
+ setCurrentTheme(themeName), setActiveTheme(themeObj), handleThemeChange(themeObj)) : (
2486
+ // For string theme that isn't in our themes record, just set the name
2487
+ setCurrentTheme(themeName), setActiveTheme(null), handleThemeChange(themeName));
3393
2488
  } catch (err) {
3394
2489
  const error = err instanceof Error ? err : new Error(String(err));
3395
- throw handleError(error, "string" == typeof theme ? theme : "js-theme"), setError(error),
3396
- setIsLoading(!1), err;
2490
+ setError(error), handleError(error, String(theme));
2491
+ } finally {
2492
+ setIsLoading(!1);
3397
2493
  }
3398
- }), [ registry, basePath, cdnPath, useMinified, dataAttribute, enablePersistence, storageAdapter, storageKey, currentTheme, activeTheme, applyJSTheme, handleThemeChange, handleError ]), preloadTheme = useCallback((async themeName => {
3399
- if (!isServer() && !isThemeLoaded(themeName)) {
2494
+ }), [ themes, currentTheme, handleThemeChange, handleError, basePath, useMinified, cdnPath ]), isThemeLoaded = useCallback((themeName => loadedThemesRef.current.has(themeName)), []), preloadTheme = useCallback((async themeName => {
2495
+ if (themes[themeName] && !isThemeLoaded(themeName)) {
3400
2496
  setIsLoading(!0);
3401
2497
  try {
3402
- if (!registry.has(themeName)) throw new Error(`Theme "${themeName}" not found in registry`);
3403
- const themePath = buildThemePath(themeName, basePath, useMinified, cdnPath || void 0), linkId = getThemeLinkId(themeName);
3404
- await loadThemeCSS(themePath, linkId), loadedThemesRef.current.add(themeName);
2498
+ // Build CSS path using utility function
2499
+ const cssPath = buildThemePath(themeName, basePath, useMinified, cdnPath);
2500
+ // Preload CSS by fetching it
2501
+ await fetch(cssPath), loadedThemesRef.current.add(themeName);
3405
2502
  } catch (err) {
3406
2503
  const error = err instanceof Error ? err : new Error(String(err));
3407
- handleError(error, themeName), setError(error);
2504
+ setError(error), handleError(error, themeName);
3408
2505
  } finally {
3409
2506
  setIsLoading(!1);
3410
2507
  }
3411
2508
  }
3412
- }), [ registry, basePath, cdnPath, useMinified, handleError ]), isThemeLoaded$1 = useCallback((themeName => isThemeLoaded(themeName)), []);
3413
- // Set theme function (supports string, Theme, or DesignTokens)
3414
- // Initialize default theme on mount
3415
- useEffect((() => {
3416
- isServer() || (async () => {
3417
- // Use the initial default theme we computed
3418
- const defaultThemeValue = initialDefaultTheme;
3419
- if (defaultThemeValue) try {
3420
- null === defaultThemeValue || "object" != typeof defaultThemeValue || "palette" in defaultThemeValue || "typography" in defaultThemeValue || "__isJSTheme" in defaultThemeValue || "string" == typeof defaultThemeValue ?
3421
- // Handle string or Theme object
3422
- await setTheme(defaultThemeValue, {
3423
- removePrevious: !1,
3424
- fallbackOnError: !0
3425
- }) : (
3426
- // Apply config tokens directly
3427
- await applyJSTheme(defaultThemeValue, !1),
3428
- // Update state and emit events
3429
- setCurrentTheme("config-theme"), setActiveTheme(null), Date.now(), handleThemeChange("config-theme"),
3430
- // Persist to storage
3431
- enablePersistence && storageAdapter.isAvailable() && storageAdapter.setItem(storageKey, "config-theme"));
3432
- } catch (err) {
3433
- const error = err instanceof Error ? err : new Error(String(err));
3434
- throw logger.error("Failed to load theme from config", error, {
3435
- theme: defaultThemeValue
3436
- }), handleError(error, "config-theme"), setError(error), error;
2509
+ }), [ themes, isThemeLoaded, handleError, basePath, useMinified, cdnPath ]), themeManager = useMemo((() => ({})), []), contextValue = useMemo((() => ({
2510
+ theme: "string" == typeof currentTheme ? currentTheme : "js-theme",
2511
+ activeTheme: activeTheme,
2512
+ setTheme: setTheme,
2513
+ availableThemes: Object.entries(themes).map((([name, metadata]) => ({
2514
+ ...metadata
2515
+ }))),
2516
+ isLoading: isLoading,
2517
+ error: error,
2518
+ isThemeLoaded: isThemeLoaded,
2519
+ preloadTheme: preloadTheme,
2520
+ themeManager: themeManager
2521
+ })), [ currentTheme, activeTheme, setTheme, themes, isLoading, error, isThemeLoaded, preloadTheme, themeManager ]);
2522
+ // Check if theme is loaded
2523
+ return jsx(ThemeContext.Provider, {
2524
+ value: contextValue,
2525
+ children: children
2526
+ });
2527
+ };
2528
+
2529
+ /**
2530
+ * useTheme Hook
2531
+ *
2532
+ * React hook for accessing theme context
2533
+ */
2534
+ /**
2535
+ * useTheme hook
2536
+ *
2537
+ * Access theme context and theme management functions
2538
+ *
2539
+ * @example
2540
+ * ```tsx
2541
+ * function MyComponent() {
2542
+ * const { theme, setTheme, availableThemes } = useTheme();
2543
+ *
2544
+ * return (
2545
+ * <div>
2546
+ * <p>Current theme: {theme}</p>
2547
+ * <button onClick={() => setTheme('dark-theme')}>
2548
+ * Switch to Dark
2549
+ * </button>
2550
+ * </div>
2551
+ * );
2552
+ * }
2553
+ * ```
2554
+ */ function useTheme() {
2555
+ const context = useContext(ThemeContext);
2556
+ if (!context) throw new Error("useTheme must be used within a ThemeProvider");
2557
+ return {
2558
+ theme: context.theme,
2559
+ activeTheme: context.activeTheme,
2560
+ setTheme: context.setTheme,
2561
+ availableThemes: context.availableThemes,
2562
+ isLoading: context.isLoading,
2563
+ error: context.error,
2564
+ isThemeLoaded: context.isThemeLoaded,
2565
+ preloadTheme: context.preloadTheme
2566
+ };
2567
+ }
2568
+
2569
+ function useThemeTokens() {
2570
+ const {theme: theme, activeTheme: activeTheme} = useTheme(), getToken = useCallback(((tokenName, fallback) => {
2571
+ if ("undefined" == typeof window) return fallback || "";
2572
+ const cssVarName = `--atomix-${tokenName}`;
2573
+ return getComputedStyle(document.documentElement).getPropertyValue(cssVarName).trim() || fallback || "";
2574
+ }), []), getThemeValue = useCallback(((path, fallback) => {
2575
+ var _context;
2576
+ return activeTheme && _reduceInstanceProperty(_context = path.split(".")).call(_context, ((obj, key) => obj?.[key]), activeTheme) || fallback;
2577
+ // Navigate through nested theme object using dot notation
2578
+ }), [ activeTheme ]);
2579
+ // Helper function to get CSS variable value
2580
+ // Return unified API for accessing theme values
2581
+ return {
2582
+ theme: theme,
2583
+ activeTheme: activeTheme,
2584
+ getToken: getToken,
2585
+ getThemeValue: getThemeValue,
2586
+ // Commonly used tokens with fallbacks
2587
+ colors: {
2588
+ primary: getToken("primary", "#3b82f6"),
2589
+ secondary: getToken("secondary", "#10b981"),
2590
+ error: getToken("error", "#ef4444"),
2591
+ success: getToken("success", "#22c55e"),
2592
+ warning: getToken("warning", "#eab308"),
2593
+ info: getToken("info", "#3b82f6"),
2594
+ light: getToken("light", "#f9fafb"),
2595
+ dark: getToken("dark", "#111827")
2596
+ },
2597
+ spacing: {
2598
+ 1: getToken("spacing-1", "0.25rem"),
2599
+ 2: getToken("spacing-2", "0.5rem"),
2600
+ 3: getToken("spacing-3", "0.75rem"),
2601
+ 4: getToken("spacing-4", "1rem"),
2602
+ 5: getToken("spacing-5", "1.25rem"),
2603
+ 6: getToken("spacing-6", "1.5rem"),
2604
+ 8: getToken("spacing-8", "2rem"),
2605
+ 10: getToken("spacing-10", "2.5rem"),
2606
+ 12: getToken("spacing-12", "3rem"),
2607
+ 16: getToken("spacing-16", "4rem"),
2608
+ 20: getToken("spacing-20", "5rem")
2609
+ },
2610
+ borderRadius: {
2611
+ sm: getToken("border-radius-sm", "0.25rem"),
2612
+ md: getToken("border-radius-md", "0.5rem"),
2613
+ lg: getToken("border-radius-lg", "0.75rem"),
2614
+ xl: getToken("border-radius-xl", "1rem"),
2615
+ full: getToken("border-radius-full", "9999px")
2616
+ },
2617
+ typography: {
2618
+ fontFamily: {
2619
+ sans: getToken("font-sans-serif", "Inter, system-ui, sans-serif"),
2620
+ serif: getToken("font-serif", "Georgia, serif"),
2621
+ mono: getToken("font-monospace", "Fira Code, monospace")
2622
+ },
2623
+ fontSize: {
2624
+ xs: getToken("font-size-xs", "0.75rem"),
2625
+ sm: getToken("font-size-sm", "0.875rem"),
2626
+ md: getToken("font-size-md", "1rem"),
2627
+ lg: getToken("font-size-lg", "1.125rem"),
2628
+ xl: getToken("font-size-xl", "1.25rem"),
2629
+ "2xl": getToken("font-size-2xl", "1.5rem"),
2630
+ "3xl": getToken("font-size-3xl", "1.875rem"),
2631
+ "4xl": getToken("font-size-4xl", "2.25rem")
2632
+ },
2633
+ fontWeight: {
2634
+ light: getToken("font-weight-light", "300"),
2635
+ normal: getToken("font-weight-normal", "400"),
2636
+ medium: getToken("font-weight-medium", "500"),
2637
+ semibold: getToken("font-weight-semibold", "600"),
2638
+ bold: getToken("font-weight-bold", "700")
3437
2639
  }
3438
- })();
2640
+ },
2641
+ shadows: {
2642
+ sm: getToken("box-shadow-sm", "0 1px 2px 0 rgba(0, 0, 0, 0.05)"),
2643
+ md: getToken("box-shadow-md", "0 4px 6px -1px rgba(0, 0, 0, 0.1)"),
2644
+ lg: getToken("box-shadow-lg", "0 10px 15px -3px rgba(0, 0, 0, 0.1)"),
2645
+ xl: getToken("box-shadow-xl", "0 20px 25px -5px rgba(0, 0, 0, 0.1)"),
2646
+ inset: getToken("box-shadow-inset", "inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)")
2647
+ },
2648
+ transitions: {
2649
+ fast: getToken("transition-fast", "150ms"),
2650
+ base: getToken("transition-base", "200ms"),
2651
+ slow: getToken("transition-slow", "300ms")
2652
+ }
2653
+ };
2654
+ }
2655
+
2656
+ /**
2657
+ * Theme System Error Handling
2658
+ *
2659
+ * Centralized error handling for the Atomix theme system.
2660
+ * Provides custom error classes and logging utilities.
2661
+ */
2662
+ /**
2663
+ * Theme error codes
2664
+ */ var ThemeErrorCode, LogLevel;
2665
+
2666
+ !function(ThemeErrorCode) {
2667
+ /** Theme not found in registry */
2668
+ ThemeErrorCode.THEME_NOT_FOUND = "THEME_NOT_FOUND",
2669
+ /** Theme failed to load */
2670
+ ThemeErrorCode.THEME_LOAD_FAILED = "THEME_LOAD_FAILED",
2671
+ /** Theme validation failed */
2672
+ ThemeErrorCode.THEME_VALIDATION_FAILED = "THEME_VALIDATION_FAILED",
2673
+ /** Configuration loading failed */
2674
+ ThemeErrorCode.CONFIG_LOAD_FAILED = "CONFIG_LOAD_FAILED",
2675
+ /** Configuration validation failed */
2676
+ ThemeErrorCode.CONFIG_VALIDATION_FAILED = "CONFIG_VALIDATION_FAILED",
2677
+ /** Circular dependency detected */
2678
+ ThemeErrorCode.CIRCULAR_DEPENDENCY = "CIRCULAR_DEPENDENCY",
2679
+ /** Missing dependency */
2680
+ ThemeErrorCode.MISSING_DEPENDENCY = "MISSING_DEPENDENCY",
2681
+ /** Storage operation failed */
2682
+ ThemeErrorCode.STORAGE_ERROR = "STORAGE_ERROR",
2683
+ /** Invalid theme name */
2684
+ ThemeErrorCode.INVALID_THEME_NAME = "INVALID_THEME_NAME",
2685
+ /** CSS injection failed */
2686
+ ThemeErrorCode.CSS_INJECTION_FAILED = "CSS_INJECTION_FAILED",
2687
+ /** Unknown error */
2688
+ ThemeErrorCode.UNKNOWN_ERROR = "UNKNOWN_ERROR";
2689
+ }(ThemeErrorCode || (ThemeErrorCode = {}));
2690
+
2691
+ /**
2692
+ * Custom error class for theme-related errors
2693
+ */
2694
+ class ThemeError extends Error {
2695
+ constructor(message, code = ThemeErrorCode.UNKNOWN_ERROR, context) {
2696
+ super(message), this.name = "ThemeError", this.code = code, this.context = context,
2697
+ this.timestamp = Date.now(),
2698
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
2699
+ Error.captureStackTrace && Error.captureStackTrace(this, ThemeError);
2700
+ }
2701
+ /**
2702
+ * Convert error to JSON for logging
2703
+ */ toJSON() {
2704
+ return {
2705
+ name: this.name,
2706
+ message: this.message,
2707
+ code: this.code,
2708
+ context: this.context,
2709
+ timestamp: this.timestamp,
2710
+ stack: this.stack
2711
+ };
2712
+ }
2713
+ }
2714
+
2715
+ /**
2716
+ * Log level
2717
+ */ !function(LogLevel) {
2718
+ LogLevel[LogLevel.ERROR = 0] = "ERROR", LogLevel[LogLevel.WARN = 1] = "WARN", LogLevel[LogLevel.INFO = 2] = "INFO",
2719
+ LogLevel[LogLevel.DEBUG = 3] = "DEBUG";
2720
+ }(LogLevel || (LogLevel = {}));
2721
+
2722
+ /**
2723
+ * Theme Logger
2724
+ *
2725
+ * Centralized logging for the theme system.
2726
+ * Replaces console statements with structured logging.
2727
+ */
2728
+ class ThemeLogger {
2729
+ constructor(config = {}) {
2730
+ this.config = {
2731
+ level: config.level ?? ("undefined" != typeof process && "production" === process.env?.NODE_ENV ? LogLevel.WARN : LogLevel.INFO),
2732
+ enableConsole: config.enableConsole ?? !0,
2733
+ onError: config.onError,
2734
+ onWarn: config.onWarn,
2735
+ onInfo: config.onInfo,
2736
+ onDebug: config.onDebug
2737
+ };
3439
2738
  }
3440
- // eslint-disable-next-line react-hooks/exhaustive-deps
3441
- ), []), // Only run once on mount - initialDefaultTheme is stable
3442
- // Preload themes
3443
- useEffect((() => {
3444
- !isServer() && preload && 0 !== preload.length && (async () => {
3445
- for (const themeName of preload) if (!isThemeLoaded(themeName)) try {
3446
- await preloadTheme(themeName);
3447
- } catch (err) {
3448
- // Silently fail for preload
3449
- logger.warn(`Failed to preload theme "${themeName}"`, {
3450
- error: err instanceof Error ? err.message : String(err)
3451
- });
3452
- }
3453
- })();
3454
- }), [ preload, preloadTheme, logger ]);
3455
- // Context value
3456
- const contextValue = useMemo((() => ({
3457
- theme: currentTheme,
3458
- activeTheme: activeTheme,
3459
- setTheme: setTheme,
3460
- availableThemes: availableThemes,
3461
- isLoading: isLoading,
3462
- error: error,
3463
- isThemeLoaded: isThemeLoaded$1,
3464
- preloadTheme: preloadTheme
3465
- })), [ currentTheme, activeTheme, setTheme, availableThemes, isLoading, error, isThemeLoaded$1, preloadTheme ]);
3466
- return jsx(ThemeContext.Provider, {
3467
- value: contextValue,
3468
- children: children
3469
- });
3470
- };
2739
+ /**
2740
+ * Log an error
2741
+ */ error(message, error, context) {
2742
+ if (this.config.level < LogLevel.ERROR) return;
2743
+ const errorObj = error instanceof Error ? error : new Error(message), themeError = error instanceof ThemeError ? error : new ThemeError(message, ThemeErrorCode.UNKNOWN_ERROR, context);
2744
+ this.config.enableConsole && console.error(`[ThemeError] ${message}`, {
2745
+ error: errorObj,
2746
+ context: {
2747
+ ...context,
2748
+ ...themeError.context
2749
+ },
2750
+ code: themeError.code
2751
+ }), this.config.onError?.(themeError, context);
2752
+ }
2753
+ /**
2754
+ * Log a warning
2755
+ */ warn(message, context) {
2756
+ this.config.level < LogLevel.WARN || (this.config.enableConsole && console.warn(`[ThemeWarning] ${message}`, context || {}),
2757
+ this.config.onWarn?.(message, context));
2758
+ }
2759
+ /**
2760
+ * Log an info message
2761
+ */ info(message, context) {
2762
+ this.config.level < LogLevel.INFO || (this.config.enableConsole && console.info(`[ThemeInfo] ${message}`, context || {}),
2763
+ this.config.onInfo?.(message, context));
2764
+ }
2765
+ /**
2766
+ * Log a debug message
2767
+ */ debug(message, context) {
2768
+ this.config.level < LogLevel.DEBUG || (this.config.enableConsole, this.config.onDebug?.(message, context));
2769
+ }
2770
+ }
3471
2771
 
3472
2772
  /**
3473
- * useTheme Hook
3474
- *
3475
- * React hook for accessing theme context
3476
- */
2773
+ * Default logger instance
2774
+ */ let defaultLogger = null;
2775
+
3477
2776
  /**
3478
- * useTheme hook
3479
- *
3480
- * Access theme context and theme management functions
3481
- *
3482
- * @example
3483
- * ```tsx
3484
- * function MyComponent() {
3485
- * const { theme, setTheme, availableThemes } = useTheme();
3486
- *
3487
- * return (
3488
- * <div>
3489
- * <p>Current theme: {theme}</p>
3490
- * <button onClick={() => setTheme('dark-theme')}>
3491
- * Switch to Dark
3492
- * </button>
3493
- * </div>
3494
- * );
3495
- * }
3496
- * ```
3497
- */ function useTheme() {
3498
- const context = useContext(ThemeContext);
3499
- if (!context) throw new Error("useTheme must be used within a ThemeProvider");
3500
- return {
3501
- theme: context.theme,
3502
- activeTheme: context.activeTheme,
3503
- setTheme: context.setTheme,
3504
- availableThemes: context.availableThemes,
3505
- isLoading: context.isLoading,
3506
- error: context.error,
3507
- isThemeLoaded: context.isThemeLoaded,
3508
- preloadTheme: context.preloadTheme
3509
- };
2777
+ * Get or create default logger
2778
+ */ function getLogger() {
2779
+ return defaultLogger || (defaultLogger = new ThemeLogger), defaultLogger;
3510
2780
  }
3511
2781
 
3512
2782
  /**
@@ -3557,7 +2827,7 @@ class ThemeApplicator {
3557
2827
  },
3558
2828
  children: JSON.stringify(context, null, 2)
3559
2829
  }) ]
3560
- }), "development" === process.env.NODE_ENV && errorInfo && jsxs("details", {
2830
+ }), ("undefined" == typeof process || "development" === process.env?.NODE_ENV) && errorInfo && jsxs("details", {
3561
2831
  style: {
3562
2832
  marginTop: "1rem"
3563
2833
  },
@@ -3653,6 +2923,130 @@ class ThemeApplicator {
3653
2923
  }
3654
2924
  }
3655
2925
 
2926
+ /**
2927
+ * Theme Applicator
2928
+ *
2929
+ * Applies theme configurations to the DOM, including CSS variables,
2930
+ * component overrides, typography, spacing, and color palettes.
2931
+ *
2932
+ * Uses the unified theme system for CSS generation and injection.
2933
+ */
2934
+ /**
2935
+ * Theme applicator class for runtime theme application
2936
+ *
2937
+ * Uses the unified theme system for efficient CSS variable generation and injection.
2938
+ */ class ThemeApplicator {
2939
+ constructor(root = document.documentElement) {
2940
+ this.styleId = "atomix-theme-applicator", this.root = root;
2941
+ }
2942
+ /**
2943
+ * Apply a complete theme configuration
2944
+ *
2945
+ * Uses the unified theme system to convert Theme to DesignTokens and inject CSS.
2946
+ * Automatically respects atomix.config.ts when using DesignTokens.
2947
+ */ applyTheme(theme) {
2948
+ // Clear previously applied variables
2949
+ this.clearAppliedVars(),
2950
+ // Check if it's DesignTokens
2951
+ this.isDesignTokens(theme) ?
2952
+ // Direct DesignTokens - use unified theme system (with config support)
2953
+ this.applyDesignTokens(theme) : injectCSS$1(createTheme(theme, {
2954
+ selector: ":root",
2955
+ prefix: "atomix"
2956
+ }), this.styleId),
2957
+ // Apply component overrides (only for Theme objects)
2958
+ !this.isDesignTokens(theme) && theme.components && this.applyComponentOverrides(theme.components);
2959
+ }
2960
+ /**
2961
+ * Apply DesignTokens using unified theme system
2962
+ *
2963
+ * Uses createTheme() which automatically loads from atomix.config.ts
2964
+ * if no tokens are provided, ensuring config is always respected.
2965
+ */ applyDesignTokens(tokens) {
2966
+ // Inject CSS into DOM
2967
+ injectCSS$1(createTheme(tokens, {
2968
+ selector: ":root",
2969
+ prefix: "atomix"
2970
+ }), this.styleId);
2971
+ }
2972
+ /**
2973
+ * Check if object is DesignTokens
2974
+ */ isDesignTokens(obj) {
2975
+ // DesignTokens is a flat object with string keys, no nested structures
2976
+ return null !== obj && "object" == typeof obj && !("palette" in obj) && !("typography" in obj) && !("__isJSTheme" in obj);
2977
+ }
2978
+ /**
2979
+ * Apply global CSS variables (for component overrides)
2980
+ */ applyGlobalCSSVars(vars) {
2981
+ Object.entries(vars).forEach((([key, value]) => {
2982
+ this.root.style.setProperty(key, String(value));
2983
+ }));
2984
+ }
2985
+ /**
2986
+ * Apply component-level overrides
2987
+ */ applyComponentOverrides(overrides) {
2988
+ Object.entries(overrides).forEach((([componentName, override]) => {
2989
+ override && this.applyComponentOverride(componentName, override);
2990
+ }));
2991
+ }
2992
+ /**
2993
+ * Apply override for a specific component
2994
+ */ applyComponentOverride(componentName, override) {
2995
+ const vars = {}, componentKey = componentName.toLowerCase();
2996
+ // Apply component-level CSS variables
2997
+ override.cssVars && Object.entries(override.cssVars).forEach((([key, value]) => {
2998
+ // If key doesn't start with --, add component prefix
2999
+ const varKey = key.startsWith("--") ? key : `--atomix-${componentKey}-${key}`;
3000
+ vars[varKey] = value;
3001
+ })),
3002
+ // Apply part-specific CSS variables
3003
+ override.parts && Object.entries(override.parts).forEach((([partName, partOverride]) => {
3004
+ partOverride.cssVars && Object.entries(partOverride.cssVars).forEach((([key, value]) => {
3005
+ const varKey = key.startsWith("--") ? key : `--atomix-${componentKey}-${partName}-${key}`;
3006
+ vars[varKey] = value;
3007
+ }));
3008
+ })),
3009
+ // Apply variant-specific CSS variables
3010
+ override.variants && Object.entries(override.variants).forEach((([variantName, variantOverride]) => {
3011
+ variantOverride.cssVars && Object.entries(variantOverride.cssVars).forEach((([key, value]) => {
3012
+ const varKey = key.startsWith("--") ? key : `--atomix-${componentKey}-${variantName}-${key}`;
3013
+ vars[varKey] = value;
3014
+ }));
3015
+ })), this.applyGlobalCSSVars(vars);
3016
+ }
3017
+ /**
3018
+ * Clear all applied CSS variables
3019
+ */ clearAppliedVars() {
3020
+ removeCSS(this.styleId);
3021
+ }
3022
+ /**
3023
+ * Remove theme application
3024
+ */ removeTheme() {
3025
+ this.clearAppliedVars(), removeCSS(this.styleId);
3026
+ }
3027
+ /**
3028
+ * Update specific CSS variables without clearing all
3029
+ */ updateCSSVars(vars) {
3030
+ this.applyGlobalCSSVars(vars);
3031
+ }
3032
+ }
3033
+
3034
+ /**
3035
+ * Global theme applicator instance
3036
+ */ let globalApplicator = null;
3037
+
3038
+ /**
3039
+ * Get or create global theme applicator
3040
+ */ function getThemeApplicator() {
3041
+ return globalApplicator || (globalApplicator = new ThemeApplicator), globalApplicator;
3042
+ }
3043
+
3044
+ /**
3045
+ * Apply theme using global applicator
3046
+ */ function applyTheme(theme) {
3047
+ getThemeApplicator().applyTheme(theme);
3048
+ }
3049
+
3656
3050
  const VIEWPORT_PRESETS = {
3657
3051
  mobile: {
3658
3052
  width: 375,
@@ -5601,62 +4995,6 @@ const ThemeLiveEditor = ({initialTheme: initialTheme, onChange: onChange, classN
5601
4995
  });
5602
4996
  };
5603
4997
 
5604
- /**
5605
- * CSS Variable Mapper
5606
- *
5607
- * Utilities for generating and managing CSS custom properties from SCSS tokens
5608
- * and component configurations.
5609
- */
5610
- /**
5611
- * Generate CSS variable name from parts
5612
- *
5613
- * @example
5614
- * generateCSSVariableName('button', 'bg', { prefix: 'atomix' })
5615
- * // Returns: '--atomix-button-bg'
5616
- */ function generateCSSVariableName(component, property, options = {}) {
5617
- const {prefix: prefix = "atomix", separator: separator = "-", includeComponent: includeComponent = !0} = options, parts = [ prefix ];
5618
- return includeComponent && parts.push(component), parts.push(property), `--${parts.join(separator)}`;
5619
- }
5620
-
5621
- /**
5622
- * Generate CSS variables object from configuration
5623
- *
5624
- * @example
5625
- * const vars = generateComponentCSSVars({
5626
- * component: 'button',
5627
- * properties: { bg: '#000', color: '#fff' }
5628
- * })
5629
- * // Returns: { '--atomix-button-bg': '#000', '--atomix-button-color': '#fff' }
5630
- */ function generateComponentCSSVars(config, options = {}) {
5631
- const vars = {}, {component: component, properties: properties, parts: parts, states: states, variants: variants} = config;
5632
- // Base properties
5633
- return Object.entries(properties).forEach((([key, value]) => {
5634
- const varName = generateCSSVariableName(component, key, options);
5635
- vars[varName] = String(value);
5636
- })),
5637
- // Part properties
5638
- parts && Object.entries(parts).forEach((([partName, partProps]) => {
5639
- Object.entries(partProps).forEach((([key, value]) => {
5640
- const varName = generateCSSVariableName(component, `${partName}-${key}`, options);
5641
- vars[varName] = String(value);
5642
- }));
5643
- })),
5644
- // State properties
5645
- states && Object.entries(states).forEach((([stateName, stateProps]) => {
5646
- Object.entries(stateProps).forEach((([key, value]) => {
5647
- const varName = generateCSSVariableName(component, `${stateName}-${key}`, options);
5648
- vars[varName] = String(value);
5649
- }));
5650
- })),
5651
- // Variant properties
5652
- variants && Object.entries(variants).forEach((([variantName, variantProps]) => {
5653
- Object.entries(variantProps).forEach((([key, value]) => {
5654
- const varName = generateCSSVariableName(component, `${variantName}-${key}`, options);
5655
- vars[varName] = String(value);
5656
- }));
5657
- })), vars;
5658
- }
5659
-
5660
4998
  /**
5661
4999
  * Map SCSS tokens to CSS custom properties
5662
5000
  *
@@ -6022,63 +5360,26 @@ class RTLManager {
6022
5360
  /**
6023
5361
  * Save theme to CSS file
6024
5362
  */ async function saveTheme(css, filePath) {
6025
- await saveCSSFile(css, filePath);
6026
- }
6027
-
6028
- /**
6029
- * Atomix Config Loader
5363
+ await async function(css, filePath) {
5364
+ // Check if in browser environment
5365
+ if ("undefined" != typeof window) throw new Error("saveCSSFile can only be used in Node.js environment. Use injectCSS() for browser environments.");
5366
+ // Dynamic import to avoid bundling Node.js modules in browser builds
5367
+ const fs = await import("fs/promises"), dir = (await import("path")).dirname(filePath);
5368
+ await fs.mkdir(dir, {
5369
+ recursive: !0
5370
+ }),
5371
+ // Write file
5372
+ await fs.writeFile(filePath, css, "utf8");
5373
+ }
5374
+ /**
5375
+ * Theme System Constants
6030
5376
  *
6031
- * Helper functions to load atomix.config.ts from external projects.
6032
- * Similar to how Tailwind loads tailwind.config.js
5377
+ * Centralized constants for the theme system to avoid magic numbers and strings.
6033
5378
  */
6034
- /**
6035
- * Load Atomix configuration from project root
6036
- *
6037
- * Attempts to load atomix.config.ts from the current working directory.
6038
- * Falls back to default config if file doesn't exist.
6039
- *
6040
- * @param options - Loader options
6041
- * @returns Loaded configuration or default
6042
- *
6043
- * @example
6044
- * ```typescript
6045
- * import { loadAtomixConfig } from '@shohojdhara/atomix/config';
6046
- * import { createTheme } from '@shohojdhara/atomix/theme';
6047
- *
6048
- * const config = loadAtomixConfig();
6049
- * const theme = createTheme(config.theme?.tokens || {});
6050
- * ```
6051
- */ const loader = Object.freeze( Object.defineProperty({
6052
- __proto__: null,
6053
- loadAtomixConfig: function(options = {}) {
6054
- const {configPath: configPath = "atomix.config.ts", required: required = !1} = options, defaultConfig = {
6055
- prefix: "atomix",
6056
- theme: {
6057
- extend: {}
6058
- }
6059
- };
6060
- // Default config
6061
- // In browser environments, config loading is not supported
6062
- if ("undefined" != typeof window) {
6063
- if (required) throw new Error("Config loading not supported in browser environment");
6064
- return defaultConfig;
6065
- }
6066
- // Try to load config file
6067
- try {
6068
- // Use dynamic import for ESM compatibility
6069
- const configModule = require(configPath), config = configModule.default || configModule;
6070
- // Validate it's an AtomixConfig
6071
- if (config && "object" == typeof config) return config;
6072
- throw new Error("Invalid config format");
6073
- } catch (error) {
6074
- if (required) throw new Error(`Failed to load config from ${configPath}: ${error.message}`);
6075
- // Return default config if not required
6076
- return defaultConfig;
6077
- }
6078
- }
6079
- }, Symbol.toStringTag, {
6080
- value: "Module"
6081
- }));
5379
+ /**
5380
+ * Default storage key for theme persistence
5381
+ */ (css, filePath);
5382
+ }
6082
5383
 
6083
- export { RTLManager, ThemeApplicator, ThemeComparator, ThemeContext, ThemeErrorBoundary, ThemeInspector, ThemeLiveEditor, ThemePreview, ThemeProvider, ThemeRegistry, ThemeValidator, applyCSSVariables, applyTheme, createDesignTokensFromTheme, createTheme, createThemeObject, createTokens, cssVarsToStyle, deepMerge, defaultTokens, designTokensToCSSVars, designTokensToTheme, extendTheme, extractComponentName, generateCSSVariableName, generateCSSVariables$1 as generateCSSVariables, generateCSSVariablesForSelector, generateComponentCSSVars, getCSSVariable, getDesignTokensFromTheme, getThemeApplicator, injectCSS$1 as injectCSS, injectTheme, isCSSInjected, isDesignTokens, isThemeObject, isValidCSSVariableName, loadThemeFromConfig, loadThemeFromConfigSync, mapSCSSTokensToCSSVars, mergeCSSVars, mergeTheme, removeCSS, removeCSSVariables, removeTheme, saveCSSFile, saveCSSFileSync, saveTheme, themeToDesignTokens, useHistory, useTheme };
5384
+ export { RTLManager, ThemeApplicator, ThemeComparator, ThemeContext, ThemeErrorBoundary, ThemeInspector, ThemeLiveEditor, ThemePreview, ThemeProvider, ThemeValidator, applyCSSVariables, applyComponentTheme, applyTheme, camelToKebab, clearThemes, createDesignTokensFromTheme, createTheme, createThemeObject, createThemeRegistry, createTokens, cssVarsToStyle, deepMerge, defaultTokens, designTokensToCSSVars, designTokensToTheme, extendTheme, extractComponentName, generateCSSVariableName, generateCSSVariables$1 as generateCSSVariables, generateCSSVariablesForSelector, generateClassName, generateComponentCSSVars, getAllThemes, getCSSVariable, getComponentThemeValue, getDesignTokensFromTheme, getTheme, getThemeApplicator, getThemeCount, getThemeIds, hasTheme, injectCSS$1 as injectCSS, injectTheme, isCSSInjected, isDesignTokens, isThemeObject, isValidCSSVariableName, loadThemeFromConfig, loadThemeFromConfigSync, mapSCSSTokensToCSSVars, mergeCSSVars, mergeTheme, normalizeThemeTokens, registerTheme, removeCSS, removeCSSVariables, removeTheme, saveTheme, themePropertyToCSSVar, themeToDesignTokens, unregisterTheme, useComponentTheme, useHistory, useTheme, useThemeTokens };
6084
5385
  //# sourceMappingURL=theme.js.map