@shohojdhara/atomix 0.5.2 → 0.5.5

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 (61) hide show
  1. package/atomix.config.ts +33 -33
  2. package/dist/atomix.css +3213 -159
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +5 -5
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/config.d.ts +187 -112
  7. package/dist/config.js +2 -47
  8. package/dist/config.js.map +1 -1
  9. package/dist/index.d.ts +1958 -900
  10. package/dist/index.esm.js +2279 -382
  11. package/dist/index.esm.js.map +1 -1
  12. package/dist/index.js +2332 -413
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.min.js +1 -1
  15. package/dist/index.min.js.map +1 -1
  16. package/dist/theme.d.ts +1390 -276
  17. package/dist/theme.js +2125 -621
  18. package/dist/theme.js.map +1 -1
  19. package/package.json +1 -1
  20. package/scripts/cli/internal/config-loader.js +30 -20
  21. package/src/lib/config/index.ts +38 -362
  22. package/src/lib/config/loader.ts +422 -0
  23. package/src/lib/config/public-api.ts +43 -0
  24. package/src/lib/config/types.ts +389 -0
  25. package/src/lib/config/validator.ts +305 -0
  26. package/src/lib/theme/adapters/index.ts +1 -1
  27. package/src/lib/theme/adapters/themeAdapter.ts +358 -229
  28. package/src/lib/theme/components/ThemeToggle.tsx +276 -0
  29. package/src/lib/theme/config/configLoader.ts +351 -0
  30. package/src/lib/theme/config/loader.ts +221 -0
  31. package/src/lib/theme/core/createTheme.ts +126 -50
  32. package/src/lib/theme/core/createThemeObject.ts +7 -4
  33. package/src/lib/theme/hooks/useThemeSwitcher.ts +164 -0
  34. package/src/lib/theme/index.ts +322 -38
  35. package/src/lib/theme/runtime/ThemeProvider.tsx +44 -10
  36. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +44 -393
  37. package/src/lib/theme/runtime/useTheme.ts +1 -0
  38. package/src/lib/theme/tokens/tokens.ts +101 -1
  39. package/src/lib/theme/types.ts +91 -0
  40. package/src/lib/theme/utils/performanceMonitor.ts +315 -0
  41. package/src/lib/theme/utils/responsive.ts +280 -0
  42. package/src/lib/theme/utils/themeUtils.ts +531 -117
  43. package/src/styles/01-settings/_settings.background.scss +34 -5
  44. package/src/styles/02-tools/_tools.background.scss +330 -52
  45. package/src/styles/05-objects/_objects.masonry-grid.scss +3 -3
  46. package/src/styles/06-components/_components.accordion.scss +2 -2
  47. package/src/styles/06-components/_components.badge.scss +1 -1
  48. package/src/styles/06-components/_components.button.scss +2 -2
  49. package/src/styles/06-components/_components.callout.scss +2 -2
  50. package/src/styles/06-components/_components.card.scss +1 -1
  51. package/src/styles/06-components/_components.dropdown.scss +1 -1
  52. package/src/styles/06-components/_components.dynamic-background.scss +69 -0
  53. package/src/styles/06-components/_components.edge-panel.scss +2 -2
  54. package/src/styles/06-components/_components.input.scss +3 -3
  55. package/src/styles/06-components/_components.messages.scss +6 -6
  56. package/src/styles/06-components/_components.modal.scss +1 -1
  57. package/src/styles/06-components/_components.navbar.scss +1 -1
  58. package/src/styles/06-components/_components.popover.scss +1 -1
  59. package/src/styles/06-components/_components.toggle.scss +1 -1
  60. package/src/styles/06-components/_components.tooltip.scss +3 -3
  61. package/src/styles/06-components/_index.scss +1 -0
package/dist/theme.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import { createContext, useRef, useEffect, useCallback, useMemo, useState, useContext, Component } from "react";
3
+ import React, { createContext, useRef, useEffect, useCallback, useMemo, useState, useContext, Component } from "react";
4
4
 
5
5
  /**
6
6
  * Design Tokens
@@ -296,7 +296,51 @@ import { createContext, useRef, useEffect, useCallback, useMemo, useState, useCo
296
296
  "breakpoint-md": "768px",
297
297
  "breakpoint-lg": "992px",
298
298
  "breakpoint-xl": "1200px",
299
- "breakpoint-xxl": "1440px"
299
+ "breakpoint-xxl": "1440px",
300
+ // Advanced Features - Interactive Effects (Phase 2)
301
+ "interactive-vortex-enabled": "false",
302
+ "interactive-vortex-strength": "0.5",
303
+ "interactive-vortex-radius": "100",
304
+ "interactive-vortex-decay": "0.8",
305
+ "interactive-chromatic-enabled": "false",
306
+ "interactive-chromatic-mode": "lateral",
307
+ "interactive-chromatic-red-shift": "0.02",
308
+ "interactive-chromatic-green-shift": "0",
309
+ "interactive-chromatic-blue-shift": "-0.02",
310
+ "interactive-chromatic-edge-only": "false",
311
+ "interactive-chromatic-edge-threshold": "0.5",
312
+ "interactive-mouse-sensitivity": "1.0",
313
+ "interactive-mouse-trail-effect": "false",
314
+ "interactive-animation-speed-base": "1.0",
315
+ "interactive-animation-speed-multiplier": "1.0",
316
+ // Advanced Features - Optimization (Phase 3)
317
+ "optimization-breakpoint-mobile": "0px",
318
+ "optimization-breakpoint-tablet": "768px",
319
+ "optimization-breakpoint-desktop": "1024px",
320
+ "optimization-breakpoint-wide": "1440px",
321
+ "optimization-device-scaling-mobile": "0.5",
322
+ "optimization-device-scaling-tablet": "0.75",
323
+ "optimization-device-scaling-desktop": "1.0",
324
+ "optimization-performance-fps-target": "60",
325
+ "optimization-auto-scaling-enabled": "false",
326
+ "optimization-auto-scaling-low-end": "0.5",
327
+ "optimization-auto-scaling-mid-range": "0.75",
328
+ "optimization-auto-scaling-high-end": "1.0",
329
+ // Advanced Features - Visual Polish (Phase 4)
330
+ "visual-polish-border-iridescent-glow": "false",
331
+ "visual-polish-border-shimmer-effect": "false",
332
+ "visual-polish-border-beveled-edges": "false",
333
+ "visual-polish-border-pulsing-glow": "false",
334
+ "visual-polish-content-aware-blur-enabled": "false",
335
+ "visual-polish-content-aware-depth-detection": "false",
336
+ "visual-polish-content-aware-edge-preservation": "false",
337
+ "visual-polish-content-aware-variable-radius": "false",
338
+ "visual-polish-holographic-enabled": "false",
339
+ "visual-polish-holographic-rainbow-diffraction": "false",
340
+ "visual-polish-holographic-scanline-animation": "false",
341
+ "visual-polish-holographic-grid-overlay": "false",
342
+ "visual-polish-holographic-data-stream": "false",
343
+ "visual-polish-holographic-pulse-rings": "false"
300
344
  };
301
345
 
302
346
  /**
@@ -376,412 +420,278 @@ import { createContext, useRef, useEffect, useCallback, useMemo, useState, useCo
376
420
  }
377
421
 
378
422
  /**
379
- * Theme System Error Handling
423
+ * Theme Adapter
380
424
  *
381
- * Centralized error handling for the Atomix theme system.
382
- * Provides custom error classes and logging utilities.
383
- */
384
- /**
385
- * Theme error codes
386
- */ var ThemeErrorCode, LogLevel;
387
-
388
- !function(ThemeErrorCode) {
389
- /** Theme not found in registry */
390
- ThemeErrorCode.THEME_NOT_FOUND = "THEME_NOT_FOUND",
391
- /** Theme failed to load */
392
- ThemeErrorCode.THEME_LOAD_FAILED = "THEME_LOAD_FAILED",
393
- /** Theme validation failed */
394
- ThemeErrorCode.THEME_VALIDATION_FAILED = "THEME_VALIDATION_FAILED",
395
- /** Configuration loading failed */
396
- ThemeErrorCode.CONFIG_LOAD_FAILED = "CONFIG_LOAD_FAILED",
397
- /** Configuration validation failed */
398
- ThemeErrorCode.CONFIG_VALIDATION_FAILED = "CONFIG_VALIDATION_FAILED",
399
- /** Circular dependency detected */
400
- ThemeErrorCode.CIRCULAR_DEPENDENCY = "CIRCULAR_DEPENDENCY",
401
- /** Missing dependency */
402
- ThemeErrorCode.MISSING_DEPENDENCY = "MISSING_DEPENDENCY",
403
- /** Storage operation failed */
404
- ThemeErrorCode.STORAGE_ERROR = "STORAGE_ERROR",
405
- /** Invalid theme name */
406
- ThemeErrorCode.INVALID_THEME_NAME = "INVALID_THEME_NAME",
407
- /** CSS injection failed */
408
- ThemeErrorCode.CSS_INJECTION_FAILED = "CSS_INJECTION_FAILED",
409
- /** Invalid color format */
410
- ThemeErrorCode.INVALID_COLOR_FORMAT = "INVALID_COLOR_FORMAT",
411
- /** Missing required token */
412
- ThemeErrorCode.MISSING_REQUIRED_TOKEN = "MISSING_REQUIRED_TOKEN",
413
- /** Accessibility contrast violation */
414
- ThemeErrorCode.CONTRAST_VIOLATION = "CONTRAST_VIOLATION",
415
- /** Invalid token type */
416
- ThemeErrorCode.INVALID_TOKEN_TYPE = "INVALID_TOKEN_TYPE",
417
- /** Unknown error */
418
- ThemeErrorCode.UNKNOWN_ERROR = "UNKNOWN_ERROR";
419
- }(ThemeErrorCode || (ThemeErrorCode = {}));
420
-
421
- /**
422
- * Custom error class for theme-related errors
425
+ * Converts between Theme objects and DesignTokens
423
426
  */
424
- class ThemeError extends Error {
425
- constructor(message, code = ThemeErrorCode.UNKNOWN_ERROR, context) {
426
- super(message), this.name = "ThemeError", this.code = code, this.context = context,
427
- this.timestamp = Date.now(),
428
- // Maintains proper stack trace for where our error was thrown (only available on V8)
429
- Error.captureStackTrace && Error.captureStackTrace(this, ThemeError);
430
- }
431
- /**
432
- * Convert error to JSON for logging
433
- */ toJSON() {
434
- return {
435
- name: this.name,
436
- message: this.message,
437
- code: this.code,
438
- context: this.context,
439
- timestamp: this.timestamp,
440
- stack: this.stack
441
- };
442
- }
443
- }
444
-
445
- /**
446
- * Log level
447
- */ !function(LogLevel) {
448
- LogLevel[LogLevel.ERROR = 0] = "ERROR", LogLevel[LogLevel.WARN = 1] = "WARN", LogLevel[LogLevel.INFO = 2] = "INFO",
449
- LogLevel[LogLevel.DEBUG = 3] = "DEBUG";
450
- }(LogLevel || (LogLevel = {}));
451
-
452
427
  /**
453
- * Theme Logger
454
- *
455
- * Centralized logging for the theme system.
456
- * Replaces console statements with structured logging.
457
- */
458
- class ThemeLogger {
459
- constructor(config = {}) {
460
- this.config = {
461
- level: config.level ?? ("undefined" != typeof process && "production" === process.env?.NODE_ENV ? LogLevel.WARN : LogLevel.INFO),
462
- enableConsole: config.enableConsole ?? !0,
463
- onError: config.onError,
464
- onWarn: config.onWarn,
465
- onInfo: config.onInfo,
466
- onDebug: config.onDebug
467
- };
468
- }
469
- /**
470
- * Log an error
471
- */ error(message, error, context) {
472
- if (this.config.level < LogLevel.ERROR) return;
473
- const errorObj = error instanceof Error ? error : new Error(message), themeError = error instanceof ThemeError ? error : new ThemeError(message, ThemeErrorCode.UNKNOWN_ERROR, context);
474
- this.config.enableConsole && console.error(`[ThemeError] ${message}`, {
475
- error: errorObj,
476
- context: {
477
- ...context,
478
- ...themeError.context
479
- },
480
- code: themeError.code
481
- }), this.config.onError?.(themeError, context);
482
- }
483
- /**
484
- * Log a warning
485
- */ warn(message, context) {
486
- this.config.level < LogLevel.WARN || (this.config.enableConsole && console.warn(`[ThemeWarning] ${message}`, context || {}),
487
- this.config.onWarn?.(message, context));
428
+ * Convert a Theme object to DesignTokens
429
+ */ function themeToDesignTokens(theme) {
430
+ const tokens = {};
431
+ // Convert colors
432
+ if (theme.palette) {
433
+ // Primary color
434
+ if (theme.palette.primary) {
435
+ const primaryMain = theme.palette.primary.main;
436
+ tokens.primary = primaryMain;
437
+ const rgb = hexToRgb$2(primaryMain);
438
+ rgb && (tokens["primary-rgb"] = rgb);
439
+ }
440
+ // Secondary color
441
+ if (theme.palette.secondary) {
442
+ const secondaryMain = theme.palette.secondary.main;
443
+ tokens.secondary = secondaryMain;
444
+ const rgb = hexToRgb$2(secondaryMain);
445
+ rgb && (tokens["secondary-rgb"] = rgb);
446
+ }
447
+ // Other colors
448
+ const colorKeys = [ "error", "warning", "info", "success" ];
449
+ for (const key of colorKeys) if (theme.palette[key]) {
450
+ const colorMain = theme.palette[key].main;
451
+ tokens[key] = colorMain;
452
+ const rgb = hexToRgb$2(colorMain);
453
+ rgb && (tokens[`${key}-rgb`] = rgb);
454
+ }
455
+ // Background colors
456
+ theme.palette.background && (tokens["body-bg"] = theme.palette.background.default),
457
+ // Text colors
458
+ theme.palette.text && (tokens["body-color"] = theme.palette.text.primary);
488
459
  }
489
- /**
490
- * Log an info message
491
- */ info(message, context) {
492
- this.config.level < LogLevel.INFO || (this.config.enableConsole && console.info(`[ThemeInfo] ${message}`, context || {}),
493
- this.config.onInfo?.(message, context));
460
+ // Convert typography
461
+ // Convert border radius
462
+ if (theme.typography && (tokens["body-font-family"] = theme.typography.fontFamily,
463
+ tokens["body-font-size"] = `${theme.typography.fontSize}px`, tokens["font-weight-normal"] = `${theme.typography.fontWeightRegular}`,
464
+ tokens["font-weight-bold"] = `${theme.typography.fontWeightBold}`),
465
+ // Convert spacing
466
+ "function" == typeof theme.spacing && (
467
+ // If spacing is a function, call it with some values to get results
468
+ tokens["spacing-1"] = theme.spacing(1), tokens["spacing-2"] = theme.spacing(2),
469
+ tokens["spacing-4"] = theme.spacing(4)),
470
+ // Convert breakpoints
471
+ theme.breakpoints?.values && (tokens["breakpoint-xs"] = `${theme.breakpoints.values.xs}px`,
472
+ tokens["breakpoint-sm"] = `${theme.breakpoints.values.sm}px`, tokens["breakpoint-md"] = `${theme.breakpoints.values.md}px`,
473
+ tokens["breakpoint-lg"] = `${theme.breakpoints.values.lg}px`, tokens["breakpoint-xl"] = `${theme.breakpoints.values.xl}px`),
474
+ // Convert shadows
475
+ theme.shadows && (tokens["box-shadow"] = theme.shadows[2], // Use a moderate shadow
476
+ tokens["box-shadow-sm"] = theme.shadows[1], tokens["box-shadow-lg"] = theme.shadows[3]),
477
+ // Convert transitions
478
+ theme.transitions && (tokens["transition-duration-base"] = `${theme.transitions.duration.standard}ms`),
479
+ // Convert z-index
480
+ theme.zIndex && (tokens["z-modal"] = `${theme.zIndex.modal}`, tokens["z-popover"] = `${theme.zIndex.popover}`,
481
+ tokens["z-tooltip"] = `${theme.zIndex.tooltip}`), theme.borderRadius) {
482
+ const baseRadius = theme.borderRadius.base;
483
+ tokens["border-radius"] = "number" == typeof baseRadius ? `${baseRadius}px` : baseRadius;
494
484
  }
495
- /**
496
- * Log a debug message
497
- */ debug(message, context) {
498
- this.config.level < LogLevel.DEBUG || (this.config.enableConsole, this.config.onDebug?.(message, context));
485
+ // Add advanced feature tokens if available in theme
486
+ if (theme.custom) {
487
+ // Interactive Effects (Phase 2)
488
+ if (theme.custom.interactiveEffects) {
489
+ const ie = theme.custom.interactiveEffects;
490
+ // Vortex effects
491
+ ie.vortex && (tokens["interactive-vortex-enabled"] = String(ie.vortex.enabled ?? !1),
492
+ tokens["interactive-vortex-strength"] = String(ie.vortex.strength ?? .5), tokens["interactive-vortex-radius"] = String(ie.vortex.radius ?? 100),
493
+ tokens["interactive-vortex-decay"] = String(ie.vortex.decay ?? .8)),
494
+ // Chromatic aberration
495
+ ie.chromaticAberration && (tokens["interactive-chromatic-enabled"] = String(ie.chromaticAberration.enabled ?? !1),
496
+ tokens["interactive-chromatic-mode"] = ie.chromaticAberration.mode ?? "lateral",
497
+ tokens["interactive-chromatic-red-shift"] = String(ie.chromaticAberration.redShift ?? .02),
498
+ tokens["interactive-chromatic-green-shift"] = String(ie.chromaticAberration.greenShift ?? 0),
499
+ tokens["interactive-chromatic-blue-shift"] = String(ie.chromaticAberration.blueShift ?? -.02),
500
+ tokens["interactive-chromatic-edge-only"] = String(ie.chromaticAberration.edgeOnly ?? !1),
501
+ tokens["interactive-chromatic-edge-threshold"] = String(ie.chromaticAberration.edgeThreshold ?? .5)),
502
+ // Mouse interaction
503
+ ie.mouseInteraction && (tokens["interactive-mouse-sensitivity"] = String(ie.mouseInteraction.sensitivity ?? 1),
504
+ tokens["interactive-mouse-trail-effect"] = String(ie.mouseInteraction.trailEffect ?? !1)),
505
+ // Animation speed
506
+ ie.animationSpeed && (tokens["interactive-animation-speed-base"] = String(ie.animationSpeed.base ?? 1),
507
+ tokens["interactive-animation-speed-multiplier"] = String(ie.animationSpeed.timeMultiplier ?? 1));
508
+ }
509
+ // Optimization (Phase 3)
510
+ if (theme.custom.optimization) {
511
+ const opt = theme.custom.optimization;
512
+ // Responsive breakpoints
513
+ opt.responsive && (opt.responsive.breakpoints && (tokens["optimization-breakpoint-mobile"] = opt.responsive.breakpoints.mobile ?? "0px",
514
+ tokens["optimization-breakpoint-tablet"] = opt.responsive.breakpoints.tablet ?? "768px",
515
+ tokens["optimization-breakpoint-desktop"] = opt.responsive.breakpoints.desktop ?? "1024px",
516
+ tokens["optimization-breakpoint-wide"] = opt.responsive.breakpoints.wide ?? "1440px"),
517
+ opt.responsive.deviceScaling && (tokens["optimization-device-scaling-mobile"] = String(opt.responsive.deviceScaling.mobile ?? .5),
518
+ tokens["optimization-device-scaling-tablet"] = String(opt.responsive.deviceScaling.tablet ?? .75),
519
+ tokens["optimization-device-scaling-desktop"] = String(opt.responsive.deviceScaling.desktop ?? 1))),
520
+ // Performance settings
521
+ opt.performance && (tokens["optimization-performance-fps-target"] = String(opt.performance.fpsTarget ?? 60),
522
+ tokens["optimization-auto-scaling-enabled"] = String(opt.performance.autoScaling ?? !1)),
523
+ // Auto-scaling settings
524
+ opt.autoScaling && (tokens["optimization-auto-scaling-enabled"] = String(opt.autoScaling.enabled ?? !1),
525
+ tokens["optimization-auto-scaling-low-end"] = String(opt.autoScaling.qualityThresholds?.lowEnd ?? .5),
526
+ tokens["optimization-auto-scaling-mid-range"] = String(opt.autoScaling.qualityThresholds?.midRange ?? .75),
527
+ tokens["optimization-auto-scaling-high-end"] = String(opt.autoScaling.qualityThresholds?.highEnd ?? 1));
528
+ }
529
+ // Visual Polish (Phase 4)
530
+ if (theme.custom.visualPolish) {
531
+ const vp = theme.custom.visualPolish;
532
+ vp.borders && (tokens["visual-polish-border-iridescent-glow"] = String(vp.borders.iridescentGlow ?? !1),
533
+ tokens["visual-polish-border-shimmer-effect"] = String(vp.borders.shimmerEffect ?? !1),
534
+ tokens["visual-polish-border-beveled-edges"] = String(vp.borders.beveledEdges ?? !1),
535
+ tokens["visual-polish-border-pulsing-glow"] = String(vp.borders.pulsingGlow ?? !1)),
536
+ vp.contentAwareBlur && (tokens["visual-polish-content-aware-blur-enabled"] = String(vp.contentAwareBlur.enabled ?? !1),
537
+ tokens["visual-polish-content-aware-depth-detection"] = String(vp.contentAwareBlur.depthDetection ?? !1),
538
+ tokens["visual-polish-content-aware-edge-preservation"] = String(vp.contentAwareBlur.edgePreservation ?? !1),
539
+ tokens["visual-polish-content-aware-variable-radius"] = String(vp.contentAwareBlur.variableRadius ?? !1)),
540
+ vp.holographicEffects && (tokens["visual-polish-holographic-enabled"] = String(vp.holographicEffects.enabled ?? !1),
541
+ tokens["visual-polish-holographic-rainbow-diffraction"] = String(vp.holographicEffects.rainbowDiffraction ?? !1),
542
+ tokens["visual-polish-holographic-scanline-animation"] = String(vp.holographicEffects.scanlineAnimation ?? !1),
543
+ tokens["visual-polish-holographic-grid-overlay"] = String(vp.holographicEffects.gridOverlay ?? !1),
544
+ tokens["visual-polish-holographic-data-stream"] = String(vp.holographicEffects.dataStream ?? !1),
545
+ tokens["visual-polish-holographic-pulse-rings"] = String(vp.holographicEffects.pulseRings ?? !1));
546
+ }
499
547
  }
548
+ // Create full tokens object with defaults
549
+ return createTokens(tokens);
500
550
  }
501
551
 
502
552
  /**
503
- * Default logger instance
504
- */ let defaultLogger = null;
505
-
506
- /**
507
- * Get or create default logger
508
- */ function getLogger() {
509
- return defaultLogger || (defaultLogger = new ThemeLogger), defaultLogger;
510
- }
511
-
512
- /**
513
- * Core Theme Functions
514
- *
515
- * Simplified theme system using DesignTokens only.
516
- * Config-first approach: loads from atomix.config.ts when no input is provided.
517
- */
518
- /**
519
- * Create theme CSS from DesignTokens
553
+ * Converts an AtomixConfig to DesignTokens
520
554
  *
521
- * **Config-First Approach**: If no input is provided, loads from `atomix.config.ts`.
555
+ * This function maps the configuration from the user-facing format
556
+ * to the internal DesignTokens format used by the theme system.
522
557
  *
523
- * @param input - DesignTokens (partial) or undefined (loads from config)
524
- * @param options - CSS generation options (prefix is automatically read from config if not provided)
525
- * @returns CSS string with custom properties
526
- * @throws Error if config loading fails when no input is provided
558
+ * @param config - The configuration object to convert
559
+ * @returns DesignTokens object ready for theme generation
527
560
  *
528
561
  * @example
529
562
  * ```typescript
530
- * // Loads from atomix.config.ts
531
- * const css = createTheme();
532
- *
533
- * // Using DesignTokens
534
- * const css = createTheme({
535
- * 'primary': '#7c3aed',
536
- * 'spacing-4': '1rem',
537
- * });
563
+ * import { configToTokens } from '@shohojdhara/atomix/theme';
538
564
  *
539
- * // With custom options
540
- * const css = createTheme(undefined, { prefix: 'myapp', selector: ':root' });
565
+ * const config = {
566
+ * prefix: 'myapp',
567
+ * theme: { extend: { colors: { primary: { main: '#7AFFD7' } } } }
568
+ * };
569
+ * const tokens = configToTokens(config);
541
570
  * ```
542
- */ function createTheme(input, options) {
543
- // Validate options if provided
544
- if (options?.prefix) {
545
- const prefixPattern = /^[a-z][a-z0-9-]*$/;
546
- if (!prefixPattern.test(options.prefix)) throw new ThemeError(`Invalid CSS variable prefix: "${options.prefix}". Prefix must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens (e.g., "atomix", "my-app").`, ThemeErrorCode.THEME_VALIDATION_FAILED, {
547
- prefix: options.prefix,
548
- pattern: prefixPattern.toString()
549
- });
550
- }
551
- // Validate selector if provided
552
- if (options?.selector && ("string" != typeof options.selector || 0 === options.selector.trim().length)) throw new ThemeError(`Invalid CSS selector: "${options.selector}". Selector must be a non-empty string (e.g., ":root", ".my-theme").`, ThemeErrorCode.THEME_VALIDATION_FAILED, {
553
- selector: options.selector
554
- });
555
- // Determine tokens based on input
556
- let tokens;
557
- if (input) {
558
- // Validate input tokens structure
559
- if ("object" != typeof input || null === input || Array.isArray(input)) throw new ThemeError(`Invalid tokens input. Expected an object with DesignTokens, but received: ${typeof input}.`, ThemeErrorCode.THEME_VALIDATION_FAILED, {
560
- inputType: typeof input
561
- });
562
- // Use DesignTokens directly
563
- tokens = input;
564
- }
565
- // Merge with defaults and generate CSS
566
- else
567
- // Auto-loading config from file system is removed for browser compatibility.
568
- // If no input is provided, we return an empty theme (using defaults only) or user must provide tokens.
569
- // This allows createTheme to be isomorphic.
570
- // Warn in development if no input provided
571
- "production" !== process.env.NODE_ENV && "undefined" != typeof window && console.warn("Atomix: createTheme() called without tokens. Using default tokens only."),
572
- tokens = {};
573
- const allTokens = createTokens(tokens), prefix = options?.prefix ?? "atomix";
574
- // Get prefix from options or use default
575
- return generateCSSVariables$1(allTokens, {
576
- ...options,
577
- prefix: prefix
578
- });
579
- }
580
-
581
- /**
582
- * Theme Composition Utilities
583
- *
584
- * Simplified utilities for composing and merging DesignTokens.
585
- */
586
- // ============================================================================
587
- // Deep Merge Utility
588
- // ============================================================================
589
- /**
590
- * Check if value is an object
591
- */ function isObject$6(item) {
592
- return item && "object" == typeof item && !Array.isArray(item) && "function" != typeof item;
593
- }
594
-
595
- /**
596
- * Deep merge multiple objects
597
- * Later objects override earlier ones
598
- */ function deepMerge(...objects) {
599
- if (0 === objects.length) return {};
600
- if (1 === objects.length) return objects[0];
601
- const [target, ...sources] = objects, result = {
602
- ...target
571
+ */ function configToTokens(config) {
572
+ const prefix = config.prefix || "atomix", theme = config.theme || {};
573
+ // Start with default tokens
574
+ let tokens = {
575
+ ...defaultTokens
603
576
  };
604
- for (const source of sources) if (source) for (const key in source) {
605
- if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
606
- const targetValue = result[key], sourceValue = source[key];
607
- isObject$6(targetValue) && isObject$6(sourceValue) ?
608
- // Recursively merge objects
609
- result[key] = deepMerge(targetValue, sourceValue) :
610
- // Override with source value
611
- result[key] = sourceValue;
577
+ // Apply theme extensions
578
+ // Apply advanced features if available in config
579
+ if (theme.extend &&
580
+ // Apply extensions to tokens
581
+ Object.entries(theme.extend).forEach((([category, values]) => {
582
+ "object" == typeof values && null !== values && Object.entries(values).forEach((([key, value]) => {
583
+ // Map theme categories to token names
584
+ const tokenName = `${category}-${key}`;
585
+ "string" == typeof value || "number" == typeof value ? tokens[tokenName] = String(value) : "object" == typeof value && null !== value &&
586
+ // Handle nested objects like color scales
587
+ Object.entries(value).forEach((([nestedKey, nestedValue]) => {
588
+ "string" != typeof nestedValue && "number" != typeof nestedValue || (tokens[`${tokenName}-${nestedKey}`] = String(nestedValue));
589
+ }));
590
+ }));
591
+ })),
592
+ // Apply theme tokens if provided (completely replacing defaults)
593
+ theme.tokens && (tokens = {
594
+ ...tokens,
595
+ ...theme.tokens
596
+ }), config) {
597
+ // Interactive Effects (Phase 2)
598
+ if (config.interactiveEffects) {
599
+ const ie = config.interactiveEffects;
600
+ // Vortex effects
601
+ ie.vortex && (tokens["interactive-vortex-enabled"] = String(ie.vortex.enabled ?? !1),
602
+ tokens["interactive-vortex-strength"] = String(ie.vortex.strength ?? .5), tokens["interactive-vortex-radius"] = String(ie.vortex.radius ?? 100),
603
+ tokens["interactive-vortex-decay"] = String(ie.vortex.decay ?? .8), tokens["interactive-vortex-curl-noise"] = String(ie.vortex.curlNoise ?? !1),
604
+ tokens["interactive-vortex-velocity-tracking"] = String(ie.vortex.velocityTracking ?? !1)),
605
+ // Chromatic aberration
606
+ ie.chromaticAberration && (tokens["interactive-chromatic-enabled"] = String(ie.chromaticAberration.enabled ?? !1),
607
+ tokens["interactive-chromatic-mode"] = ie.chromaticAberration.mode ?? "lateral",
608
+ tokens["interactive-chromatic-red-shift"] = String(ie.chromaticAberration.redShift ?? .02),
609
+ tokens["interactive-chromatic-green-shift"] = String(ie.chromaticAberration.greenShift ?? 0),
610
+ tokens["interactive-chromatic-blue-shift"] = String(ie.chromaticAberration.blueShift ?? -.02),
611
+ tokens["interactive-chromatic-edge-only"] = String(ie.chromaticAberration.edgeOnly ?? !1),
612
+ tokens["interactive-chromatic-edge-threshold"] = String(ie.chromaticAberration.edgeThreshold ?? .5)),
613
+ // Mouse interaction
614
+ ie.mouseInteraction && (tokens["interactive-mouse-sensitivity"] = String(ie.mouseInteraction.sensitivity ?? 1),
615
+ tokens["interactive-mouse-trail-effect"] = String(ie.mouseInteraction.trailEffect ?? !1),
616
+ tokens["interactive-mouse-pressure-sensitivity"] = String(ie.mouseInteraction.pressureSensitivity ?? !1)),
617
+ // Animation speed
618
+ ie.animationSpeed && (tokens["interactive-animation-speed-base"] = String(ie.animationSpeed.base ?? 1),
619
+ tokens["interactive-animation-speed-multiplier"] = String(ie.animationSpeed.timeMultiplier ?? 1));
620
+ }
621
+ // Optimization (Phase 3)
622
+ if (config.optimization) {
623
+ const opt = config.optimization;
624
+ // Responsive breakpoints
625
+ opt.responsive && (opt.responsive.breakpoints && (tokens["optimization-breakpoint-mobile"] = opt.responsive.breakpoints.mobile ?? "0px",
626
+ tokens["optimization-breakpoint-tablet"] = opt.responsive.breakpoints.tablet ?? "768px",
627
+ tokens["optimization-breakpoint-desktop"] = opt.responsive.breakpoints.desktop ?? "1024px",
628
+ tokens["optimization-breakpoint-wide"] = opt.responsive.breakpoints.wide ?? "1440px"),
629
+ opt.responsive.deviceScaling && (tokens["optimization-device-scaling-mobile"] = String(opt.responsive.deviceScaling.mobile ?? .5),
630
+ tokens["optimization-device-scaling-tablet"] = String(opt.responsive.deviceScaling.tablet ?? .75),
631
+ tokens["optimization-device-scaling-desktop"] = String(opt.responsive.deviceScaling.desktop ?? 1))),
632
+ // Performance settings
633
+ opt.performance && (tokens["optimization-performance-fps-target"] = String(opt.performance.fpsTarget ?? 60),
634
+ tokens["optimization-auto-scaling-enabled"] = String(opt.performance.autoScaling ?? !1),
635
+ tokens["optimization-monitor-dashboard-enabled"] = String(opt.performance.monitorDashboard ?? !1)),
636
+ // Auto-scaling settings
637
+ opt.autoScaling && (tokens["optimization-auto-scaling-enabled"] = String(opt.autoScaling.enabled ?? !1),
638
+ tokens["optimization-auto-scaling-low-end"] = String(opt.autoScaling.qualityThresholds?.lowEnd ?? .5),
639
+ tokens["optimization-auto-scaling-mid-range"] = String(opt.autoScaling.qualityThresholds?.midRange ?? .75),
640
+ tokens["optimization-auto-scaling-high-end"] = String(opt.autoScaling.qualityThresholds?.highEnd ?? 1));
641
+ }
642
+ // Visual Polish (Phase 4)
643
+ if (config.visualPolish) {
644
+ const vp = config.visualPolish;
645
+ vp.borders && (tokens["visual-polish-border-iridescent-glow"] = String(vp.borders.iridescentGlow ?? !1),
646
+ tokens["visual-polish-border-shimmer-effect"] = String(vp.borders.shimmerEffect ?? !1),
647
+ tokens["visual-polish-border-beveled-edges"] = String(vp.borders.beveledEdges ?? !1),
648
+ tokens["visual-polish-border-pulsing-glow"] = String(vp.borders.pulsingGlow ?? !1)),
649
+ vp.contentAwareBlur && (tokens["visual-polish-content-aware-blur-enabled"] = String(vp.contentAwareBlur.enabled ?? !1),
650
+ tokens["visual-polish-content-aware-depth-detection"] = String(vp.contentAwareBlur.depthDetection ?? !1),
651
+ tokens["visual-polish-content-aware-edge-preservation"] = String(vp.contentAwareBlur.edgePreservation ?? !1),
652
+ tokens["visual-polish-content-aware-variable-radius"] = String(vp.contentAwareBlur.variableRadius ?? !1)),
653
+ vp.holographicEffects && (tokens["visual-polish-holographic-enabled"] = String(vp.holographicEffects.enabled ?? !1),
654
+ tokens["visual-polish-holographic-rainbow-diffraction"] = String(vp.holographicEffects.rainbowDiffraction ?? !1),
655
+ tokens["visual-polish-holographic-scanline-animation"] = String(vp.holographicEffects.scanlineAnimation ?? !1),
656
+ tokens["visual-polish-holographic-grid-overlay"] = String(vp.holographicEffects.gridOverlay ?? !1),
657
+ tokens["visual-polish-holographic-data-stream"] = String(vp.holographicEffects.dataStream ?? !1),
658
+ tokens["visual-polish-holographic-pulse-rings"] = String(vp.holographicEffects.pulseRings ?? !1));
659
+ }
612
660
  }
613
- return result;
661
+ // Apply prefix to all tokens
662
+ const prefixedTokens = {};
663
+ return Object.entries(tokens).forEach((([key, value]) => {
664
+ // If the token key already starts with the prefix, use as-is
665
+ // Otherwise, add the prefix
666
+ const prefixedKey = key.startsWith(prefix) ? key : `${prefix}-${key}`;
667
+ prefixedTokens[prefixedKey] = value;
668
+ })), prefixedTokens;
614
669
  }
615
670
 
616
671
  /**
617
- * Merge multiple DesignTokens objects into a single DesignTokens object
618
- *
619
- * @param tokens - DesignTokens objects to merge
620
- * @returns Merged DesignTokens object
621
- *
622
- * @example
623
- * ```typescript
624
- * const baseTokens = { 'primary': '#000', 'spacing-4': '1rem' };
625
- * const customTokens = { 'secondary': '#fff', 'spacing-4': '1.5rem' };
626
- * const merged = mergeTheme(baseTokens, customTokens);
627
- * // Returns: { 'primary': '#000', 'secondary': '#fff', 'spacing-4': '1.5rem' }
628
- * ```
629
- */ function mergeTheme(...tokens) {
630
- return deepMerge({}, ...tokens);
672
+ * Convert hex color to RGB
673
+ */ function hexToRgb$2(hex) {
674
+ hex = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, ((m, r, g, b) => r + r + g + g + b + b));
675
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
676
+ return result && result[1] && result[2] && result[3] ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` : "0, 0, 0";
631
677
  }
632
678
 
633
679
  /**
634
- * Extend DesignTokens with additional tokens
635
- *
636
- * @param baseTokens - Base DesignTokens to extend
637
- * @param extension - Additional DesignTokens to merge
638
- * @returns Extended DesignTokens object
680
+ * Converts DesignTokens to CSS variables
639
681
  *
640
- * @example
641
- * ```typescript
642
- * const base = { 'primary': '#000' };
643
- * const extended = extendTheme(base, { 'secondary': '#fff' });
644
- * // Returns: { 'primary': '#000', 'secondary': '#fff' }
645
- * ```
646
- */ function extendTheme(baseTokens, extension) {
647
- return mergeTheme(baseTokens, extension);
648
- }
649
-
650
- /**
651
- * Create a new theme registry
652
- */ function createThemeRegistry() {
653
- return {};
682
+ * @param tokens - The tokens to convert
683
+ * @returns A record of CSS variable names and values
684
+ */ function designTokensToCSSVars(tokens) {
685
+ const cssVars = {};
686
+ return Object.entries(tokens).forEach((([key, value]) => {
687
+ void 0 !== value && (cssVars[`--atomix-${key}`] = String(value));
688
+ })), cssVars;
654
689
  }
655
690
 
656
- /**
657
- * Register a theme
658
- * @param registry - Theme registry object
659
- * @param id - Theme identifier
660
- * @param metadata - Theme metadata
661
- */ function registerTheme(registry, id, metadata) {
662
- registry[id] = metadata;
663
- }
691
+ var commonjsGlobal = "undefined" != typeof globalThis ? globalThis : "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {};
664
692
 
665
- /**
666
- * Unregister a theme
667
- * @param registry - Theme registry object
668
- * @param id - Theme identifier
669
- */ function unregisterTheme(registry, id) {
670
- const exists = id in registry;
671
- return delete registry[id], exists;
672
- }
673
-
674
- /**
675
- * Check if a theme is registered
676
- * @param registry - Theme registry object
677
- * @param id - Theme identifier
678
- */ function hasTheme(registry, id) {
679
- return id in registry;
680
- }
681
-
682
- /**
683
- * Get theme metadata
684
- * @param registry - Theme registry object
685
- * @param id - Theme identifier
686
- */ function getTheme(registry, id) {
687
- return registry[id];
688
- }
689
-
690
- /**
691
- * Get all registered theme metadata
692
- * @param registry - Theme registry object
693
- */ function getAllThemes(registry) {
694
- return Object.values(registry);
695
- }
696
-
697
- /**
698
- * Get all registered theme IDs
699
- * @param registry - Theme registry object
700
- */ function getThemeIds(registry) {
701
- return Object.keys(registry);
702
- }
703
-
704
- /**
705
- * Clear all registered themes
706
- * @param registry - Theme registry object
707
- */ function clearThemes(registry) {
708
- Object.keys(registry).forEach((key => delete registry[key]));
709
- }
710
-
711
- /**
712
- * Get the number of registered themes
713
- * @param registry - Theme registry object
714
- */ function getThemeCount(registry) {
715
- return Object.keys(registry).length;
716
- }
717
-
718
- /**
719
- * CSS Injection Utilities
720
- *
721
- * Inject CSS into HTML head via <style> element.
722
- */
723
- /**
724
- * Check if running in browser environment
725
- */ function isBrowser$1() {
726
- return "undefined" != typeof document;
727
- }
728
-
729
- /**
730
- * Inject CSS into HTML head via <style> element
731
- *
732
- * Creates or updates a style element in the document head.
733
- * If an element with the same ID exists, it will be updated.
734
- *
735
- * @param css - CSS string to inject
736
- * @param id - Style element ID (default: 'atomix-theme')
737
- *
738
- * @example
739
- * ```typescript
740
- * const css = ':root { --atomix-color-primary: #7AFFD7; }';
741
- * injectCSS(css);
742
- *
743
- * // With custom ID
744
- * injectCSS(css, 'my-custom-theme');
745
- * ```
746
- */ function injectCSS$1(css, id = "atomix-theme") {
747
- if (!isBrowser$1()) return;
748
- let styleElement = document.getElementById(id);
749
- styleElement || (styleElement = document.createElement("style"), styleElement.id = id,
750
- styleElement.setAttribute("data-atomix-theme", "true"), document.head.appendChild(styleElement)),
751
- styleElement.textContent = css;
752
- }
753
-
754
- /**
755
- * Remove injected CSS from DOM
756
- *
757
- * Removes the style element with the given ID from the document head.
758
- *
759
- * @param id - Style element ID to remove (default: 'atomix-theme')
760
- *
761
- * @example
762
- * ```typescript
763
- * removeCSS(); // Removes default 'atomix-theme'
764
- * removeCSS('my-custom-theme'); // Removes custom ID
765
- * ```
766
- */ function removeCSS(id = "atomix-theme") {
767
- if (!isBrowser$1()) return;
768
- const styleElement = document.getElementById(id);
769
- styleElement && styleElement.remove();
770
- }
771
-
772
- /**
773
- * Check if CSS is already injected
774
- *
775
- * @param id - Style element ID to check (default: 'atomix-theme')
776
- * @returns True if style element exists
777
- */ function isCSSInjected(id = "atomix-theme") {
778
- return !!isBrowser$1() && null !== document.getElementById(id);
779
- }
780
-
781
- var commonjsGlobal = "undefined" != typeof globalThis ? globalThis : "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {};
782
-
783
- function getDefaultExportFromCjs(x) {
784
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x.default : x;
693
+ function getDefaultExportFromCjs(x) {
694
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x.default : x;
785
695
  }
786
696
 
787
697
  var fails$9 = function(exec) {
@@ -862,7 +772,7 @@ var match, version, createPropertyDescriptor$2 = function(bitmap, value) {
862
772
  return it;
863
773
  }, IndexedObject$1 = indexedObject, requireObjectCoercible$2 = requireObjectCoercible$3, toIndexedObject$2 = function(it) {
864
774
  return IndexedObject$1(requireObjectCoercible$2(it));
865
- }, isCallable$7 = isCallable$8, isObject$5 = function(it) {
775
+ }, isCallable$7 = isCallable$8, isObject$6 = function(it) {
866
776
  return "object" == typeof it ? null !== it : isCallable$7(it);
867
777
  }, path$3 = {}, path$2 = path$3, globalThis$b = globalThis_1, isCallable$6 = isCallable$8, aFunction = function(variable) {
868
778
  return isCallable$6(variable) ? variable : void 0;
@@ -901,7 +811,7 @@ var environmentV8Version = version, V8_VERSION = environmentV8Version, fails$5 =
901
811
  return "Object";
902
812
  }
903
813
  }(argument) + " is not a function");
904
- }, aCallable$2 = aCallable$3, isNullOrUndefined = isNullOrUndefined$2, call$2 = functionCall, isCallable$3 = isCallable$8, isObject$4 = isObject$5, $TypeError$5 = TypeError, sharedStore = {
814
+ }, aCallable$2 = aCallable$3, isNullOrUndefined = isNullOrUndefined$2, call$2 = functionCall, isCallable$3 = isCallable$8, isObject$5 = isObject$6, $TypeError$5 = TypeError, sharedStore = {
905
815
  exports: {}
906
816
  }, globalThis$7 = globalThis_1, defineProperty = Object.defineProperty, globalThis$6 = globalThis_1, store$1 = sharedStore.exports = globalThis$6["__core-js_shared__"] || function(key, value) {
907
817
  try {
@@ -933,25 +843,25 @@ var key, value, store = sharedStore.exports, requireObjectCoercible$1 = requireO
933
843
  }, wellKnownSymbol$5 = function(name) {
934
844
  return hasOwn$2(WellKnownSymbolsStore, name) || (WellKnownSymbolsStore[name] = NATIVE_SYMBOL && hasOwn$2(Symbol$1, name) ? Symbol$1[name] : createWellKnownSymbol("Symbol." + name)),
935
845
  WellKnownSymbolsStore[name];
936
- }, call$1 = functionCall, isObject$3 = isObject$5, isSymbol$1 = isSymbol$2, $TypeError$4 = TypeError, TO_PRIMITIVE = wellKnownSymbol$5("toPrimitive"), toPrimitive = function(input, pref) {
937
- if (!isObject$3(input) || isSymbol$1(input)) return input;
846
+ }, call$1 = functionCall, isObject$4 = isObject$6, isSymbol$1 = isSymbol$2, $TypeError$4 = TypeError, TO_PRIMITIVE = wellKnownSymbol$5("toPrimitive"), toPrimitive = function(input, pref) {
847
+ if (!isObject$4(input) || isSymbol$1(input)) return input;
938
848
  var result, func, exoticToPrim = (func = input[TO_PRIMITIVE], isNullOrUndefined(func) ? void 0 : aCallable$2(func));
939
849
  if (exoticToPrim) {
940
850
  if (void 0 === pref && (pref = "default"), result = call$1(exoticToPrim, input, pref),
941
- !isObject$3(result) || isSymbol$1(result)) return result;
851
+ !isObject$4(result) || isSymbol$1(result)) return result;
942
852
  throw new $TypeError$4("Can't convert object to primitive value");
943
853
  }
944
854
  return void 0 === pref && (pref = "number"), function(input, pref) {
945
855
  var fn, val;
946
- if ("string" === pref && isCallable$3(fn = input.toString) && !isObject$4(val = call$2(fn, input))) return val;
947
- if (isCallable$3(fn = input.valueOf) && !isObject$4(val = call$2(fn, input))) return val;
948
- if ("string" !== pref && isCallable$3(fn = input.toString) && !isObject$4(val = call$2(fn, input))) return val;
856
+ if ("string" === pref && isCallable$3(fn = input.toString) && !isObject$5(val = call$2(fn, input))) return val;
857
+ if (isCallable$3(fn = input.valueOf) && !isObject$5(val = call$2(fn, input))) return val;
858
+ if ("string" !== pref && isCallable$3(fn = input.toString) && !isObject$5(val = call$2(fn, input))) return val;
949
859
  throw new $TypeError$5("Can't convert object to primitive value");
950
860
  }(input, pref);
951
861
  }, isSymbol = isSymbol$2, toPropertyKey$2 = function(argument) {
952
862
  var key = toPrimitive(argument, "string");
953
863
  return isSymbol(key) ? key : key + "";
954
- }, isObject$2 = isObject$5, document$1 = globalThis_1.document, EXISTS = isObject$2(document$1) && isObject$2(document$1.createElement), ie8DomDefine = !descriptors && !fails$9((function() {
864
+ }, isObject$3 = isObject$6, document$1 = globalThis_1.document, EXISTS = isObject$3(document$1) && isObject$3(document$1.createElement), ie8DomDefine = !descriptors && !fails$9((function() {
955
865
  // eslint-disable-next-line es/no-object-defineproperty -- required for testing
956
866
  return 7 !== Object.defineProperty((it = "div", EXISTS ? document$1.createElement(it) : {}), "a", {
957
867
  get: function() {
@@ -981,8 +891,8 @@ var fails$3 = fails$9, isCallable$2 = isCallable$8, replacement = /#|\.prototype
981
891
  value: 42,
982
892
  writable: !1
983
893
  }).prototype;
984
- })), isObject$1 = isObject$5, $String$1 = String, $TypeError$3 = TypeError, DESCRIPTORS$1 = descriptors, IE8_DOM_DEFINE = ie8DomDefine, V8_PROTOTYPE_DEFINE_BUG = v8PrototypeDefineBug, anObject = function(argument) {
985
- if (isObject$1(argument)) return argument;
894
+ })), isObject$2 = isObject$6, $String$1 = String, $TypeError$3 = TypeError, DESCRIPTORS$1 = descriptors, IE8_DOM_DEFINE = ie8DomDefine, V8_PROTOTYPE_DEFINE_BUG = v8PrototypeDefineBug, anObject = function(argument) {
895
+ if (isObject$2(argument)) return argument;
986
896
  throw new $TypeError$3($String$1(argument) + " is not an object");
987
897
  }, toPropertyKey = toPropertyKey$2, $TypeError$2 = TypeError, $defineProperty = Object.defineProperty, $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
988
898
 
@@ -1099,7 +1009,7 @@ var globalThis$2 = globalThis_1, path = path$3, getBuiltInPrototypeMethod$3 = fu
1099
1009
  if (pureMethod) return pureMethod;
1100
1010
  var NativeConstructor = globalThis$2[CONSTRUCTOR], NativePrototype = NativeConstructor && NativeConstructor.prototype;
1101
1011
  return NativePrototype && NativePrototype[METHOD];
1102
- }, includes$4 = getBuiltInPrototypeMethod$3("Array", "includes"), isObject = isObject$5, classof$3 = classofRaw$2, MATCH$1 = wellKnownSymbol$5("match"), $TypeError$1 = TypeError, test = {};
1012
+ }, includes$4 = getBuiltInPrototypeMethod$3("Array", "includes"), isObject$1 = isObject$6, classof$3 = classofRaw$2, MATCH$1 = wellKnownSymbol$5("match"), $TypeError$1 = TypeError, test = {};
1103
1013
 
1104
1014
  test[wellKnownSymbol$5("toStringTag")] = "z";
1105
1015
 
@@ -1115,7 +1025,7 @@ var TO_STRING_TAG_SUPPORT = "[object z]" === String(test), isCallable = isCallab
1115
1025
  }, $String = String, MATCH = wellKnownSymbol$5("match"), $$1 = _export, notARegExp = function(it) {
1116
1026
  if (function(it) {
1117
1027
  var isRegExp;
1118
- return isObject(it) && (void 0 !== (isRegExp = it[MATCH$1]) ? !!isRegExp : "RegExp" === classof$3(it));
1028
+ return isObject$1(it) && (void 0 !== (isRegExp = it[MATCH$1]) ? !!isRegExp : "RegExp" === classof$3(it));
1119
1029
  }(it)) throw new $TypeError$1("The method doesn't accept regular expressions");
1120
1030
  return it;
1121
1031
  }, requireObjectCoercible = requireObjectCoercible$3, toString = function(argument) {
@@ -1153,18 +1063,595 @@ const _includesInstanceProperty = getDefaultExportFromCjs((function(it) {
1153
1063
  }));
1154
1064
 
1155
1065
  /**
1156
- * Theme System Constants
1157
- *
1158
- * Centralized constants for the theme system to avoid magic numbers and strings.
1159
- */
1160
- /**
1161
- * Default storage key for theme persistence
1162
- */ "undefined" != typeof process && process.env;
1066
+ * Load theme from config file (synchronous, Node.js only)
1067
+ * @param configPath - Path to config file (default: atomix.config.ts)
1068
+ * @returns DesignTokens from theme configuration
1069
+ * @throws Error if config loading is not available in browser environment
1070
+ */
1071
+ /**
1072
+ * Helper function to load config from a specific path
1073
+ */
1074
+ function loadConfigAtPath(path, required, defaultConfig) {
1075
+ try {
1076
+ // Use dynamic import for ESM compatibility
1077
+ const configModule = require(path), config = configModule.default || configModule;
1078
+ // Validate it's an AtomixConfig
1079
+ if (config && "object" == typeof config) return config;
1080
+ throw new Error("Invalid config format");
1081
+ } catch (error) {
1082
+ if (required) throw new Error(`Failed to load config from ${path}: ${error.message}`);
1083
+ // Return default config if not required
1084
+ return defaultConfig;
1085
+ }
1086
+ }
1087
+
1088
+ /**
1089
+ * Core Theme Functions
1090
+ *
1091
+ * Unified theme system that handles both DesignTokens and Theme objects.
1092
+ * Config-first approach: loads from atomix.config.ts when no input is provided.
1093
+ * Config-first approach: loads advanced features from config when available.
1094
+ */
1095
+ /**
1096
+ * Create theme CSS from tokens or Theme object
1097
+ *
1098
+ * **Config-First Approach**: If no input is provided, loads from `atomix.config.ts`.
1099
+ * Config file is required for automatic loading.
1100
+ *
1101
+ * @param input - DesignTokens (partial), Theme object, or undefined (loads from config)
1102
+ * @param options - CSS generation options (prefix is automatically read from config if not provided)
1103
+ * @returns CSS string with custom properties
1104
+ * @throws Error if config loading fails when no input is provided
1105
+ *
1106
+ * @example
1107
+ * ```typescript
1108
+ * // Loads from atomix.config.ts (config file required)
1109
+ * const css = createTheme();
1110
+ *
1111
+ * // Using DesignTokens
1112
+ * const css = createTheme({
1113
+ * 'primary': '#7c3aed',
1114
+ * 'spacing-4': '1rem',
1115
+ * });
1116
+ *
1117
+ * // Using Theme object
1118
+ * const theme = createThemeObject({ palette: { primary: { main: '#7c3aed' } } });
1119
+ * const css = createTheme(theme);
1120
+ *
1121
+ * // With custom options
1122
+ * const css = createTheme(undefined, { prefix: 'myapp', selector: ':root' });
1123
+ * ```
1124
+ */ function createTheme(input, options) {
1125
+ let tokens, configPrefix;
1126
+ // If no input provided, load from config (required)
1127
+ if (input)
1128
+ // Convert Theme object to DesignTokens
1129
+ tokens = !0 === input.__isJSTheme || input.palette && input.typography ? themeToDesignTokens(input) : input; else {
1130
+ const configTokens = function() {
1131
+ // Check if we're in a browser environment
1132
+ if ("undefined" != typeof window) throw new Error("loadThemeFromConfigSync: Not available in browser environment. Config loading requires Node.js/SSR environment.");
1133
+ // Use dynamic import to load the config loader
1134
+ // This allows bundlers to handle external dependencies properly
1135
+ let loadAtomixConfig;
1136
+ try {
1137
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1138
+ const {loadAtomixConfig: loader} = require("../../config/loader");
1139
+ loadAtomixConfig = loader;
1140
+ } catch (error) {
1141
+ throw new Error("Config loader module not available");
1142
+ }
1143
+ const config = loadAtomixConfig({
1144
+ configPath: undefined,
1145
+ required: !0
1146
+ });
1147
+ if (!config?.theme) return createTokens({});
1148
+ if ((obj = config.theme) && "object" == typeof obj && (obj.palette || obj.typography || obj.spacing || obj.breakpoints || obj.colors)) return themeToDesignTokens(config.theme);
1149
+ // Handle the config.theme object which has extend/tokens/themes properties
1150
+ // Extract the actual tokens from the theme configuration
1151
+ // Helper type guard function
1152
+ var obj;
1153
+ /**
1154
+ * Load Atomix configuration from project root
1155
+ *
1156
+ * Attempts to load atomix.config.ts, atomix.config.js, or atomix.config.json from the current working directory.
1157
+ * Falls back to default config if file doesn't exist.
1158
+ *
1159
+ * @param options - Loader options
1160
+ * @returns Loaded configuration or default
1161
+ *
1162
+ * @example
1163
+ * ```typescript
1164
+ * import { loadAtomixConfig } from '@shohojdhara/atomix/config';
1165
+ * import { createTheme } from '@shohojdhara/atomix/theme';
1166
+ *
1167
+ * const config = loadAtomixConfig();
1168
+ * const theme = createTheme(config.theme?.tokens || {});
1169
+ * ```
1170
+ */ const themeConfig = config.theme;
1171
+ let tokensToApply = {};
1172
+ return themeConfig.tokens ?
1173
+ // If tokens is provided, use it as the base
1174
+ tokensToApply = themeConfig.tokens : themeConfig.extend && (
1175
+ // If only extend is provided, use it as overrides
1176
+ tokensToApply = themeConfig.extend),
1177
+ // Apply advanced feature configurations as tokens
1178
+ config.interactiveEffects && (
1179
+ // Vortex effects
1180
+ config.interactiveEffects.vortex && (tokensToApply = {
1181
+ ...tokensToApply,
1182
+ "interactive-vortex-enabled": String(config.interactiveEffects.vortex.enabled ?? !1),
1183
+ "interactive-vortex-strength": String(config.interactiveEffects.vortex.strength ?? .5),
1184
+ "interactive-vortex-radius": String(config.interactiveEffects.vortex.radius ?? 100),
1185
+ "interactive-vortex-decay": String(config.interactiveEffects.vortex.decay ?? .8)
1186
+ }),
1187
+ // Chromatic aberration
1188
+ config.interactiveEffects.chromaticAberration && (tokensToApply = {
1189
+ ...tokensToApply,
1190
+ "interactive-chromatic-enabled": String(config.interactiveEffects.chromaticAberration.enabled ?? !1),
1191
+ "interactive-chromatic-mode": config.interactiveEffects.chromaticAberration.mode ?? "lateral",
1192
+ "interactive-chromatic-red-shift": String(config.interactiveEffects.chromaticAberration.redShift ?? .02),
1193
+ "interactive-chromatic-green-shift": String(config.interactiveEffects.chromaticAberration.greenShift ?? 0),
1194
+ "interactive-chromatic-blue-shift": String(config.interactiveEffects.chromaticAberration.blueShift ?? -.02),
1195
+ "interactive-chromatic-edge-only": String(config.interactiveEffects.chromaticAberration.edgeOnly ?? !1),
1196
+ "interactive-chromatic-edge-threshold": String(config.interactiveEffects.chromaticAberration.edgeThreshold ?? .5)
1197
+ }),
1198
+ // Mouse interaction
1199
+ config.interactiveEffects.mouseInteraction && (tokensToApply = {
1200
+ ...tokensToApply,
1201
+ "interactive-mouse-sensitivity": String(config.interactiveEffects.mouseInteraction.sensitivity ?? 1),
1202
+ "interactive-mouse-trail-effect": String(config.interactiveEffects.mouseInteraction.trailEffect ?? !1)
1203
+ }),
1204
+ // Animation speed
1205
+ config.interactiveEffects.animationSpeed && (tokensToApply = {
1206
+ ...tokensToApply,
1207
+ "interactive-animation-speed-base": String(config.interactiveEffects.animationSpeed.base ?? 1),
1208
+ "interactive-animation-speed-multiplier": String(config.interactiveEffects.animationSpeed.timeMultiplier ?? 1)
1209
+ })),
1210
+ // Apply optimization configurations as tokens
1211
+ config.optimization && (
1212
+ // Responsive breakpoints
1213
+ config.optimization.responsive && (config.optimization.responsive.breakpoints && (tokensToApply = {
1214
+ ...tokensToApply,
1215
+ "optimization-breakpoint-mobile": config.optimization.responsive.breakpoints.mobile ?? "0px",
1216
+ "optimization-breakpoint-tablet": config.optimization.responsive.breakpoints.tablet ?? "768px",
1217
+ "optimization-breakpoint-desktop": config.optimization.responsive.breakpoints.desktop ?? "1024px",
1218
+ "optimization-breakpoint-wide": config.optimization.responsive.breakpoints.wide ?? "1440px"
1219
+ }), config.optimization.responsive.deviceScaling && (tokensToApply = {
1220
+ ...tokensToApply,
1221
+ "optimization-device-scaling-mobile": String(config.optimization.responsive.deviceScaling.mobile ?? .5),
1222
+ "optimization-device-scaling-tablet": String(config.optimization.responsive.deviceScaling.tablet ?? .75),
1223
+ "optimization-device-scaling-desktop": String(config.optimization.responsive.deviceScaling.desktop ?? 1)
1224
+ })),
1225
+ // Performance settings
1226
+ config.optimization.performance && (tokensToApply = {
1227
+ ...tokensToApply,
1228
+ "optimization-performance-fps-target": String(config.optimization.performance.fpsTarget ?? 60),
1229
+ "optimization-auto-scaling-enabled": String(config.optimization.performance.autoScaling ?? !1)
1230
+ }),
1231
+ // Auto-scaling settings
1232
+ config.optimization.autoScaling && (tokensToApply = {
1233
+ ...tokensToApply,
1234
+ "optimization-auto-scaling-enabled": String(config.optimization.autoScaling.enabled ?? !1),
1235
+ "optimization-auto-scaling-low-end": String(config.optimization.autoScaling.qualityThresholds?.lowEnd ?? .5),
1236
+ "optimization-auto-scaling-mid-range": String(config.optimization.autoScaling.qualityThresholds?.midRange ?? .75),
1237
+ "optimization-auto-scaling-high-end": String(config.optimization.autoScaling.qualityThresholds?.highEnd ?? 1)
1238
+ })),
1239
+ // Apply visual polish configurations as tokens
1240
+ config.visualPolish && (config.visualPolish.borders && (tokensToApply = {
1241
+ ...tokensToApply,
1242
+ "visual-polish-border-iridescent-glow": String(config.visualPolish.borders.iridescentGlow ?? !1),
1243
+ "visual-polish-border-shimmer-effect": String(config.visualPolish.borders.shimmerEffect ?? !1),
1244
+ "visual-polish-border-beveled-edges": String(config.visualPolish.borders.beveledEdges ?? !1),
1245
+ "visual-polish-border-pulsing-glow": String(config.visualPolish.borders.pulsingGlow ?? !1)
1246
+ }), config.visualPolish.contentAwareBlur && (tokensToApply = {
1247
+ ...tokensToApply,
1248
+ "visual-polish-content-aware-blur-enabled": String(config.visualPolish.contentAwareBlur.enabled ?? !1),
1249
+ "visual-polish-content-aware-depth-detection": String(config.visualPolish.contentAwareBlur.depthDetection ?? !1),
1250
+ "visual-polish-content-aware-edge-preservation": String(config.visualPolish.contentAwareBlur.edgePreservation ?? !1),
1251
+ "visual-polish-content-aware-variable-radius": String(config.visualPolish.contentAwareBlur.variableRadius ?? !1)
1252
+ }), config.visualPolish.holographicEffects && (tokensToApply = {
1253
+ ...tokensToApply,
1254
+ "visual-polish-holographic-enabled": String(config.visualPolish.holographicEffects.enabled ?? !1),
1255
+ "visual-polish-holographic-rainbow-diffraction": String(config.visualPolish.holographicEffects.rainbowDiffraction ?? !1),
1256
+ "visual-polish-holographic-scanline-animation": String(config.visualPolish.holographicEffects.scanlineAnimation ?? !1),
1257
+ "visual-polish-holographic-grid-overlay": String(config.visualPolish.holographicEffects.gridOverlay ?? !1),
1258
+ "visual-polish-holographic-data-stream": String(config.visualPolish.holographicEffects.dataStream ?? !1),
1259
+ "visual-polish-holographic-pulse-rings": String(config.visualPolish.holographicEffects.pulseRings ?? !1)
1260
+ })), createTokens(tokensToApply);
1261
+ }();
1262
+ // Get prefix from config
1263
+ try {
1264
+ // Use the imported function directly instead of require to avoid bundling issues
1265
+ const config = function(options = {}) {
1266
+ const {configPath: configPath, required: required = !1} = options, defaultConfig = {
1267
+ prefix: "atomix",
1268
+ theme: {
1269
+ extend: {}
1270
+ }
1271
+ };
1272
+ // Default config
1273
+ // In browser environments, config loading is not supported
1274
+ if ("undefined" != typeof window) {
1275
+ if (required) throw new Error('Config loading requires Node.js file system access.\n\nSolutions:\n1. Provide tokens explicitly to createTheme():\n const css = createTheme({ "--brand-primary": "#6366f1" });\n\n2. Use SSR framework (Next.js, Remix, Astro)\n\n3. Load config on server and pass to client\n\nSee examples/config-examples/browser-only.config.ts');
1276
+ return defaultConfig;
1277
+ }
1278
+ // If a specific config path is provided, try to load it directly
1279
+ if (configPath) return loadConfigAtPath(configPath, required, defaultConfig);
1280
+ // Otherwise, try standard locations in order of preference
1281
+ const possiblePaths = [ "atomix.config.ts", "atomix.config.js", "atomix.config.json" ];
1282
+ for (const path of possiblePaths) {
1283
+ const config = loadConfigAtPath(path, !1, defaultConfig);
1284
+ // If we found a valid config, return it
1285
+ if (JSON.stringify(config) !== JSON.stringify(defaultConfig)) return config;
1286
+ }
1287
+ // If no config file was found or all contained only defaults, return default config
1288
+ if (required) throw new Error('No Atomix configuration file found in project root.\n\nExpected one of:\n - atomix.config.ts (recommended)\n - atomix.config.js\n - atomix.config.json\n\nQuick Fix:\n1. Create a config file in your project root:\n touch atomix.config.ts\n\n2. Add basic configuration:\n import { defineConfig } from "@shohojdhara/atomix/config";\n export default defineConfig({ prefix: "myapp" });\n\n3. Or copy an example:\n cp node_modules/@shohojdhara/atomix/examples/config-examples/standard.config.ts ./atomix.config.ts');
1289
+ return defaultConfig;
1290
+ }({
1291
+ configPath: "atomix.config.ts",
1292
+ required: !0
1293
+ });
1294
+ configPrefix = config?.prefix;
1295
+ } catch (error) {
1296
+ // Prefix loading failed, but tokens were loaded, so continue
1297
+ }
1298
+ tokens = configTokens;
1299
+ }
1300
+ // Merge with defaults and generate CSS
1301
+ const allTokens = createTokens(tokens), prefix = options?.prefix ?? configPrefix ?? "atomix";
1302
+ // Get prefix from options, config, or use default
1303
+ return generateCSSVariables$1(allTokens, {
1304
+ ...options,
1305
+ prefix: prefix
1306
+ });
1307
+ }
1308
+
1309
+ /**
1310
+ * Theme Composition Utilities
1311
+ *
1312
+ * Simplified utilities for composing and merging DesignTokens.
1313
+ */
1314
+ // ============================================================================
1315
+ // Deep Merge Utility
1316
+ // ============================================================================
1317
+ /**
1318
+ * Check if value is an object
1319
+ */ function isObject(item) {
1320
+ return item && "object" == typeof item && !Array.isArray(item) && "function" != typeof item;
1321
+ }
1322
+
1323
+ /**
1324
+ * Deep merge multiple objects
1325
+ * Later objects override earlier ones
1326
+ */ function deepMerge(...objects) {
1327
+ if (0 === objects.length) return {};
1328
+ if (1 === objects.length) return objects[0];
1329
+ const [target, ...sources] = objects, result = {
1330
+ ...target
1331
+ };
1332
+ for (const source of sources) if (source) for (const key in source) {
1333
+ if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
1334
+ const targetValue = result[key], sourceValue = source[key];
1335
+ isObject(targetValue) && isObject(sourceValue) ?
1336
+ // Recursively merge objects
1337
+ result[key] = deepMerge(targetValue, sourceValue) :
1338
+ // Override with source value
1339
+ result[key] = sourceValue;
1340
+ }
1341
+ return result;
1342
+ }
1343
+
1344
+ /**
1345
+ * Merge multiple DesignTokens objects into a single DesignTokens object
1346
+ *
1347
+ * @param tokens - DesignTokens objects to merge
1348
+ * @returns Merged DesignTokens object
1349
+ *
1350
+ * @example
1351
+ * ```typescript
1352
+ * const baseTokens = { 'primary': '#000', 'spacing-4': '1rem' };
1353
+ * const customTokens = { 'secondary': '#fff', 'spacing-4': '1.5rem' };
1354
+ * const merged = mergeTheme(baseTokens, customTokens);
1355
+ * // Returns: { 'primary': '#000', 'secondary': '#fff', 'spacing-4': '1.5rem' }
1356
+ * ```
1357
+ */ function mergeTheme(...tokens) {
1358
+ return deepMerge({}, ...tokens);
1359
+ }
1360
+
1361
+ /**
1362
+ * Extend DesignTokens with additional tokens
1363
+ *
1364
+ * @param baseTokens - Base DesignTokens to extend
1365
+ * @param extension - Additional DesignTokens to merge
1366
+ * @returns Extended DesignTokens object
1367
+ *
1368
+ * @example
1369
+ * ```typescript
1370
+ * const base = { 'primary': '#000' };
1371
+ * const extended = extendTheme(base, { 'secondary': '#fff' });
1372
+ * // Returns: { 'primary': '#000', 'secondary': '#fff' }
1373
+ * ```
1374
+ */ function extendTheme(baseTokens, extension) {
1375
+ return mergeTheme(baseTokens, extension);
1376
+ }
1377
+
1378
+ /**
1379
+ * Create a new theme registry
1380
+ */ function createThemeRegistry() {
1381
+ return {};
1382
+ }
1383
+
1384
+ /**
1385
+ * Register a theme
1386
+ * @param registry - Theme registry object
1387
+ * @param id - Theme identifier
1388
+ * @param metadata - Theme metadata
1389
+ */ function registerTheme(registry, id, metadata) {
1390
+ registry[id] = metadata;
1391
+ }
1392
+
1393
+ /**
1394
+ * Unregister a theme
1395
+ * @param registry - Theme registry object
1396
+ * @param id - Theme identifier
1397
+ */ function unregisterTheme(registry, id) {
1398
+ const exists = id in registry;
1399
+ return delete registry[id], exists;
1400
+ }
1401
+
1402
+ /**
1403
+ * Check if a theme is registered
1404
+ * @param registry - Theme registry object
1405
+ * @param id - Theme identifier
1406
+ */ function hasTheme(registry, id) {
1407
+ return id in registry;
1408
+ }
1409
+
1410
+ /**
1411
+ * Get theme metadata
1412
+ * @param registry - Theme registry object
1413
+ * @param id - Theme identifier
1414
+ */ function getTheme(registry, id) {
1415
+ return registry[id];
1416
+ }
1417
+
1418
+ /**
1419
+ * Get all registered theme metadata
1420
+ * @param registry - Theme registry object
1421
+ */ function getAllThemes(registry) {
1422
+ return Object.values(registry);
1423
+ }
1424
+
1425
+ /**
1426
+ * Get all registered theme IDs
1427
+ * @param registry - Theme registry object
1428
+ */ function getThemeIds(registry) {
1429
+ return Object.keys(registry);
1430
+ }
1431
+
1432
+ /**
1433
+ * Clear all registered themes
1434
+ * @param registry - Theme registry object
1435
+ */ function clearThemes(registry) {
1436
+ Object.keys(registry).forEach((key => delete registry[key]));
1437
+ }
1438
+
1439
+ /**
1440
+ * Get the number of registered themes
1441
+ * @param registry - Theme registry object
1442
+ */ function getThemeCount(registry) {
1443
+ return Object.keys(registry).length;
1444
+ }
1445
+
1446
+ /**
1447
+ * CSS Injection Utilities
1448
+ *
1449
+ * Inject CSS into HTML head via <style> element.
1450
+ */
1451
+ /**
1452
+ * Check if running in browser environment
1453
+ */ function isBrowser$1() {
1454
+ return "undefined" != typeof document;
1455
+ }
1456
+
1457
+ /**
1458
+ * Inject CSS into HTML head via <style> element
1459
+ *
1460
+ * Creates or updates a style element in the document head.
1461
+ * If an element with the same ID exists, it will be updated.
1462
+ *
1463
+ * @param css - CSS string to inject
1464
+ * @param id - Style element ID (default: 'atomix-theme')
1465
+ *
1466
+ * @example
1467
+ * ```typescript
1468
+ * const css = ':root { --atomix-color-primary: #7AFFD7; }';
1469
+ * injectCSS(css);
1470
+ *
1471
+ * // With custom ID
1472
+ * injectCSS(css, 'my-custom-theme');
1473
+ * ```
1474
+ */ function injectCSS$1(css, id = "atomix-theme") {
1475
+ if (!isBrowser$1()) return;
1476
+ let styleElement = document.getElementById(id);
1477
+ styleElement || (styleElement = document.createElement("style"), styleElement.id = id,
1478
+ styleElement.setAttribute("data-atomix-theme", "true"), document.head.appendChild(styleElement)),
1479
+ styleElement.textContent = css;
1480
+ }
1481
+
1482
+ /**
1483
+ * Remove injected CSS from DOM
1484
+ *
1485
+ * Removes the style element with the given ID from the document head.
1486
+ *
1487
+ * @param id - Style element ID to remove (default: 'atomix-theme')
1488
+ *
1489
+ * @example
1490
+ * ```typescript
1491
+ * removeCSS(); // Removes default 'atomix-theme'
1492
+ * removeCSS('my-custom-theme'); // Removes custom ID
1493
+ * ```
1494
+ */ function removeCSS(id = "atomix-theme") {
1495
+ if (!isBrowser$1()) return;
1496
+ const styleElement = document.getElementById(id);
1497
+ styleElement && styleElement.remove();
1498
+ }
1499
+
1500
+ /**
1501
+ * Check if CSS is already injected
1502
+ *
1503
+ * @param id - Style element ID to check (default: 'atomix-theme')
1504
+ * @returns True if style element exists
1505
+ */ function isCSSInjected(id = "atomix-theme") {
1506
+ return !!isBrowser$1() && null !== document.getElementById(id);
1507
+ }
1508
+
1509
+ /**
1510
+ * Theme System Constants
1511
+ *
1512
+ * Centralized constants for the theme system to avoid magic numbers and strings.
1513
+ */
1514
+ /**
1515
+ * Default storage key for theme persistence
1516
+ */
1517
+ /**
1518
+ * Theme System Error Handling
1519
+ *
1520
+ * Centralized error handling for the Atomix theme system.
1521
+ * Provides custom error classes and logging utilities.
1522
+ */
1523
+ /**
1524
+ * Theme error codes
1525
+ */
1526
+ var ThemeErrorCode, LogLevel;
1527
+
1528
+ "undefined" != typeof process && process.env, function(ThemeErrorCode) {
1529
+ /** Theme not found in registry */
1530
+ ThemeErrorCode.THEME_NOT_FOUND = "THEME_NOT_FOUND",
1531
+ /** Theme failed to load */
1532
+ ThemeErrorCode.THEME_LOAD_FAILED = "THEME_LOAD_FAILED",
1533
+ /** Theme validation failed */
1534
+ ThemeErrorCode.THEME_VALIDATION_FAILED = "THEME_VALIDATION_FAILED",
1535
+ /** Configuration loading failed */
1536
+ ThemeErrorCode.CONFIG_LOAD_FAILED = "CONFIG_LOAD_FAILED",
1537
+ /** Configuration validation failed */
1538
+ ThemeErrorCode.CONFIG_VALIDATION_FAILED = "CONFIG_VALIDATION_FAILED",
1539
+ /** Circular dependency detected */
1540
+ ThemeErrorCode.CIRCULAR_DEPENDENCY = "CIRCULAR_DEPENDENCY",
1541
+ /** Missing dependency */
1542
+ ThemeErrorCode.MISSING_DEPENDENCY = "MISSING_DEPENDENCY",
1543
+ /** Storage operation failed */
1544
+ ThemeErrorCode.STORAGE_ERROR = "STORAGE_ERROR",
1545
+ /** Invalid theme name */
1546
+ ThemeErrorCode.INVALID_THEME_NAME = "INVALID_THEME_NAME",
1547
+ /** CSS injection failed */
1548
+ ThemeErrorCode.CSS_INJECTION_FAILED = "CSS_INJECTION_FAILED",
1549
+ /** Invalid color format */
1550
+ ThemeErrorCode.INVALID_COLOR_FORMAT = "INVALID_COLOR_FORMAT",
1551
+ /** Missing required token */
1552
+ ThemeErrorCode.MISSING_REQUIRED_TOKEN = "MISSING_REQUIRED_TOKEN",
1553
+ /** Accessibility contrast violation */
1554
+ ThemeErrorCode.CONTRAST_VIOLATION = "CONTRAST_VIOLATION",
1555
+ /** Invalid token type */
1556
+ ThemeErrorCode.INVALID_TOKEN_TYPE = "INVALID_TOKEN_TYPE",
1557
+ /** Unknown error */
1558
+ ThemeErrorCode.UNKNOWN_ERROR = "UNKNOWN_ERROR";
1559
+ }(ThemeErrorCode || (ThemeErrorCode = {}));
1560
+
1561
+ /**
1562
+ * Custom error class for theme-related errors
1563
+ */
1564
+ class ThemeError extends Error {
1565
+ constructor(message, code = ThemeErrorCode.UNKNOWN_ERROR, context) {
1566
+ super(message), this.name = "ThemeError", this.code = code, this.context = context,
1567
+ this.timestamp = Date.now(),
1568
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
1569
+ Error.captureStackTrace && Error.captureStackTrace(this, ThemeError);
1570
+ }
1571
+ /**
1572
+ * Convert error to JSON for logging
1573
+ */ toJSON() {
1574
+ return {
1575
+ name: this.name,
1576
+ message: this.message,
1577
+ code: this.code,
1578
+ context: this.context,
1579
+ timestamp: this.timestamp,
1580
+ stack: this.stack
1581
+ };
1582
+ }
1583
+ }
1584
+
1585
+ /**
1586
+ * Log level
1587
+ */ !function(LogLevel) {
1588
+ LogLevel[LogLevel.ERROR = 0] = "ERROR", LogLevel[LogLevel.WARN = 1] = "WARN", LogLevel[LogLevel.INFO = 2] = "INFO",
1589
+ LogLevel[LogLevel.DEBUG = 3] = "DEBUG";
1590
+ }(LogLevel || (LogLevel = {}));
1591
+
1592
+ /**
1593
+ * Theme Logger
1594
+ *
1595
+ * Centralized logging for the theme system.
1596
+ * Replaces console statements with structured logging.
1597
+ */
1598
+ class ThemeLogger {
1599
+ constructor(config = {}) {
1600
+ this.config = {
1601
+ level: config.level ?? ("undefined" != typeof process && "production" === process.env?.NODE_ENV ? LogLevel.WARN : LogLevel.INFO),
1602
+ enableConsole: config.enableConsole ?? !0,
1603
+ onError: config.onError,
1604
+ onWarn: config.onWarn,
1605
+ onInfo: config.onInfo,
1606
+ onDebug: config.onDebug
1607
+ };
1608
+ }
1609
+ /**
1610
+ * Log an error
1611
+ */ error(message, error, context) {
1612
+ if (this.config.level < LogLevel.ERROR) return;
1613
+ const errorObj = error instanceof Error ? error : new Error(message), themeError = error instanceof ThemeError ? error : new ThemeError(message, ThemeErrorCode.UNKNOWN_ERROR, context);
1614
+ this.config.enableConsole && console.error(`[ThemeError] ${message}`, {
1615
+ error: errorObj,
1616
+ context: {
1617
+ ...context,
1618
+ ...themeError.context
1619
+ },
1620
+ code: themeError.code
1621
+ }), this.config.onError?.(themeError, context);
1622
+ }
1623
+ /**
1624
+ * Log a warning
1625
+ */ warn(message, context) {
1626
+ this.config.level < LogLevel.WARN || (this.config.enableConsole && console.warn(`[ThemeWarning] ${message}`, context || {}),
1627
+ this.config.onWarn?.(message, context));
1628
+ }
1629
+ /**
1630
+ * Log an info message
1631
+ */ info(message, context) {
1632
+ this.config.level < LogLevel.INFO || (this.config.enableConsole && console.info(`[ThemeInfo] ${message}`, context || {}),
1633
+ this.config.onInfo?.(message, context));
1634
+ }
1635
+ /**
1636
+ * Log a debug message
1637
+ */ debug(message, context) {
1638
+ this.config.level < LogLevel.DEBUG || (this.config.enableConsole, this.config.onDebug?.(message, context));
1639
+ }
1640
+ }
1641
+
1642
+ /**
1643
+ * Default logger instance
1644
+ */ let defaultLogger = null;
1645
+
1646
+ /**
1647
+ * Get or create default logger
1648
+ */ function getLogger() {
1649
+ return defaultLogger || (defaultLogger = new ThemeLogger), defaultLogger;
1650
+ }
1163
1651
 
1164
1652
  /**
1165
1653
  * Check if code is running in a browser environment
1166
- */
1167
- const isBrowser = () => "undefined" != typeof window && "undefined" != typeof document, isServer = () => !isBrowser(), sanitizePath = path => path.replace(/[<>"']/g, "").replace(/\.\./g, "").replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "") // Trim leading/trailing slashes
1654
+ */ const isBrowser = () => "undefined" != typeof window && "undefined" != typeof document, isServer = () => !isBrowser(), sanitizePath = path => path.replace(/[<>"']/g, "").replace(/\.\./g, "").replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "") // Trim leading/trailing slashes
1168
1655
  , buildThemePath = (themeName, basePath = "/themes", useMinified = !1, cdnPath = null) => {
1169
1656
  // Validate theme name to prevent path injection
1170
1657
  if (!isValidThemeName(themeName)) throw new ThemeError(`Invalid theme name: "${themeName}". Theme names must be lowercase alphanumeric with hyphens (e.g., "my-theme").`, ThemeErrorCode.INVALID_THEME_NAME, {
@@ -1248,107 +1735,481 @@ const isBrowser = () => "undefined" != typeof window && "undefined" != typeof do
1248
1735
 
1249
1736
  /**
1250
1737
  * Check if code is running on the server (SSR)
1251
- */
1738
+ */ var aCallable = aCallable$3, toObject = toObject$2, IndexedObject = indexedObject, lengthOfArrayLike = lengthOfArrayLike$2, $TypeError = TypeError, REDUCE_EMPTY = "Reduce of empty array with no initial value", createMethod = function(IS_RIGHT) {
1739
+ return function(that, callbackfn, argumentsLength, memo) {
1740
+ var O = toObject(that), self = IndexedObject(O), length = lengthOfArrayLike(O);
1741
+ if (aCallable(callbackfn), 0 === length && argumentsLength < 2) throw new $TypeError(REDUCE_EMPTY);
1742
+ var index = IS_RIGHT ? length - 1 : 0, i = IS_RIGHT ? -1 : 1;
1743
+ if (argumentsLength < 2) for (;;) {
1744
+ if (index in self) {
1745
+ memo = self[index], index += i;
1746
+ break;
1747
+ }
1748
+ if (index += i, IS_RIGHT ? index < 0 : length <= index) throw new $TypeError(REDUCE_EMPTY);
1749
+ }
1750
+ for (;IS_RIGHT ? index >= 0 : length > index; index += i) index in self && (memo = callbackfn(memo, self[index], index, O));
1751
+ return memo;
1752
+ };
1753
+ }, arrayReduce = {
1754
+ // `Array.prototype.reduce` method
1755
+ // https://tc39.es/ecma262/#sec-array.prototype.reduce
1756
+ left: createMethod(!1),
1757
+ // `Array.prototype.reduceRight` method
1758
+ // https://tc39.es/ecma262/#sec-array.prototype.reduceright
1759
+ right: createMethod(!0)
1760
+ }, fails = fails$9, globalThis$1 = globalThis_1, userAgent = environmentUserAgent, classof = classofRaw$2, userAgentStartsWith = function(string) {
1761
+ return userAgent.slice(0, string.length) === string;
1762
+ }, environment = userAgentStartsWith("Bun/") ? "BUN" : userAgentStartsWith("Cloudflare-Workers") ? "CLOUDFLARE" : userAgentStartsWith("Deno/") ? "DENO" : userAgentStartsWith("Node.js/") ? "NODE" : globalThis$1.Bun && "string" == typeof Bun.version ? "BUN" : globalThis$1.Deno && "object" == typeof Deno.version ? "DENO" : "process" === classof(globalThis$1.process) ? "NODE" : globalThis$1.window && globalThis$1.document ? "BROWSER" : "REST", $reduce = arrayReduce.left;
1763
+
1764
+ // `Array.prototype.reduce` method
1765
+ // https://tc39.es/ecma262/#sec-array.prototype.reduce
1766
+ _export({
1767
+ target: "Array",
1768
+ proto: !0,
1769
+ forced: !("NODE" === environment) && environmentV8Version > 79 && environmentV8Version < 83 || !function(METHOD_NAME, argument) {
1770
+ var method = [][METHOD_NAME];
1771
+ return !!method && fails((function() {
1772
+ // eslint-disable-next-line no-useless-call -- required for testing
1773
+ method.call(null, argument || function() {
1774
+ return 1;
1775
+ }, 1);
1776
+ }));
1777
+ }("reduce")
1778
+ }, {
1779
+ reduce: function(callbackfn /* , initialValue */) {
1780
+ var length = arguments.length;
1781
+ return $reduce(this, callbackfn, length, length > 1 ? arguments[1] : void 0);
1782
+ }
1783
+ });
1784
+
1785
+ var reduce$3 = getBuiltInPrototypeMethod$3("Array", "reduce"), isPrototypeOf = objectIsPrototypeOf, method = reduce$3, ArrayPrototype = Array.prototype;
1786
+
1787
+ const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
1788
+ var own = it.reduce;
1789
+ return it === ArrayPrototype || isPrototypeOf(ArrayPrototype, it) && own === ArrayPrototype.reduce ? method : own;
1790
+ }));
1791
+
1792
+ // ============================================================================
1793
+ // Theme Mode Switching
1794
+ // ============================================================================
1252
1795
  /**
1253
- * Theme Utilities
1796
+ * Switch between light and dark themes
1254
1797
  *
1255
- * Helper utilities for working with themes, including color manipulation,
1256
- * spacing helpers, and theme value accessors.
1257
- */
1798
+ * Automatically toggles a class on the root element and persists the choice.
1799
+ *
1800
+ * @param mode - Theme mode ('light', 'dark', or 'system')
1801
+ * @param options - Configuration options
1802
+ *
1803
+ * @example
1804
+ * ```typescript
1805
+ * import { switchTheme } from '@shohojdhara/atomix/theme/utils';
1806
+ *
1807
+ * // Switch to dark mode
1808
+ * switchTheme('dark');
1809
+ *
1810
+ * // Toggle between light/dark
1811
+ * const current = getCurrentTheme();
1812
+ * switchTheme(current === 'dark' ? 'light' : 'dark');
1813
+ * ```
1814
+ */ function switchTheme(mode, options = {}) {
1815
+ const {selector: selector = ":root", storageKey: storageKey = "atomix-theme", enableTransition: enableTransition = !0, transitionDuration: transitionDuration = 300} = options, resolvedMode = "system" === mode ? getSystemTheme() : mode, root = ":root" === selector ? document.documentElement : document.querySelector(selector);
1816
+ // Determine actual mode (resolve 'system')
1817
+ if (root) {
1818
+ // Add transition class if enabled
1819
+ if (enableTransition) {
1820
+ const htmlRoot = root;
1821
+ htmlRoot.style.transition = `all ${transitionDuration}ms ease-in-out`,
1822
+ // Remove transition after it completes
1823
+ setTimeout((() => {
1824
+ htmlRoot.style.transition = "";
1825
+ }), transitionDuration);
1826
+ }
1827
+ // Apply theme class
1828
+ root.classList.remove("atomix-theme-light", "atomix-theme-dark"), root.classList.add(`atomix-theme-${resolvedMode}`),
1829
+ // Set data attribute for CSS selectors
1830
+ root.setAttribute("data-theme", resolvedMode),
1831
+ // Persist choice
1832
+ persistTheme(resolvedMode, {
1833
+ storageKey: storageKey
1834
+ }),
1835
+ // Dispatch custom event for listeners
1836
+ window.dispatchEvent(new CustomEvent("atomix-theme-change", {
1837
+ detail: {
1838
+ mode: resolvedMode
1839
+ }
1840
+ }));
1841
+ }
1842
+ }
1843
+
1844
+ /**
1845
+ * Toggle between light and dark themes
1846
+ *
1847
+ * @param options - Configuration options
1848
+ * @returns The new theme mode
1849
+ *
1850
+ * @example
1851
+ * ```typescript
1852
+ * const newMode = toggleTheme();
1853
+ * console.log('Switched to:', newMode);
1854
+ * ```
1855
+ */ function toggleTheme(options = {}) {
1856
+ const next = "dark" === getCurrentTheme(options.storageKey) ? "light" : "dark";
1857
+ return switchTheme(next, options), next;
1858
+ }
1859
+
1860
+ /**
1861
+ * Get current theme mode
1862
+ *
1863
+ * @param storageKey - Storage key (default: 'atomix-theme')
1864
+ * @returns Current theme mode or 'light' if not set
1865
+ */ function getCurrentTheme(storageKey = "atomix-theme") {
1866
+ return "undefined" == typeof window ? "light" : localStorage.getItem(storageKey) || "light";
1867
+ }
1868
+
1869
+ /**
1870
+ * Get system theme preference
1871
+ *
1872
+ * @returns 'dark' if system prefers dark mode, 'light' otherwise
1873
+ */ function getSystemTheme() {
1874
+ return "undefined" == typeof window ? "light" : window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1875
+ }
1876
+
1877
+ /**
1878
+ * Initialize theme based on saved preference or system preference
1879
+ *
1880
+ * Call this once at app startup.
1881
+ *
1882
+ * @param options - Configuration options
1883
+ * @returns The initialized theme mode
1884
+ *
1885
+ * @example
1886
+ * ```typescript
1887
+ * // In your app entry point
1888
+ * import { initializeTheme } from '@shohojdhara/atomix/theme/utils';
1889
+ *
1890
+ * const theme = initializeTheme();
1891
+ * console.log('Theme initialized:', theme);
1892
+ * ```
1893
+ */ function initializeTheme(options = {}) {
1894
+ const saved = getCurrentTheme(options.storageKey);
1895
+ // If no saved preference, use system preference
1896
+ if (!saved || "system" === saved) {
1897
+ const system = getSystemTheme();
1898
+ return switchTheme(system, options), system;
1899
+ }
1900
+ // Use saved preference
1901
+ return switchTheme(saved, options), saved;
1902
+ }
1903
+
1904
+ /**
1905
+ * Listen for system theme changes
1906
+ *
1907
+ * @param callback - Function to call when system theme changes
1908
+ * @returns Cleanup function to stop listening
1909
+ *
1910
+ * @example
1911
+ * ```typescript
1912
+ * const cleanup = listenToSystemTheme((mode) => {
1913
+ * console.log('System theme changed to:', mode);
1914
+ * switchTheme(mode);
1915
+ * });
1916
+ *
1917
+ * // Later, when component unmounts
1918
+ * cleanup();
1919
+ * ```
1920
+ */ function listenToSystemTheme(callback) {
1921
+ if ("undefined" == typeof window) return () => {};
1922
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"), handler = e => {
1923
+ callback(e.matches ? "dark" : "light");
1924
+ };
1925
+ // Modern browsers
1926
+ return mediaQuery.addEventListener ? (mediaQuery.addEventListener("change", handler),
1927
+ () => mediaQuery.removeEventListener("change", handler)) : (
1928
+ // Fallback for older browsers
1929
+ mediaQuery.addListener(handler), () => mediaQuery.removeListener(handler));
1930
+ }
1931
+
1258
1932
  // ============================================================================
1259
- // Color Manipulation Utilities
1933
+ // Theme Persistence
1260
1934
  // ============================================================================
1261
1935
  /**
1262
- * Convert hex color to RGB object
1263
- */
1264
- function hexToRgb$1(hex) {
1265
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1266
- return result ? {
1267
- r: parseInt(result[1], 16),
1268
- g: parseInt(result[2], 16),
1269
- b: parseInt(result[3], 16)
1270
- } : null;
1936
+ * Save theme preference to storage
1937
+ *
1938
+ * @param mode - Theme mode to save
1939
+ * @param options - Persistence options
1940
+ */ function persistTheme(mode, options = {}) {
1941
+ if ("undefined" == typeof window) return;
1942
+ const {storageKey: storageKey = "atomix-theme", storageType: storageType = "localStorage"} = options;
1943
+ ("localStorage" === storageType ? localStorage : sessionStorage).setItem(storageKey, mode);
1944
+ }
1945
+
1946
+ /**
1947
+ * Clear saved theme preference
1948
+ *
1949
+ * @param options - Persistence options
1950
+ */ function clearThemePreference(options = {}) {
1951
+ if ("undefined" == typeof window) return;
1952
+ const {storageKey: storageKey = "atomix-theme", storageType: storageType = "localStorage"} = options;
1953
+ ("localStorage" === storageType ? localStorage : sessionStorage).removeItem(storageKey);
1954
+ }
1955
+
1956
+ // ============================================================================
1957
+ // Theme Tokens Manipulation
1958
+ // ============================================================================
1959
+ /**
1960
+ * Merge multiple token sets
1961
+ *
1962
+ * Deep merges token objects, with later tokens overriding earlier ones.
1963
+ *
1964
+ * @param tokens - Token objects to merge
1965
+ * @returns Merged tokens
1966
+ *
1967
+ * @example
1968
+ * ```typescript
1969
+ * const merged = mergeTokens(
1970
+ * baseTokens,
1971
+ * { colors: { primary: { main: '#custom' } } }
1972
+ * );
1973
+ * ```
1974
+ */ function mergeTokens(...tokens) {
1975
+ return _reduceInstanceProperty(tokens).call(tokens, ((acc, current) => deepMerge(acc, current)), {});
1976
+ }
1977
+
1978
+ /**
1979
+ * Override specific tokens
1980
+ *
1981
+ * Creates a new token object with specific overrides.
1982
+ *
1983
+ * @param base - Base tokens
1984
+ * @param overrides - Tokens to override
1985
+ * @returns New tokens with overrides applied
1986
+ *
1987
+ * @example
1988
+ * ```typescript
1989
+ * const customized = overrideTokens(defaultTokens, {
1990
+ * colors: {
1991
+ * primary: { main: '#ff0000' }
1992
+ * }
1993
+ * });
1994
+ * ```
1995
+ */ function overrideTokens(base, overrides) {
1996
+ return deepMerge({
1997
+ ...base
1998
+ }, overrides);
1999
+ }
2000
+
2001
+ /**
2002
+ * Pick specific token categories
2003
+ *
2004
+ * Extracts only the specified categories from tokens.
2005
+ *
2006
+ * @param tokens - Source tokens
2007
+ * @param categories - Categories to pick
2008
+ * @returns Tokens with only selected categories
2009
+ *
2010
+ * @example
2011
+ * ```typescript
2012
+ * const colorTokens = pickTokens(allTokens, ['colors']);
2013
+ * ```
2014
+ */ function pickTokens(tokens, categories) {
2015
+ const result = {};
2016
+ return categories.forEach((category => {
2017
+ tokens[category] && (result[category] = tokens[category]);
2018
+ })), result;
2019
+ }
2020
+
2021
+ /**
2022
+ * Omit specific token categories
2023
+ *
2024
+ * Removes specified categories from tokens.
2025
+ *
2026
+ * @param tokens - Source tokens
2027
+ * @param categories - Categories to omit
2028
+ * @returns Tokens without omitted categories
2029
+ *
2030
+ * @example
2031
+ * ```typescript
2032
+ * const withoutColors = omitTokens(allTokens, ['colors']);
2033
+ * ```
2034
+ */ function omitTokens(tokens, categories) {
2035
+ const result = {
2036
+ ...tokens
2037
+ };
2038
+ return categories.forEach((category => {
2039
+ delete result[category];
2040
+ })), result;
2041
+ }
2042
+
2043
+ // ============================================================================
2044
+ // Color Utilities
2045
+ // ============================================================================
2046
+ /**
2047
+ * Convert hex color to RGB
2048
+ *
2049
+ * @param hex - Hex color (with or without #)
2050
+ * @returns RGB object { r, g, b }
2051
+ */ function hexToRgb$1(hex) {
2052
+ // Validate
2053
+ if (
2054
+ // Remove # if present
2055
+ // Handle shorthand hex
2056
+ 3 === (hex = hex.replace(/^#/, "")).length && (hex = hex.split("").map((c => c + c)).join("")),
2057
+ 6 !== hex.length) return null;
2058
+ const num = parseInt(hex, 16);
2059
+ return {
2060
+ r: num >> 16 & 255,
2061
+ g: num >> 8 & 255,
2062
+ b: 255 & num
2063
+ };
1271
2064
  }
1272
2065
 
1273
2066
  /**
1274
- * Convert RGB to hex color
2067
+ * Convert RGB to hex
2068
+ *
2069
+ * @param r - Red (0-255)
2070
+ * @param g - Green (0-255)
2071
+ * @param b - Blue (0-255)
2072
+ * @returns Hex color with #
1275
2073
  */ function rgbToHex(r, g, b) {
1276
- const toHex = val => Math.round(Math.max(0, Math.min(255, val))).toString(16).padStart(2, "0");
1277
- return `#${toHex(r ?? 0)}${toHex(g ?? 0)}${toHex(b ?? 0)}`;
2074
+ return "#" + [ r, g, b ].map((x => {
2075
+ const hex = x.toString(16);
2076
+ return 1 === hex.length ? "0" + hex : hex;
2077
+ })).join("");
1278
2078
  }
1279
2079
 
1280
2080
  /**
1281
- * Calculate relative luminance of a color
1282
- * Used for determining contrast ratios
1283
- */ function getLuminance(color) {
1284
- const rgb = hexToRgb$1(color);
2081
+ * Calculate luminance of a color
2082
+ *
2083
+ * Used for determining contrast ratios.
2084
+ *
2085
+ * @param hex - Hex color
2086
+ * @returns Luminance value (0-1)
2087
+ */ function getLuminance(hex) {
2088
+ const rgb = hexToRgb$1(hex);
1285
2089
  if (!rgb) return 0;
1286
- const {r: r, g: g, b: b} = rgb, [rs, gs, bs] = [ r ?? 0, g ?? 0, b ?? 0 ].map((c => {
1287
- const val = c / 255;
1288
- return val <= .03928 ? val / 12.92 : Math.pow((val + .055) / 1.055, 2.4);
1289
- }));
1290
- return .2126 * (rs ?? 0) + .7152 * (gs ?? 0) + .0722 * (bs ?? 0);
2090
+ const [r, g, b] = [ rgb.r, rgb.g, rgb.b ].map((v => (v /= 255) <= .03928 ? v / 12.92 : Math.pow((v + .055) / 1.055, 2.4)));
2091
+ return .2126 * (r ?? 0) + .7152 * (g ?? 0) + .0722 * (b ?? 0);
1291
2092
  }
1292
2093
 
1293
2094
  /**
1294
2095
  * Calculate contrast ratio between two colors
1295
- */ function getContrastRatio(foreground, background) {
1296
- const lumA = getLuminance(foreground), lumB = getLuminance(background);
1297
- return (Math.max(lumA, lumB) + .05) / (Math.min(lumA, lumB) + .05);
2096
+ *
2097
+ * @param hex1 - First hex color
2098
+ * @param hex2 - Second hex color
2099
+ * @returns Contrast ratio (1-21)
2100
+ */ function getContrastRatio(hex1, hex2) {
2101
+ const lum1 = getLuminance(hex1), lum2 = getLuminance(hex2);
2102
+ return (Math.max(lum1, lum2) + .05) / (Math.min(lum1, lum2) + .05);
2103
+ }
2104
+
2105
+ /**
2106
+ * Check if text color passes WCAG AA standard
2107
+ *
2108
+ * @param textColor - Text color hex
2109
+ * @param backgroundColor - Background color hex
2110
+ * @param size - Font size ('small' or 'large')
2111
+ * @returns true if passes WCAG AA
2112
+ */ function isAccessible(textColor, backgroundColor, size = "small") {
2113
+ return getContrastRatio(textColor, backgroundColor) >= ("large" === size ? 3 : 4.5);
1298
2114
  }
1299
2115
 
1300
2116
  /**
1301
- * Get appropriate contrast text color (black or white) for a background color
1302
- */ function getContrastText(background, threshold = 3) {
1303
- const contrastWithWhite = getContrastRatio("#FFFFFF", background), contrastWithBlack = getContrastRatio("#000000", background);
1304
- return contrastWithWhite >= threshold ? "#FFFFFF" : contrastWithBlack >= threshold ? "#000000" : contrastWithWhite > contrastWithBlack ? "#FFFFFF" : "#000000";
2117
+ * Get appropriate text color (black or white) for a background
2118
+ *
2119
+ * @param backgroundColor - Background hex color
2120
+ * @param threshold - Contrast threshold (default: 3)
2121
+ * @returns '#000000' or '#FFFFFF'
2122
+ */ function getContrastText(backgroundColor, threshold = 3) {
2123
+ return getContrastRatio(backgroundColor, "#FFFFFF") >= threshold ? "#FFFFFF" : "#000000";
1305
2124
  }
1306
2125
 
1307
2126
  /**
1308
- * Lighten a color by a given amount
2127
+ * Lighten a color
1309
2128
  *
1310
- * @param color - Hex color string
1311
- * @param amount - Amount to lighten (0-1), default 0.2
2129
+ * @param hex - Base hex color
2130
+ * @param amount - Amount to lighten (0-1)
1312
2131
  * @returns Lightened hex color
1313
- */ function lighten(color, amount = .2) {
1314
- const rgb = hexToRgb$1(color);
1315
- if (!rgb) return color;
1316
- const {r: r, g: g, b: b} = rgb, lightenValue = val => Math.min(255, Math.round(val + (255 - val) * amount));
1317
- return rgbToHex(lightenValue(r), lightenValue(g), lightenValue(b));
2132
+ */ function lighten(hex, amount = 0) {
2133
+ const rgb = hexToRgb$1(hex);
2134
+ if (!rgb) return hex;
2135
+ // Use amount directly as factor (0-1)
2136
+ const factor = Math.max(0, Math.min(1, amount)), r = Math.round(rgb.r + (255 - rgb.r) * factor), g = Math.round(rgb.g + (255 - rgb.g) * factor), b = Math.round(rgb.b + (255 - rgb.b) * factor);
2137
+ return rgbToHex(Math.min(255, r), Math.min(255, g), Math.min(255, b));
1318
2138
  }
1319
2139
 
1320
2140
  /**
1321
- * Darken a color by a given amount
2141
+ * Darken a color
1322
2142
  *
1323
- * @param color - Hex color string
1324
- * @param amount - Amount to darken (0-1), default 0.2
2143
+ * @param hex - Base hex color
2144
+ * @param amount - Amount to darken (0-1)
1325
2145
  * @returns Darkened hex color
1326
- */ function darken(color, amount = .2) {
1327
- const rgb = hexToRgb$1(color);
1328
- if (!rgb) return color;
1329
- const {r: r, g: g, b: b} = rgb, darkenValue = val => Math.max(0, Math.round(val * (1 - amount)));
1330
- return rgbToHex(darkenValue(r), darkenValue(g), darkenValue(b));
2146
+ */ function darken(hex, amount = 0) {
2147
+ const rgb = hexToRgb$1(hex);
2148
+ if (!rgb) return hex;
2149
+ // Use amount directly as factor (0-1)
2150
+ const factor = Math.max(0, Math.min(1, amount)), r = Math.round(rgb.r * (1 - factor)), g = Math.round(rgb.g * (1 - factor)), b = Math.round(rgb.b * (1 - factor));
2151
+ return rgbToHex(Math.max(0, r), Math.max(0, g), Math.max(0, b));
1331
2152
  }
1332
2153
 
1333
2154
  /**
1334
- * Add alpha (opacity) to a color
2155
+ * Add alpha to a color
1335
2156
  *
1336
- * @param color - Hex color string
2157
+ * @param hex - Hex color
1337
2158
  * @param opacity - Opacity value (0-1)
1338
2159
  * @returns RGBA color string
1339
- */ function alpha(color, opacity) {
1340
- const rgb = hexToRgb$1(color);
1341
- if (!rgb) return color;
1342
- const {r: r, g: g, b: b} = rgb;
1343
- return `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, opacity))})`;
2160
+ */ function alpha(hex, opacity) {
2161
+ const rgb = hexToRgb$1(hex);
2162
+ if (!rgb) return hex;
2163
+ const validOpacity = Math.max(0, Math.min(1, opacity));
2164
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${validOpacity})`;
1344
2165
  }
1345
2166
 
1346
2167
  /**
1347
2168
  * Emphasize a color (lighten if dark, darken if light)
1348
2169
  *
1349
- * @param color - Hex color string
1350
- * @param coefficient - Amount to emphasize (0-1), default 0.15
2170
+ * @param hex - Hex color
2171
+ * @param amount - Amount to emphasize (0-1)
1351
2172
  * @returns Emphasized hex color
2173
+ */ function emphasize(hex, amount = .15) {
2174
+ return getLuminance(hex) > .5 ? darken(hex, amount) : lighten(hex, amount);
2175
+ }
2176
+
2177
+ /**
2178
+ * Create a spacing utility
2179
+ *
2180
+ * @param spacingInput - Spacing configuration
2181
+ * @returns Spacing function
2182
+ */ function createSpacing(spacingInput = 4) {
2183
+ return (...values) => 0 === values.length ? "0px" : "function" == typeof spacingInput ? spacingInput(...values) : values.map((value => {
2184
+ if ("number" == typeof spacingInput) return value * spacingInput + "px";
2185
+ if (Array.isArray(spacingInput)) {
2186
+ const scaled = spacingInput[value];
2187
+ return "number" == typeof scaled ? `${scaled}px` : `${value}px`;
2188
+ }
2189
+ return `${value}px`;
2190
+ })).join(" ");
2191
+ }
2192
+
2193
+ /**
2194
+ * CSS Variable Generator
2195
+ *
2196
+ * Generates CSS custom properties from theme objects and injects them into the DOM.
2197
+ *
2198
+ * **Token Naming Alignment:**
2199
+ * This generator produces CSS variables that match the SCSS token naming pattern exactly:
2200
+ * - Colors: --atomix-primary, --atomix-primary-1 through --atomix-primary-10
2201
+ * - Spacing: --atomix-spacing-1, --atomix-spacing-4, etc.
2202
+ * - Typography: --atomix-font-size-base, --atomix-font-weight-normal, etc.
2203
+ * - Shadows: --atomix-box-shadow, --atomix-box-shadow-sm, etc.
2204
+ *
2205
+ * All tokens follow the flat structure pattern used in SCSS (not nested like --atomix-palette-primary-main).
2206
+ * This ensures compatibility between SCSS themes and JavaScript themes.
2207
+ *
2208
+ * @see src/styles/03-generic/_generic.root.scss for SCSS token definitions
2209
+ */
2210
+ /**
2211
+ * Convert a nested object to flat CSS variable declarations
2212
+ * Uses iterative approach for better performance with large objects
1352
2213
  */
1353
2214
  /**
1354
2215
  * Generate a color scale from a base color (1-10 steps)
@@ -1416,18 +2277,7 @@ function generateCSSVariables(theme, options = {}) {
1416
2277
  color.dark && (vars[`${prefix}-${key}-hover`] = color.dark),
1417
2278
  // Generate semantic color variants (matches SCSS patterns)
1418
2279
  // Text emphasis: emphasized version of the color for text (--atomix-primary-text-emphasis)
1419
- vars[`${prefix}-${key}-text-emphasis`] = function(color, coefficient = .15) {
1420
- return getLuminance(color) > .5 ? darken(color, coefficient) : lighten(color, coefficient);
1421
- }
1422
- // ============================================================================
1423
- // Spacing Utilities
1424
- // ============================================================================
1425
- /**
1426
- * Create a spacing function from various input types
1427
- *
1428
- * @param spacingInput - Spacing configuration (number, array, or function), default 4
1429
- * @returns Spacing function
1430
- */ (color.main, .15),
2280
+ vars[`${prefix}-${key}-text-emphasis`] = emphasize(color.main, .15),
1431
2281
  // Background subtle: very light version for backgrounds (--atomix-primary-bg-subtle)
1432
2282
  vars[`${prefix}-${key}-bg-subtle`] = alpha(color.main, .1),
1433
2283
  // Border subtle: light version for borders (--atomix-primary-border-subtle)
@@ -1757,44 +2607,22 @@ function generateCSSVariables(theme, options = {}) {
1757
2607
  // 2X large border radius (maps to spacing-4 = 16px)
1758
2608
  vars[`${prefix}-border-radius-xxl`] = formatValue(borderRadius.xxl, "1rem"),
1759
2609
  // 3X large border radius (maps to spacing-6 = 24px)
1760
- vars[`${prefix}-border-radius-3xl`] = formatValue(borderRadius["3xl"], "1.5rem"),
1761
- // 4X large border radius (maps to spacing-8 = 32px)
1762
- vars[`${prefix}-border-radius-4xl`] = formatValue(borderRadius["4xl"], "2rem"),
1763
- // Pill shape (fully rounded, maps to spacing-200 = 800px)
1764
- vars[`${prefix}-border-radius-pill`] = formatValue(borderRadius.pill, "50rem"),
1765
- vars;
1766
- }
1767
- /**
1768
- * Generate focus ring CSS variables
1769
- */ (theme.borderRadius, prefix)), Object.assign(variables, function(palette, prefix) {
1770
- const vars = {};
1771
- // Focus ring properties
1772
- return vars[`${prefix}-focus-ring-width`] = "3px", vars[`${prefix}-focus-ring-offset`] = "2px",
1773
- vars[`${prefix}-focus-ring-opacity`] = "0.25", vars;
1774
- }(theme.palette, prefix)), theme.custom && Object.keys(theme.custom).length > 0) {
1775
- const customVars =
1776
- /**
1777
- * CSS Variable Generator
1778
- *
1779
- * Generates CSS custom properties from theme objects and injects them into the DOM.
1780
- *
1781
- * **Token Naming Alignment:**
1782
- * This generator produces CSS variables that match the SCSS token naming pattern exactly:
1783
- * - Colors: --atomix-primary, --atomix-primary-1 through --atomix-primary-10
1784
- * - Spacing: --atomix-spacing-1, --atomix-spacing-4, etc.
1785
- * - Typography: --atomix-font-size-base, --atomix-font-weight-normal, etc.
1786
- * - Shadows: --atomix-box-shadow, --atomix-box-shadow-sm, etc.
1787
- *
1788
- * All tokens follow the flat structure pattern used in SCSS (not nested like --atomix-palette-primary-main).
1789
- * This ensures compatibility between SCSS themes and JavaScript themes.
1790
- *
1791
- * @see src/styles/03-generic/_generic.root.scss for SCSS token definitions
1792
- */
1793
- /**
1794
- * Convert a nested object to flat CSS variable declarations
1795
- * Uses iterative approach for better performance with large objects
1796
- */
1797
- function(obj, prefix = "", result = {}) {
2610
+ vars[`${prefix}-border-radius-3xl`] = formatValue(borderRadius["3xl"], "1.5rem"),
2611
+ // 4X large border radius (maps to spacing-8 = 32px)
2612
+ vars[`${prefix}-border-radius-4xl`] = formatValue(borderRadius["4xl"], "2rem"),
2613
+ // Pill shape (fully rounded, maps to spacing-200 = 800px)
2614
+ vars[`${prefix}-border-radius-pill`] = formatValue(borderRadius.pill, "50rem"),
2615
+ vars;
2616
+ }
2617
+ /**
2618
+ * Generate focus ring CSS variables
2619
+ */ (theme.borderRadius, prefix)), Object.assign(variables, function(palette, prefix) {
2620
+ const vars = {};
2621
+ // Focus ring properties
2622
+ return vars[`${prefix}-focus-ring-width`] = "3px", vars[`${prefix}-focus-ring-offset`] = "2px",
2623
+ vars[`${prefix}-focus-ring-opacity`] = "0.25", vars;
2624
+ }(theme.palette, prefix)), theme.custom && Object.keys(theme.custom).length > 0) {
2625
+ const customVars = function(obj, prefix = "", result = {}) {
1798
2626
  // Use iterative approach with stack to avoid deep recursion
1799
2627
  const stack = [ {
1800
2628
  obj: obj,
@@ -2248,19 +3076,29 @@ const logger = getLogger(), ThemeProvider = ({children: children, defaultTheme:
2248
3076
  // Check storage first
2249
3077
  if (enablePersistence && storageAdapter.isAvailable()) {
2250
3078
  const stored = storageAdapter.getItem(storageKey);
2251
- if (stored) return stored;
3079
+ if (stored) {
3080
+ // If it looks like a JSON object, parse it
3081
+ if (stored.trim().startsWith("{")) try {
3082
+ return JSON.parse(stored);
3083
+ } catch (e) {
3084
+ return logger.error("Failed to parse stored theme tokens", e), stored;
3085
+ }
3086
+ return stored;
3087
+ }
2252
3088
  }
2253
3089
  // If defaultTheme is provided, use it
2254
3090
  return null != defaultTheme ? defaultTheme : "default";
2255
3091
  // Default fallback
2256
3092
  }), [ defaultTheme, enablePersistence, storageKey, storageAdapter ]), [currentTheme, setCurrentTheme] = useState((() => "string" == typeof initialDefaultTheme ? initialDefaultTheme : "tokens-theme")), [activeTokens, setActiveTokens] = useState((() => {
2257
- // If defaultTheme is DesignTokens, validate and store them
2258
- if (defaultTheme && "string" != typeof defaultTheme) {
3093
+ // 1. Check if initialDefaultTheme (from storage) is an object
3094
+ if (initialDefaultTheme && "string" != typeof initialDefaultTheme) {
3095
+ const {tokens: tokens, validation: validation} = validateAndMergeTokens(initialDefaultTheme);
3096
+ if (validation.valid) return tokens;
3097
+ }
3098
+ // 2. Check if defaultTheme prop is an object
3099
+ if (defaultTheme && "string" != typeof defaultTheme) {
2259
3100
  const {tokens: tokens, validation: validation} = validateAndMergeTokens(defaultTheme);
2260
- return validation.valid ? tokens : (logger.warn("Invalid default theme tokens, using defaults", {
2261
- errors: validation.errors,
2262
- warnings: validation.warnings
2263
- }), createTokens({}));
3101
+ if (validation.valid) return tokens;
2264
3102
  }
2265
3103
  return null;
2266
3104
  })), [isLoading, setIsLoading] = useState(!1), [error, setError] = useState(null), loadedThemesRef = useRef(new Set), themePromisesRef = useRef({}), abortControllerRef = useRef(null);
@@ -2272,10 +3110,14 @@ const logger = getLogger(), ThemeProvider = ({children: children, defaultTheme:
2272
3110
  useEffect((() => {
2273
3111
  isServer() || applyThemeAttributes(String(currentTheme), dataAttribute);
2274
3112
  }), [ currentTheme, dataAttribute ]),
2275
- // Handle theme persistence
3113
+ // Handle persistence
2276
3114
  useEffect((() => {
2277
- enablePersistence && storageAdapter.isAvailable() && storageAdapter.setItem(storageKey, String(currentTheme));
2278
- }), [ currentTheme, storageKey, enablePersistence, storageAdapter ]),
3115
+ enablePersistence && storageAdapter.isAvailable() && ("tokens-theme" === currentTheme ?
3116
+ // Only persist if we have actual tokens to store
3117
+ activeTokens && storageAdapter.setItem(storageKey, JSON.stringify(activeTokens)) :
3118
+ // Persist named theme string
3119
+ storageAdapter.setItem(storageKey, String(currentTheme)));
3120
+ }), [ currentTheme, activeTokens, enablePersistence, storageKey, storageAdapter ]),
2279
3121
  // Cleanup: Remove completed promises and abort controllers on unmount
2280
3122
  useEffect((() => () => {
2281
3123
  // Cancel any in-flight theme loads
@@ -2414,20 +3256,21 @@ const logger = getLogger(), ThemeProvider = ({children: children, defaultTheme:
2414
3256
  setIsLoading(!1);
2415
3257
  }
2416
3258
  }
2417
- }), [ themes, isThemeLoaded, handleError, basePath, useMinified, cdnPath ]), themeManager = useMemo((() => ({})), []), availableThemes = useMemo((() => Object.entries(themes).map((([name, metadata]) => ({
3259
+ }), [ themes, isThemeLoaded, handleError, basePath, useMinified, cdnPath ]), updateTheme = useCallback((async (sectionOrTokens, values) => setTheme("string" == typeof sectionOrTokens && values ? values : sectionOrTokens)), [ setTheme ]), themeManager = useMemo((() => ({})), []), availableThemes = useMemo((() => Object.entries(themes).map((([name, metadata]) => ({
2418
3260
  ...metadata,
2419
3261
  name: name
2420
3262
  })))), [ themes ]), contextValue = useMemo((() => ({
2421
3263
  theme: currentTheme,
2422
3264
  activeTokens: activeTokens,
2423
3265
  setTheme: setTheme,
3266
+ updateTheme: updateTheme,
2424
3267
  availableThemes: availableThemes,
2425
3268
  isLoading: isLoading,
2426
3269
  error: error,
2427
3270
  isThemeLoaded: isThemeLoaded,
2428
3271
  preloadTheme: preloadTheme,
2429
3272
  themeManager: themeManager
2430
- })), [ currentTheme, activeTokens, setTheme, availableThemes,
3273
+ })), [ currentTheme, activeTokens, setTheme, updateTheme, availableThemes,
2431
3274
  // Use memoized value
2432
3275
  isLoading, error, isThemeLoaded, preloadTheme, themeManager ]);
2433
3276
  // Check if theme is loaded
@@ -2481,6 +3324,7 @@ function useTheme() {
2481
3324
  theme: context.theme,
2482
3325
  activeTokens: context.activeTokens,
2483
3326
  setTheme: context.setTheme,
3327
+ updateTheme: context.updateTheme,
2484
3328
  availableThemes: context.availableThemes,
2485
3329
  isLoading: context.isLoading,
2486
3330
  error: context.error,
@@ -2716,6 +3560,389 @@ function useThemeTokens() {
2716
3560
  }
2717
3561
  }
2718
3562
 
3563
+ /**
3564
+ * useThemeSwitcher Hook
3565
+ *
3566
+ * React hook for managing theme switching with persistence and system preference detection.
3567
+ * Provides an easy-to-use API for dark/light mode toggling.
3568
+ *
3569
+ * @example
3570
+ * ```tsx
3571
+ * import { useThemeSwitcher } from '@shohojdhara/atomix/theme';
3572
+ *
3573
+ * function ThemeToggle() {
3574
+ * const { mode, toggle, setMode, isDark } = useThemeSwitcher();
3575
+ *
3576
+ * return (
3577
+ * <button onClick={toggle}>
3578
+ * {isDark ? '☀️ Light' : '🌙 Dark'}
3579
+ * </button>
3580
+ * );
3581
+ * }
3582
+ * ```
3583
+ */
3584
+ /**
3585
+ * Hook for managing theme switching
3586
+ *
3587
+ * @param options - Configuration options
3588
+ * @returns Theme switcher controls
3589
+ */ function useThemeSwitcher(options = {}) {
3590
+ const {initialMode: initialMode = "system", syncWithSystem: syncWithSystem = !1, storageKey: storageKey = "atomix-theme", enableTransition: enableTransition = !0, transitionDuration: transitionDuration = 300} = options, [mode, setModeState] = useState((() => {
3591
+ if ("undefined" == typeof window) return initialMode;
3592
+ // Check for saved preference first
3593
+ const saved = getCurrentTheme(storageKey);
3594
+ return saved && "system" !== saved ? saved : "system" === initialMode ? getSystemTheme() : initialMode;
3595
+ // Fall back to initial mode or system
3596
+ }));
3597
+ // State for current mode
3598
+ // Initialize theme on mount
3599
+ return useEffect((() => {
3600
+ "undefined" != typeof window && (
3601
+ // Initialize with proper theme application
3602
+ initializeTheme({
3603
+ storageKey: storageKey,
3604
+ enableTransition: enableTransition,
3605
+ transitionDuration: transitionDuration
3606
+ }),
3607
+ // Update state to match initialized theme
3608
+ setModeState(getCurrentTheme(storageKey)));
3609
+ }), [ storageKey, enableTransition, transitionDuration ]),
3610
+ // Listen for system theme changes if enabled
3611
+ useEffect((() => {
3612
+ if (syncWithSystem) return listenToSystemTheme((newMode => {
3613
+ setModeState(newMode), switchTheme(newMode, {
3614
+ storageKey: storageKey,
3615
+ enableTransition: enableTransition,
3616
+ transitionDuration: transitionDuration
3617
+ });
3618
+ }));
3619
+ }), [ syncWithSystem, storageKey, enableTransition, transitionDuration ]), {
3620
+ mode: mode,
3621
+ isDark: "dark" === mode,
3622
+ isLight: "light" === mode,
3623
+ toggle: useCallback((() => {
3624
+ const newMode = toggleTheme({
3625
+ storageKey: storageKey,
3626
+ enableTransition: enableTransition,
3627
+ transitionDuration: transitionDuration
3628
+ });
3629
+ return setModeState(newMode), newMode;
3630
+ }), [ storageKey, enableTransition, transitionDuration ]),
3631
+ setMode: useCallback((newMode => {
3632
+ switchTheme(newMode, {
3633
+ storageKey: storageKey,
3634
+ enableTransition: enableTransition,
3635
+ transitionDuration: transitionDuration
3636
+ }), setModeState(newMode);
3637
+ }), [ storageKey, enableTransition, transitionDuration ]),
3638
+ resetToSystem: useCallback((() => {
3639
+ const systemMode = getSystemTheme();
3640
+ switchTheme(systemMode, {
3641
+ storageKey: storageKey,
3642
+ enableTransition: enableTransition,
3643
+ transitionDuration: transitionDuration
3644
+ }), setModeState(systemMode);
3645
+ }), [ storageKey, enableTransition, transitionDuration ]),
3646
+ clearPreference: useCallback((() => {
3647
+ "undefined" != typeof window && localStorage.removeItem(storageKey);
3648
+ }), [ storageKey ])
3649
+ };
3650
+ }
3651
+
3652
+ /**
3653
+ * ThemeToggle component with multiple variants
3654
+ */ const ThemeToggle = ({className: className = "", showLabel: showLabel = !1, lightLabel: lightLabel = "Light", darkLabel: darkLabel = "Dark", iconSize: iconSize = 20, variant: variant = "icon", render: render, ariaLabel: ariaLabel = "Toggle theme", ...hookOptions}) => {
3655
+ const {mode: mode, isDark: isDark, toggle: toggle} = useThemeSwitcher(hookOptions);
3656
+ // Custom render
3657
+ return render ? jsx(Fragment, {
3658
+ children: render({
3659
+ isDark: isDark,
3660
+ toggle: toggle,
3661
+ mode: mode
3662
+ })
3663
+ }) :
3664
+ // Icon-only variant (default)
3665
+ "icon" === variant ? jsx("button", {
3666
+ onClick: toggle,
3667
+ className: `theme-toggle theme-toggle-icon ${className}`,
3668
+ "aria-label": ariaLabel,
3669
+ title: isDark ? darkLabel : lightLabel,
3670
+ style: {
3671
+ background: "none",
3672
+ border: "none",
3673
+ cursor: "pointer",
3674
+ padding: "8px",
3675
+ borderRadius: "50%",
3676
+ display: "flex",
3677
+ alignItems: "center",
3678
+ justifyContent: "center",
3679
+ transition: "all 0.3s ease-in-out"
3680
+ },
3681
+ children: isDark ? jsxs("svg", {
3682
+ width: iconSize,
3683
+ height: iconSize,
3684
+ viewBox: "0 0 24 24",
3685
+ fill: "none",
3686
+ stroke: "currentColor",
3687
+ strokeWidth: "2",
3688
+ strokeLinecap: "round",
3689
+ strokeLinejoin: "round",
3690
+ children: [ jsx("circle", {
3691
+ cx: "12",
3692
+ cy: "12",
3693
+ r: "5"
3694
+ }), jsx("line", {
3695
+ x1: "12",
3696
+ y1: "1",
3697
+ x2: "12",
3698
+ y2: "3"
3699
+ }), jsx("line", {
3700
+ x1: "12",
3701
+ y1: "21",
3702
+ x2: "12",
3703
+ y2: "23"
3704
+ }), jsx("line", {
3705
+ x1: "4.22",
3706
+ y1: "4.22",
3707
+ x2: "5.64",
3708
+ y2: "5.64"
3709
+ }), jsx("line", {
3710
+ x1: "18.36",
3711
+ y1: "18.36",
3712
+ x2: "19.78",
3713
+ y2: "19.78"
3714
+ }), jsx("line", {
3715
+ x1: "1",
3716
+ y1: "12",
3717
+ x2: "3",
3718
+ y2: "12"
3719
+ }), jsx("line", {
3720
+ x1: "21",
3721
+ y1: "12",
3722
+ x2: "23",
3723
+ y2: "12"
3724
+ }), jsx("line", {
3725
+ x1: "4.22",
3726
+ y1: "19.78",
3727
+ x2: "5.64",
3728
+ y2: "18.36"
3729
+ }), jsx("line", {
3730
+ x1: "18.36",
3731
+ y1: "5.64",
3732
+ x2: "19.78",
3733
+ y2: "4.22"
3734
+ }) ]
3735
+ }) : jsx("svg", {
3736
+ width: iconSize,
3737
+ height: iconSize,
3738
+ viewBox: "0 0 24 24",
3739
+ fill: "none",
3740
+ stroke: "currentColor",
3741
+ strokeWidth: "2",
3742
+ strokeLinecap: "round",
3743
+ strokeLinejoin: "round",
3744
+ children: jsx("path", {
3745
+ d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
3746
+ })
3747
+ })
3748
+ }) :
3749
+ // Button variant with text
3750
+ "button" === variant ? jsxs("button", {
3751
+ onClick: toggle,
3752
+ className: `theme-toggle theme-toggle-button ${className}`,
3753
+ "aria-label": ariaLabel,
3754
+ style: {
3755
+ display: "flex",
3756
+ alignItems: "center",
3757
+ gap: "8px",
3758
+ padding: "8px 16px",
3759
+ borderRadius: "8px",
3760
+ border: "1px solid currentColor",
3761
+ background: "transparent",
3762
+ color: "inherit",
3763
+ cursor: "pointer",
3764
+ fontSize: "14px",
3765
+ fontWeight: "500",
3766
+ transition: "all 0.3s ease-in-out"
3767
+ },
3768
+ children: [ isDark ? jsxs("svg", {
3769
+ width: iconSize,
3770
+ height: iconSize,
3771
+ viewBox: "0 0 24 24",
3772
+ fill: "none",
3773
+ stroke: "currentColor",
3774
+ strokeWidth: "2",
3775
+ strokeLinecap: "round",
3776
+ strokeLinejoin: "round",
3777
+ children: [ jsx("circle", {
3778
+ cx: "12",
3779
+ cy: "12",
3780
+ r: "5"
3781
+ }), jsx("line", {
3782
+ x1: "12",
3783
+ y1: "1",
3784
+ x2: "12",
3785
+ y2: "3"
3786
+ }), jsx("line", {
3787
+ x1: "12",
3788
+ y1: "21",
3789
+ x2: "12",
3790
+ y2: "23"
3791
+ }), jsx("line", {
3792
+ x1: "4.22",
3793
+ y1: "4.22",
3794
+ x2: "5.64",
3795
+ y2: "5.64"
3796
+ }), jsx("line", {
3797
+ x1: "18.36",
3798
+ y1: "18.36",
3799
+ x2: "19.78",
3800
+ y2: "19.78"
3801
+ }), jsx("line", {
3802
+ x1: "1",
3803
+ y1: "12",
3804
+ x2: "3",
3805
+ y2: "12"
3806
+ }), jsx("line", {
3807
+ x1: "21",
3808
+ y1: "12",
3809
+ x2: "23",
3810
+ y2: "12"
3811
+ }), jsx("line", {
3812
+ x1: "4.22",
3813
+ y1: "19.78",
3814
+ x2: "5.64",
3815
+ y2: "18.36"
3816
+ }), jsx("line", {
3817
+ x1: "18.36",
3818
+ y1: "5.64",
3819
+ x2: "19.78",
3820
+ y2: "4.22"
3821
+ }) ]
3822
+ }) : jsx("svg", {
3823
+ width: iconSize,
3824
+ height: iconSize,
3825
+ viewBox: "0 0 24 24",
3826
+ fill: "none",
3827
+ stroke: "currentColor",
3828
+ strokeWidth: "2",
3829
+ strokeLinecap: "round",
3830
+ strokeLinejoin: "round",
3831
+ children: jsx("path", {
3832
+ d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
3833
+ })
3834
+ }), showLabel && jsx("span", {
3835
+ children: isDark ? darkLabel : lightLabel
3836
+ }) ]
3837
+ }) :
3838
+ // Switch/toggle variant
3839
+ "switch" === variant ? jsx("div", {
3840
+ className: `theme-toggle theme-toggle-switch ${className}`,
3841
+ role: "button",
3842
+ tabIndex: 0,
3843
+ onClick: toggle,
3844
+ onKeyDown: e => "Enter" === e.key && toggle(),
3845
+ "aria-label": ariaLabel,
3846
+ style: {
3847
+ position: "relative",
3848
+ width: "56px",
3849
+ height: "28px",
3850
+ borderRadius: "14px",
3851
+ background: isDark ? "#4b5563" : "#d1d5db",
3852
+ cursor: "pointer",
3853
+ transition: "background 0.3s ease-in-out",
3854
+ display: "flex",
3855
+ alignItems: "center",
3856
+ padding: "2px"
3857
+ },
3858
+ children: jsx("div", {
3859
+ style: {
3860
+ position: "absolute",
3861
+ left: isDark ? "auto" : "2px",
3862
+ right: isDark ? "2px" : "auto",
3863
+ width: "24px",
3864
+ height: "24px",
3865
+ borderRadius: "50%",
3866
+ background: "white",
3867
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)",
3868
+ transition: "all 0.3s ease-in-out",
3869
+ display: "flex",
3870
+ alignItems: "center",
3871
+ justifyContent: "center"
3872
+ },
3873
+ children: isDark ? jsx("svg", {
3874
+ width: "14",
3875
+ height: "14",
3876
+ viewBox: "0 0 24 24",
3877
+ fill: "none",
3878
+ stroke: "#4b5563",
3879
+ strokeWidth: "2",
3880
+ strokeLinecap: "round",
3881
+ strokeLinejoin: "round",
3882
+ children: jsx("path", {
3883
+ d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
3884
+ })
3885
+ }) : jsxs("svg", {
3886
+ width: "14",
3887
+ height: "14",
3888
+ viewBox: "0 0 24 24",
3889
+ fill: "none",
3890
+ stroke: "#f59e0b",
3891
+ strokeWidth: "2",
3892
+ strokeLinecap: "round",
3893
+ strokeLinejoin: "round",
3894
+ children: [ jsx("circle", {
3895
+ cx: "12",
3896
+ cy: "12",
3897
+ r: "5"
3898
+ }), jsx("line", {
3899
+ x1: "12",
3900
+ y1: "1",
3901
+ x2: "12",
3902
+ y2: "3"
3903
+ }), jsx("line", {
3904
+ x1: "12",
3905
+ y1: "21",
3906
+ x2: "12",
3907
+ y2: "23"
3908
+ }), jsx("line", {
3909
+ x1: "4.22",
3910
+ y1: "4.22",
3911
+ x2: "5.64",
3912
+ y2: "5.64"
3913
+ }), jsx("line", {
3914
+ x1: "18.36",
3915
+ y1: "18.36",
3916
+ x2: "19.78",
3917
+ y2: "19.78"
3918
+ }), jsx("line", {
3919
+ x1: "1",
3920
+ y1: "12",
3921
+ x2: "3",
3922
+ y2: "12"
3923
+ }), jsx("line", {
3924
+ x1: "21",
3925
+ y1: "12",
3926
+ x2: "23",
3927
+ y2: "12"
3928
+ }), jsx("line", {
3929
+ x1: "4.22",
3930
+ y1: "19.78",
3931
+ x2: "5.64",
3932
+ y2: "18.36"
3933
+ }), jsx("line", {
3934
+ x1: "18.36",
3935
+ y1: "5.64",
3936
+ x2: "19.78",
3937
+ y2: "4.22"
3938
+ }) ]
3939
+ })
3940
+ })
3941
+ }) : null;
3942
+ };
3943
+
3944
+ ThemeToggle.displayName = "ThemeToggle";
3945
+
2719
3946
  /**
2720
3947
  * Theme Applicator
2721
3948
  *
@@ -2728,7 +3955,8 @@ function useThemeTokens() {
2728
3955
  * Theme applicator class for runtime theme application
2729
3956
  *
2730
3957
  * Uses the unified theme system for efficient CSS variable generation and injection.
2731
- */ class ThemeApplicator {
3958
+ */
3959
+ class ThemeApplicator {
2732
3960
  constructor(root = document.documentElement) {
2733
3961
  this.styleId = "atomix-theme-applicator", this.root = root;
2734
3962
  }
@@ -4268,65 +5496,7 @@ class ThemeValidator {
4268
5496
  children: "\n .atomix-theme-comparator {\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n padding: 24px;\n }\n\n .comparator-header {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n align-items: center;\n margin-bottom: 24px;\n padding-bottom: 24px;\n border-bottom: 2px solid #e0e0e0;\n }\n\n .theme-column {\n text-align: center;\n }\n\n .theme-column h3 {\n margin: 0;\n font-size: 20px;\n color: #333;\n }\n\n .version {\n display: inline-block;\n margin-top: 4px;\n padding: 2px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n }\n\n .vs-divider {\n font-weight: bold;\n font-size: 24px;\n color: #666;\n }\n\n .filter-controls {\n display: flex;\n gap: 16px;\n align-items: center;\n margin-bottom: 24px;\n padding: 16px;\n background: #f5f5f5;\n border-radius: 8px;\n flex-wrap: wrap;\n }\n\n .filter-group {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .filter-group label {\n font-size: 14px;\n font-weight: 500;\n color: #666;\n }\n\n .search-input {\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 14px;\n width: 250px;\n }\n\n .search-input:focus {\n outline: none;\n border-color: #2196f3;\n }\n\n .clear-search {\n background: #f44336;\n color: white;\n border: none;\n border-radius: 50%;\n width: 24px;\n height: 24px;\n cursor: pointer;\n font-size: 18px;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .clear-search:hover {\n background: #d32f2f;\n }\n\n .filter-select {\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 14px;\n background: white;\n cursor: pointer;\n }\n\n .clear-filters {\n padding: 8px 16px;\n background: #666;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n }\n\n .clear-filters:hover {\n background: #555;\n }\n\n .search-highlight {\n background: #fff59d;\n padding: 2px 4px;\n border-radius: 2px;\n }\n\n .comparator-stats {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n }\n\n .stat {\n text-align: center;\n padding: 16px;\n background: #f5f5f5;\n border-radius: 8px;\n }\n\n .stat-added {\n border-left: 4px solid #4caf50;\n }\n\n .stat-removed {\n border-left: 4px solid #f44336;\n }\n\n .stat-changed {\n border-left: 4px solid #ff9800;\n }\n\n .stat-filtered {\n border-left: 4px solid #2196f3;\n }\n\n .stat-value {\n display: block;\n font-size: 32px;\n font-weight: bold;\n color: #2196f3;\n }\n\n .stat-label {\n display: block;\n font-size: 12px;\n color: #666;\n margin-top: 4px;\n }\n\n .no-differences {\n text-align: center;\n padding: 48px;\n font-size: 18px;\n color: #4caf50;\n background: #e8f5e9;\n border-radius: 8px;\n }\n\n .differences-list h4 {\n margin: 0 0 16px 0;\n font-size: 18px;\n color: #333;\n }\n\n .difference-item {\n margin-bottom: 16px;\n border: 2px solid;\n border-radius: 8px;\n overflow: hidden;\n transition: all 0.2s ease;\n }\n\n .difference-added {\n border-color: #4caf50;\n }\n\n .difference-removed {\n border-color: #f44336;\n }\n\n .difference-changed {\n border-color: #ff9800;\n }\n\n .difference-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px;\n background: rgba(0, 0, 0, 0.05);\n }\n\n .difference-type {\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: bold;\n color: white;\n text-transform: uppercase;\n }\n\n .difference-path {\n font-family: 'Monaco', 'Menlo', monospace;\n font-size: 13px;\n color: #1976d2;\n flex: 1;\n }\n\n .difference-category {\n font-size: 11px;\n padding: 2px 6px;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 4px;\n color: #666;\n text-transform: uppercase;\n }\n\n .difference-values {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 16px;\n padding: 16px;\n align-items: start;\n }\n\n .value-column {\n min-width: 0;\n }\n\n .value-column.value-empty {\n opacity: 0.5;\n }\n\n .value-label {\n font-size: 12px;\n font-weight: bold;\n color: #666;\n margin-bottom: 8px;\n }\n\n .value-content {\n margin: 0;\n padding: 12px;\n background: rgba(255, 255, 255, 0.7);\n border-radius: 4px;\n font-family: 'Monaco', 'Menlo', monospace;\n font-size: 12px;\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-all;\n }\n\n .value-empty {\n color: #999;\n font-style: italic;\n }\n\n .value-divider {\n font-size: 20px;\n color: #666;\n align-self: center;\n }\n "
4269
5497
  }) ]
4270
5498
  });
4271
- };
4272
-
4273
- /**
4274
- * Theme Comparator Component
4275
- *
4276
- * Compares two themes and highlights differences
4277
- */ var aCallable = aCallable$3, toObject = toObject$2, IndexedObject = indexedObject, lengthOfArrayLike = lengthOfArrayLike$2, $TypeError = TypeError, REDUCE_EMPTY = "Reduce of empty array with no initial value", createMethod = function(IS_RIGHT) {
4278
- return function(that, callbackfn, argumentsLength, memo) {
4279
- var O = toObject(that), self = IndexedObject(O), length = lengthOfArrayLike(O);
4280
- if (aCallable(callbackfn), 0 === length && argumentsLength < 2) throw new $TypeError(REDUCE_EMPTY);
4281
- var index = IS_RIGHT ? length - 1 : 0, i = IS_RIGHT ? -1 : 1;
4282
- if (argumentsLength < 2) for (;;) {
4283
- if (index in self) {
4284
- memo = self[index], index += i;
4285
- break;
4286
- }
4287
- if (index += i, IS_RIGHT ? index < 0 : length <= index) throw new $TypeError(REDUCE_EMPTY);
4288
- }
4289
- for (;IS_RIGHT ? index >= 0 : length > index; index += i) index in self && (memo = callbackfn(memo, self[index], index, O));
4290
- return memo;
4291
- };
4292
- }, arrayReduce = {
4293
- // `Array.prototype.reduce` method
4294
- // https://tc39.es/ecma262/#sec-array.prototype.reduce
4295
- left: createMethod(!1),
4296
- // `Array.prototype.reduceRight` method
4297
- // https://tc39.es/ecma262/#sec-array.prototype.reduceright
4298
- right: createMethod(!0)
4299
- }, fails = fails$9, globalThis$1 = globalThis_1, userAgent = environmentUserAgent, classof = classofRaw$2, userAgentStartsWith = function(string) {
4300
- return userAgent.slice(0, string.length) === string;
4301
- }, environment = userAgentStartsWith("Bun/") ? "BUN" : userAgentStartsWith("Cloudflare-Workers") ? "CLOUDFLARE" : userAgentStartsWith("Deno/") ? "DENO" : userAgentStartsWith("Node.js/") ? "NODE" : globalThis$1.Bun && "string" == typeof Bun.version ? "BUN" : globalThis$1.Deno && "object" == typeof Deno.version ? "DENO" : "process" === classof(globalThis$1.process) ? "NODE" : globalThis$1.window && globalThis$1.document ? "BROWSER" : "REST", $reduce = arrayReduce.left;
4302
-
4303
- // `Array.prototype.reduce` method
4304
- // https://tc39.es/ecma262/#sec-array.prototype.reduce
4305
- _export({
4306
- target: "Array",
4307
- proto: !0,
4308
- forced: !("NODE" === environment) && environmentV8Version > 79 && environmentV8Version < 83 || !function(METHOD_NAME, argument) {
4309
- var method = [][METHOD_NAME];
4310
- return !!method && fails((function() {
4311
- // eslint-disable-next-line no-useless-call -- required for testing
4312
- method.call(null, argument || function() {
4313
- return 1;
4314
- }, 1);
4315
- }));
4316
- }("reduce")
4317
- }, {
4318
- reduce: function(callbackfn /* , initialValue */) {
4319
- var length = arguments.length;
4320
- return $reduce(this, callbackfn, length, length > 1 ? arguments[1] : void 0);
4321
- }
4322
- });
4323
-
4324
- var reduce$3 = getBuiltInPrototypeMethod$3("Array", "reduce"), isPrototypeOf = objectIsPrototypeOf, method = reduce$3, ArrayPrototype = Array.prototype;
4325
-
4326
- const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
4327
- var own = it.reduce;
4328
- return it === ArrayPrototype || isPrototypeOf(ArrayPrototype, it) && own === ArrayPrototype.reduce ? method : own;
4329
- })), DEFAULT_PALETTE = {
5499
+ }, DEFAULT_PALETTE = {
4330
5500
  primary: {
4331
5501
  main: "#7c3aed",
4332
5502
  // Primary-6
@@ -4511,9 +5681,11 @@ const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
4511
5681
  pill: "50rem"
4512
5682
  };
4513
5683
 
4514
- // ============================================================================
4515
- // Default Theme Values
4516
- // ============================================================================
5684
+ /**
5685
+ * Theme Comparator Component
5686
+ *
5687
+ * Compares two themes and highlights differences
5688
+ */
4517
5689
  // ============================================================================
4518
5690
  // Helper Functions
4519
5691
  // ============================================================================
@@ -4523,13 +5695,13 @@ const _reduceInstanceProperty = getDefaultExportFromCjs((function(it) {
4523
5695
  function createPaletteColor(color) {
4524
5696
  return "string" == typeof color ? {
4525
5697
  main: color,
4526
- light: lighten(color),
4527
- dark: darken(color),
5698
+ light: lighten(color, .15),
5699
+ dark: darken(color, .15),
4528
5700
  contrastText: getContrastText(color)
4529
5701
  } : {
4530
5702
  main: color.main || "#000000",
4531
- light: color.light || lighten(color.main || "#000000"),
4532
- dark: color.dark || darken(color.main || "#000000"),
5703
+ light: color.light || lighten(color.main || "#000000", .15),
5704
+ dark: color.dark || darken(color.main || "#000000", .15),
4533
5705
  contrastText: color.contrastText || getContrastText(color.main || "#000000")
4534
5706
  };
4535
5707
  }
@@ -4564,23 +5736,19 @@ function createThemeObject(...options) {
4564
5736
  },
4565
5737
  background: {
4566
5738
  default: mergedOptions.palette?.background?.default || DEFAULT_PALETTE.background.default,
5739
+ paper: mergedOptions.palette?.background?.paper || DEFAULT_PALETTE.background.paper,
4567
5740
  subtle: mergedOptions.palette?.background?.subtle || DEFAULT_PALETTE.background.subtle
4568
5741
  },
4569
5742
  text: {
4570
5743
  primary: mergedOptions.palette?.text?.primary || DEFAULT_PALETTE.text.primary,
4571
5744
  secondary: mergedOptions.palette?.text?.secondary || DEFAULT_PALETTE.text.secondary,
4572
5745
  disabled: mergedOptions.palette?.text?.disabled || DEFAULT_PALETTE.text.disabled
4573
- }
5746
+ },
5747
+ // Spread other palette properties
5748
+ ...mergedOptions.palette
4574
5749
  }, typography = deepMerge({
4575
5750
  ...DEFAULT_TYPOGRAPHY
4576
- }, mergedOptions.typography || {}), spacing = function(spacingInput = 4) {
4577
- // If it's already a function, return it
4578
- return "function" == typeof spacingInput ? spacingInput :
4579
- // If it's a number, create a function that multiplies by that number
4580
- "number" == typeof spacingInput ? (...values) => 0 === values.length ? "0px" : values.map((value => value * spacingInput + "px")).join(" ") :
4581
- // If it's an array, use it as a scale
4582
- 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(" ");
4583
- }(mergedOptions.spacing), breakpoints = function(breakpointsInput) {
5751
+ }, mergedOptions.typography || {}), spacing = createSpacing(mergedOptions.spacing), breakpoints = function(breakpointsInput) {
4584
5752
  const values = {
4585
5753
  xs: 0,
4586
5754
  sm: 576,
@@ -5403,24 +6571,6 @@ const ThemeLiveEditor = ({initialTheme: initialTheme, onChange: onChange, classN
5403
6571
  /**
5404
6572
  * Design Tokens Customizer Component
5405
6573
  */
5406
- /**
5407
- * Theme Adapter
5408
- *
5409
- * Converts between Theme objects and DesignTokens.
5410
- */
5411
- /**
5412
- * Convert DesignTokens to Theme-compatible CSS variables
5413
- *
5414
- * @param tokens - DesignTokens object
5415
- * @returns CSS variables object compatible with Theme.cssVars
5416
- */
5417
- function designTokensToCSSVars(tokens) {
5418
- const cssVars = {};
5419
- return Object.entries(tokens).forEach((([key, value]) => {
5420
- void 0 !== value && (cssVars[`--atomix-${key}`] = String(value));
5421
- })), cssVars;
5422
- }
5423
-
5424
6574
  /**
5425
6575
  * Map SCSS tokens to CSS custom properties
5426
6576
  *
@@ -5428,7 +6578,8 @@ function designTokensToCSSVars(tokens) {
5428
6578
  * const tokens = { '$primary-color': '#7AFFD7', '$spacing-md': '16px' }
5429
6579
  * const vars = mapSCSSTokensToCSSVars(tokens)
5430
6580
  * // Returns: { '--primary-color': '#7AFFD7', '--spacing-md': '16px' }
5431
- */ function mapSCSSTokensToCSSVars(tokens, options = {}) {
6581
+ */
6582
+ function mapSCSSTokensToCSSVars(tokens, options = {}) {
5432
6583
  const vars = {}, {prefix: prefix = "atomix", separator: separator = "-"} = options;
5433
6584
  return Object.entries(tokens).forEach((([key, value]) => {
5434
6585
  // Remove $ prefix from SCSS variables
@@ -5541,6 +6692,335 @@ function designTokensToCSSVars(tokens) {
5541
6692
  // Check if keys look like DesignTokens (kebab-case, no nesting)
5542
6693
  }
5543
6694
 
6695
+ /**
6696
+ * Performance monitor class
6697
+ */ class PerformanceMonitor {
6698
+ /**
6699
+ * Create a new performance monitor
6700
+ *
6701
+ * @param config Configuration options
6702
+ */
6703
+ constructor(config) {
6704
+ this.frameCount = 0, this.lastSampleTime = 0, this.lastFpsUpdate = 0, this.frameTimes = [],
6705
+ this.animationFrameId = null, this.isActive = !1, this.startTime = 0, this.config = {
6706
+ fpsTarget: config?.fpsTarget ?? 60,
6707
+ sampleInterval: config?.sampleInterval ?? 500,
6708
+ onUpdate: config?.onUpdate ?? (() => {}),
6709
+ onDegraded: config?.onDegraded ?? (() => {}),
6710
+ enableMemoryMonitoring: config?.enableMemoryMonitoring ?? ("undefined" != typeof window && window.performance && window.performance.memory)
6711
+ };
6712
+ }
6713
+ /**
6714
+ * Start monitoring performance
6715
+ */ start() {
6716
+ this.isActive || (this.isActive = !0, this.frameCount = 0, this.lastSampleTime = performance.now(),
6717
+ this.lastFpsUpdate = this.lastSampleTime, this.frameTimes = [], this.startTime = this.lastSampleTime,
6718
+ this.animationFrameId = requestAnimationFrame(this.onFrame.bind(this)));
6719
+ }
6720
+ /**
6721
+ * Stop monitoring performance
6722
+ */ stop() {
6723
+ this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null),
6724
+ this.isActive = !1;
6725
+ }
6726
+ /**
6727
+ * Get current performance metrics
6728
+ */ getMetrics() {
6729
+ var _context;
6730
+ const now = performance.now(), elapsed = now - this.lastFpsUpdate, fps = elapsed > 0 ? Math.round(this.frameCount / elapsed * 1e3) : 0, avgFrameTime = this.frameTimes.length > 0 ? _reduceInstanceProperty(_context = this.frameTimes).call(_context, ((a, b) => a + b), 0) / this.frameTimes.length : 0, peakFrameTime = this.frameTimes.length > 0 ? Math.max(...this.frameTimes) : 0;
6731
+ // Get memory stats if available
6732
+ let memory;
6733
+ if (this.config.enableMemoryMonitoring) {
6734
+ const perf = window.performance;
6735
+ perf && perf.memory && (memory = {
6736
+ usedJSHeapSize: perf.memory.usedJSHeapSize,
6737
+ totalJSHeapSize: perf.memory.totalJSHeapSize,
6738
+ jsHeapSizeLimit: perf.memory.jsHeapSizeLimit
6739
+ });
6740
+ }
6741
+ return {
6742
+ fps: fps,
6743
+ frameTime: avgFrameTime,
6744
+ peakFrameTime: peakFrameTime,
6745
+ memory: memory,
6746
+ timestamp: now,
6747
+ isDegraded: fps < .7 * this.config.fpsTarget
6748
+ };
6749
+ }
6750
+ /**
6751
+ * Get the current FPS
6752
+ */ getFps() {
6753
+ return this.getMetrics().fps;
6754
+ }
6755
+ /**
6756
+ * Check if performance is degraded
6757
+ */ isPerformanceDegraded() {
6758
+ return this.getMetrics().isDegraded;
6759
+ }
6760
+ /**
6761
+ * Private method called on each animation frame
6762
+ */ onFrame(timestamp) {
6763
+ if (!this.isActive) return;
6764
+ // Calculate frame time
6765
+ const frameTime = timestamp - this.lastSampleTime;
6766
+ // Check if we need to update metrics
6767
+ if (this.frameTimes.push(frameTime),
6768
+ // Keep only the last 60 frame times for averaging
6769
+ this.frameTimes.length > 60 && this.frameTimes.shift(), this.frameCount++, this.lastSampleTime = timestamp,
6770
+ timestamp - this.lastFpsUpdate >= this.config.sampleInterval) {
6771
+ const metrics = this.getMetrics();
6772
+ // Call update callback
6773
+ this.config.onUpdate(metrics),
6774
+ // Check for degradation
6775
+ metrics.isDegraded && this.config.onDegraded(metrics),
6776
+ // Reset counters
6777
+ this.frameCount = 0, this.lastFpsUpdate = timestamp;
6778
+ }
6779
+ this.animationFrameId = requestAnimationFrame(this.onFrame.bind(this));
6780
+ }
6781
+ /**
6782
+ * Run a performance test for a specific function
6783
+ *
6784
+ * @param fn Function to test
6785
+ * @param iterations Number of iterations (default: 100)
6786
+ * @returns Average execution time in ms
6787
+ */ async testFunctionPerformance(fn, iterations = 100) {
6788
+ const times = [];
6789
+ for (let i = 0; i < iterations; i++) {
6790
+ const start = performance.now();
6791
+ fn();
6792
+ const end = performance.now();
6793
+ times.push(end - start);
6794
+ }
6795
+ return _reduceInstanceProperty(times).call(times, ((a, b) => a + b), 0) / times.length;
6796
+ }
6797
+ }
6798
+
6799
+ /**
6800
+ * Create a performance monitor instance
6801
+ *
6802
+ * @param config Configuration options
6803
+ * @returns PerformanceMonitor instance
6804
+ *
6805
+ * @example
6806
+ * ```typescript
6807
+ * import { createPerformanceMonitor } from '@shohojdhara/atomix/theme';
6808
+ *
6809
+ * const monitor = createPerformanceMonitor({
6810
+ * fpsTarget: 60,
6811
+ * onUpdate: (metrics) => console.log('FPS:', metrics.fps),
6812
+ * onDegraded: (metrics) => console.warn('Performance degraded!', metrics),
6813
+ * });
6814
+ *
6815
+ * monitor.start();
6816
+ *
6817
+ * // Later...
6818
+ * monitor.stop();
6819
+ * ```
6820
+ */ function createPerformanceMonitor(config) {
6821
+ return new PerformanceMonitor(config);
6822
+ }
6823
+
6824
+ /**
6825
+ * Hook for React components to monitor performance
6826
+ *
6827
+ * @param config Configuration options
6828
+ * @returns Performance metrics and monitor controls
6829
+ *
6830
+ * @example
6831
+ * ```typescript
6832
+ * import { usePerformanceMonitor } from '@shohojdhara/atomix/theme';
6833
+ *
6834
+ * function MyComponent() {
6835
+ * const { metrics, start, stop } = usePerformanceMonitor({ fpsTarget: 60 });
6836
+ *
6837
+ * useEffect(() => {
6838
+ * start();
6839
+ * return () => stop();
6840
+ * }, []);
6841
+ *
6842
+ * return <div>FPS: {metrics.fps}</div>;
6843
+ * }
6844
+ * ```
6845
+ */ function usePerformanceMonitor(config) {
6846
+ const [monitor] = React.useState((() => createPerformanceMonitor(config))), [metrics, setMetrics] = React.useState((() => "undefined" != typeof window ? monitor.getMetrics() : {
6847
+ fps: 0,
6848
+ frameTime: 0,
6849
+ peakFrameTime: 0,
6850
+ timestamp: 0,
6851
+ isDegraded: !1
6852
+ })), start = React.useCallback((() => {
6853
+ "undefined" != typeof window && monitor.start();
6854
+ }), [ monitor ]), stop = React.useCallback((() => {
6855
+ "undefined" != typeof window && monitor.stop();
6856
+ }), [ monitor ]);
6857
+ return React.useEffect((() => {
6858
+ if ("undefined" == typeof window) return;
6859
+ // Update metrics when monitor callbacks fire
6860
+ const originalOnUpdate = config?.onUpdate;
6861
+ return monitor.config.onUpdate = newMetrics => {
6862
+ setMetrics(newMetrics), originalOnUpdate?.(newMetrics);
6863
+ }, () => {
6864
+ monitor.stop();
6865
+ };
6866
+ }), [ monitor, config?.onUpdate ]), {
6867
+ metrics: metrics,
6868
+ start: start,
6869
+ stop: stop
6870
+ };
6871
+ }
6872
+
6873
+ /**
6874
+ * Responsive Utility for Atomix Theme System
6875
+ *
6876
+ * Provides responsive breakpoint detection and device-aware parameter scaling
6877
+ * based on configuration from the advanced optimization features.
6878
+ */
6879
+ /**
6880
+ * Responsive utility class
6881
+ */ class ResponsiveUtil {
6882
+ constructor(config) {
6883
+ this.currentDevice = "desktop", // Default
6884
+ this.resizeHandler = null, this.observer = null, this.config = config, this.currentDevice = this.getCurrentDeviceType(),
6885
+ // Set up resize listener
6886
+ this.setupResizeListener();
6887
+ }
6888
+ /**
6889
+ * Get the current device type based on viewport width
6890
+ */ getCurrentDeviceType() {
6891
+ if ("undefined" == typeof window) return "desktop";
6892
+ // SSR fallback
6893
+ const width = window.innerWidth;
6894
+ // Parse breakpoint values to numbers
6895
+ this.parsePxValue(this.config.breakpoints.mobile);
6896
+ const tabletWidth = this.parsePxValue(this.config.breakpoints.tablet), desktopWidth = this.parsePxValue(this.config.breakpoints.desktop), wideWidth = this.parsePxValue(this.config.breakpoints.wide);
6897
+ return width < tabletWidth ? "mobile" : width < desktopWidth ? "tablet" : width < wideWidth ? "desktop" : "wide";
6898
+ }
6899
+ /**
6900
+ * Get the scaling factor for the current device
6901
+ */ getCurrentScalingFactor() {
6902
+ // 'wide' devices use the same scaling as 'desktop'
6903
+ const scalingKey = "wide" === this.currentDevice ? "desktop" : this.currentDevice;
6904
+ return this.config.deviceScaling[scalingKey] || 1;
6905
+ }
6906
+ /**
6907
+ * Scale a value based on the current device's scaling factor
6908
+ */ scaleValue(value) {
6909
+ return value * this.getCurrentScalingFactor();
6910
+ }
6911
+ /**
6912
+ * Check if the current device matches a specific type
6913
+ */ isDevice(device) {
6914
+ return this.currentDevice === device;
6915
+ }
6916
+ /**
6917
+ * Check if the current device is mobile or smaller
6918
+ */ isMobileOrSmaller() {
6919
+ return "mobile" === this.currentDevice;
6920
+ }
6921
+ /**
6922
+ * Check if the current device is tablet or smaller
6923
+ */ isTabletOrSmaller() {
6924
+ return "mobile" === this.currentDevice || "tablet" === this.currentDevice;
6925
+ }
6926
+ /**
6927
+ * Check if the current device is desktop or larger
6928
+ */ isDesktopOrLarger() {
6929
+ return "desktop" === this.currentDevice || "wide" === this.currentDevice;
6930
+ }
6931
+ /**
6932
+ * Update the responsive configuration
6933
+ */ updateConfig(config) {
6934
+ this.config = config, this.currentDevice = this.getCurrentDeviceType();
6935
+ }
6936
+ /**
6937
+ * Destroy the responsive utility and clean up listeners
6938
+ */ destroy() {
6939
+ this.resizeHandler && (window.removeEventListener("resize", this.resizeHandler),
6940
+ this.resizeHandler = null), this.observer && (this.observer.disconnect(), this.observer = null);
6941
+ }
6942
+ /**
6943
+ * Parse a CSS value to pixels
6944
+ */ parsePxValue(value) {
6945
+ return value.endsWith("px") ? parseFloat(value.slice(0, -2)) :
6946
+ // For other units, we'll use a rough conversion assuming 16px base
6947
+ value.endsWith("rem") ? 16 * parseFloat(value.slice(0, -3)) : value.endsWith("em") ? 16 * parseFloat(value.slice(0, -2)) : parseFloat(value) || 0;
6948
+ }
6949
+ /**
6950
+ * Set up the resize listener
6951
+ */ setupResizeListener() {
6952
+ if ("undefined" == typeof window) return;
6953
+ // Throttled resize handler
6954
+ let resizeTimeout = null;
6955
+ const handleResize = () => {
6956
+ resizeTimeout && window.clearTimeout(resizeTimeout), resizeTimeout = window.setTimeout((() => {
6957
+ const newDeviceType = this.getCurrentDeviceType();
6958
+ newDeviceType !== this.currentDevice && (this.currentDevice = newDeviceType);
6959
+ }), 150);
6960
+ } // Throttle to 150ms
6961
+ ;
6962
+ this.resizeHandler = handleResize, window.addEventListener("resize", handleResize),
6963
+ // Also observe the document body for size changes
6964
+ "undefined" != typeof ResizeObserver && (this.observer = new ResizeObserver(handleResize),
6965
+ this.observer.observe(document.body));
6966
+ }
6967
+ }
6968
+
6969
+ /**
6970
+ * Create a responsive utility instance
6971
+ *
6972
+ * @param config Responsive configuration
6973
+ * @returns ResponsiveUtil instance
6974
+ */ function createResponsiveUtil(config) {
6975
+ return new ResponsiveUtil(config);
6976
+ }
6977
+
6978
+ /**
6979
+ * Hook for React components to use responsive features
6980
+ *
6981
+ * @param config Responsive configuration
6982
+ * @returns Current device type and utility functions
6983
+ */ function useResponsive(config) {
6984
+ const [util] = React.useState((() => createResponsiveUtil(config))), [deviceType, setDeviceType] = React.useState((() => "undefined" != typeof window ? util.getCurrentDeviceType() : "desktop"));
6985
+ return React.useEffect((() => {
6986
+ if ("undefined" == typeof window) return;
6987
+ const handleResize = () => {
6988
+ const newDeviceType = util.getCurrentDeviceType();
6989
+ newDeviceType !== deviceType && setDeviceType(newDeviceType);
6990
+ };
6991
+ // Update device type on mount
6992
+ return setDeviceType(util.getCurrentDeviceType()),
6993
+ // Listen for resize events
6994
+ window.addEventListener("resize", handleResize), () => {
6995
+ window.removeEventListener("resize", handleResize), util.destroy();
6996
+ };
6997
+ }), [ util, deviceType ]), "undefined" == typeof window ? {
6998
+ deviceType: "desktop",
6999
+ isMobile: !1,
7000
+ isTablet: !1,
7001
+ isDesktop: !0,
7002
+ isWide: !1,
7003
+ scaleValue: value => value,
7004
+ getCurrentDeviceType: () => "desktop",
7005
+ getCurrentScalingFactor: () => 1,
7006
+ isMobileOrSmaller: () => !1,
7007
+ isTabletOrSmaller: () => !0,
7008
+ isDesktopOrLarger: () => !0
7009
+ } : {
7010
+ deviceType: deviceType,
7011
+ isMobile: "mobile" === deviceType,
7012
+ isTablet: "tablet" === deviceType,
7013
+ isDesktop: "desktop" === deviceType,
7014
+ isWide: "wide" === deviceType,
7015
+ scaleValue: value => util.scaleValue(value),
7016
+ getCurrentDeviceType: () => util.getCurrentDeviceType(),
7017
+ getCurrentScalingFactor: () => util.getCurrentScalingFactor(),
7018
+ isMobileOrSmaller: () => util.isMobileOrSmaller(),
7019
+ isTabletOrSmaller: () => util.isTabletOrSmaller(),
7020
+ isDesktopOrLarger: () => util.isDesktopOrLarger()
7021
+ };
7022
+ }
7023
+
5544
7024
  /**
5545
7025
  * RTL (Right-to-Left) Support Utilities
5546
7026
  *
@@ -5726,6 +7206,32 @@ class RTLManager {
5726
7206
  }
5727
7207
  }
5728
7208
 
7209
+ /**
7210
+ * Create RTL manager instance
7211
+ */ function createRTLManager(config) {
7212
+ return new RTLManager(config);
7213
+ }
7214
+
7215
+ /**
7216
+ * Check if locale is RTL
7217
+ */ function isRTLLocale(locale) {
7218
+ return RTL_LOCALES.has(locale.toLowerCase());
7219
+ }
7220
+
7221
+ /**
7222
+ * Get direction from locale
7223
+ */ function getDirectionFromLocale(locale) {
7224
+ return isRTLLocale(locale) ? "rtl" : "ltr";
7225
+ }
7226
+
7227
+ /**
7228
+ * RTL-aware CSS helper
7229
+ *
7230
+ * Returns appropriate CSS based on direction
7231
+ */ function rtlCSS(ltrCSS, rtlCSS, direction = "ltr") {
7232
+ return "rtl" === direction ? rtlCSS : ltrCSS;
7233
+ }
7234
+
5729
7235
  /**
5730
7236
  * Theme System Exports
5731
7237
  *
@@ -5748,8 +7254,6 @@ class RTLManager {
5748
7254
  // ============================================================================
5749
7255
  // Core Theme Functions
5750
7256
  // ============================================================================
5751
- // Create theme CSS from DesignTokens
5752
- // File saving utilities removed to prevent bundling Node.js modules in browser
5753
7257
  /**
5754
7258
  * Inject theme CSS into DOM
5755
7259
  */ function injectTheme(css, id = "atomix-theme") {
@@ -5762,5 +7266,5 @@ class RTLManager {
5762
7266
  removeCSS(id);
5763
7267
  }
5764
7268
 
5765
- export { DesignTokensCustomizer, RTLManager, ThemeApplicator, ThemeComparator, ThemeContext, ThemeErrorBoundary, ThemeInspector, ThemeLiveEditor, ThemePreview, ThemeProvider, ThemeValidator, applyCSSVariables, applyComponentTheme, applyTheme, camelToKebab, clearThemes, createTheme, createThemeRegistry, createTokens, cssVarsToStyle, deepMerge, defaultTokens, designTokensToCSSVars, extendTheme, extractComponentName, generateCSSVariableName, generateCSSVariables$1 as generateCSSVariables, generateCSSVariablesForSelector, generateClassName, generateComponentCSSVars, getAllThemes, getCSSVariable, getComponentThemeValue, getTheme, getThemeApplicator, getThemeCount, getThemeIds, hasTheme, injectCSS$1 as injectCSS, injectTheme, isCSSInjected, isDesignTokens, isValidCSSVariableName, mapSCSSTokensToCSSVars, mergeCSSVars, mergeTheme, normalizeThemeTokens, registerTheme, removeCSS, removeCSSVariables, removeTheme, themePropertyToCSSVar, unregisterTheme, useComponentTheme, useHistory, useTheme, useThemeTokens };
7269
+ export { DesignTokensCustomizer, RTLManager, ThemeApplicator, ThemeComparator, ThemeContext, ThemeErrorBoundary, ThemeInspector, ThemeLiveEditor, ThemePreview, ThemeProvider, ThemeToggle, ThemeValidator, alpha, applyCSSVariables, applyComponentTheme, applyTheme, camelToKebab, clearThemePreference, clearThemes, configToTokens, createPerformanceMonitor, createRTLManager, createResponsiveUtil, createSpacing, createTheme, createThemeRegistry, createTokens, cssVarsToStyle, darken, deepMerge, defaultTokens, designTokensToCSSVars, emphasize, extendTheme, extractComponentName, generateCSSVariableName, generateCSSVariables$1 as generateCSSVariables, generateCSSVariablesForSelector, generateClassName, generateComponentCSSVars, getAllThemes, getCSSVariable, getComponentThemeValue, getContrastRatio, getContrastText, getCurrentTheme, getDirectionFromLocale, getLuminance, getSystemTheme, getTheme, getThemeApplicator, getThemeCount, getThemeIds, hasTheme, hexToRgb$1 as hexToRgb, initializeTheme, injectCSS$1 as injectCSS, injectTheme, isAccessible, isCSSInjected, isDesignTokens, isRTLLocale, isValidCSSVariableName, lighten, listenToSystemTheme, mapSCSSTokensToCSSVars, mergeCSSVars, mergeTheme, mergeTokens, normalizeThemeTokens, omitTokens, overrideTokens, persistTheme, pickTokens, registerTheme, removeCSS, removeCSSVariables, removeTheme, rgbToHex, rtlCSS, switchTheme, themePropertyToCSSVar, toggleTheme, unregisterTheme, useComponentTheme, useHistory, usePerformanceMonitor, useResponsive, useTheme, useThemeSwitcher, useThemeTokens };
5766
7270
  //# sourceMappingURL=theme.js.map