@shohojdhara/atomix 0.3.15 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-tools/index.d.ts +31 -30
- package/build-tools/package.json +4 -21
- package/dist/atomix.css +20924 -2611
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +76 -2
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/index.d.ts +31 -30
- package/dist/build-tools/package.json +4 -21
- package/dist/charts.js.map +1 -1
- package/dist/core.js.map +1 -1
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +144 -18
- package/dist/index.esm.js +110 -55
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +110 -55
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.js.map +1 -1
- package/dist/theme.d.ts +9 -9
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.stories.tsx +32 -23
- package/src/components/Accordion/Accordion.test.tsx +70 -50
- package/src/components/Accordion/Accordion.tsx +99 -94
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +1 -1
- package/src/components/AtomixGlass/GlassFilter.tsx +9 -16
- package/src/components/AtomixGlass/glass-utils.ts +4 -3
- package/src/components/AtomixGlass/shader-utils.ts +128 -52
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +1 -1
- package/src/components/Avatar/Avatar.stories.tsx +45 -62
- package/src/components/Avatar/Avatar.tsx +58 -56
- package/src/components/Badge/Badge.stories.tsx +20 -9
- package/src/components/Badge/Badge.test.tsx +41 -41
- package/src/components/Badge/Badge.tsx +64 -62
- package/src/components/Block/Block.stories.tsx +14 -4
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +9 -8
- package/src/components/Breadcrumb/Breadcrumb.tsx +62 -60
- package/src/components/Button/Button.stories.tsx +13 -22
- package/src/components/Button/Button.test.tsx +97 -81
- package/src/components/Button/Button.tsx +46 -14
- package/src/components/Button/ButtonGroup.stories.tsx +37 -32
- package/src/components/Button/ButtonGroup.tsx +4 -15
- package/src/components/Callout/Callout.stories.tsx +109 -16
- package/src/components/Card/Card.stories.tsx +67 -36
- package/src/components/Card/Card.tsx +30 -14
- package/src/components/Chart/AreaChart.tsx +1 -1
- package/src/components/Chart/CandlestickChart.tsx +23 -16
- package/src/components/Chart/Chart.stories.tsx +4 -9
- package/src/components/Chart/Chart.tsx +40 -44
- package/src/components/Chart/ChartRenderer.tsx +39 -12
- package/src/components/Chart/ChartToolbar.tsx +21 -5
- package/src/components/Chart/DonutChart.tsx +1 -1
- package/src/components/Chart/FunnelChart.tsx +4 -1
- package/src/components/Chart/GaugeChart.tsx +3 -1
- package/src/components/Chart/HeatmapChart.tsx +50 -37
- package/src/components/Chart/LineChart.tsx +3 -2
- package/src/components/Chart/MultiAxisChart.tsx +24 -16
- package/src/components/Chart/RadarChart.tsx +19 -17
- package/src/components/Chart/ScatterChart.tsx +29 -21
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +6 -2
- package/src/components/ColorModeToggle/ColorModeToggle.tsx +15 -3
- package/src/components/Countdown/Countdown.stories.tsx +7 -7
- package/src/components/DataTable/DataTable.stories.tsx +43 -38
- package/src/components/DataTable/DataTable.test.tsx +26 -148
- package/src/components/DataTable/DataTable.tsx +485 -456
- package/src/components/DatePicker/DatePicker.stories.tsx +32 -47
- package/src/components/DatePicker/DatePicker.tsx +31 -26
- package/src/components/Dropdown/Dropdown.stories.tsx +2 -5
- package/src/components/Dropdown/Dropdown.tsx +313 -299
- package/src/components/EdgePanel/EdgePanel.stories.tsx +6 -19
- package/src/components/EdgePanel/EdgePanel.tsx +1 -3
- package/src/components/Footer/Footer.stories.tsx +21 -16
- package/src/components/Footer/Footer.tsx +130 -128
- package/src/components/Footer/FooterLink.tsx +2 -2
- package/src/components/Form/Checkbox.test.tsx +49 -49
- package/src/components/Form/Checkbox.tsx +108 -100
- package/src/components/Form/Form.stories.tsx +2 -10
- package/src/components/Form/Input.stories.tsx +22 -39
- package/src/components/Form/Input.test.tsx +38 -44
- package/src/components/Form/Radio.stories.tsx +6 -12
- package/src/components/Form/Radio.tsx +68 -66
- package/src/components/Form/Select.tsx +184 -182
- package/src/components/Form/Textarea.test.tsx +27 -32
- package/src/components/Hero/Hero.stories.tsx +56 -23
- package/src/components/Hero/Hero.tsx +201 -55
- package/src/components/Icon/index.ts +7 -1
- package/src/components/List/List.tsx +19 -23
- package/src/components/Modal/Modal.stories.tsx +2 -1
- package/src/components/Modal/Modal.tsx +130 -127
- package/src/components/Navigation/Menu/MegaMenu.tsx +70 -70
- package/src/components/Navigation/Nav/NavDropdown.tsx +1 -5
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +128 -28
- package/src/components/Navigation/SideMenu/SideMenu.tsx +5 -7
- package/src/components/Navigation/SideMenu/SideMenuItem.tsx +4 -5
- package/src/components/Pagination/Pagination.stories.tsx +7 -4
- package/src/components/Pagination/Pagination.tsx +199 -202
- package/src/components/PhotoViewer/PhotoViewer.tsx +4 -1
- package/src/components/Popover/Popover.stories.tsx +99 -192
- package/src/components/Popover/Popover.tsx +41 -37
- package/src/components/Progress/Progress.stories.tsx +35 -44
- package/src/components/River/River.stories.tsx +2 -1
- package/src/components/SectionIntro/SectionIntro.stories.tsx +71 -71
- package/src/components/Slider/Slider.stories.tsx +12 -4
- package/src/components/Spinner/Spinner.stories.tsx +3 -1
- package/src/components/Spinner/Spinner.test.tsx +23 -23
- package/src/components/Spinner/Spinner.tsx +43 -46
- package/src/components/Steps/Steps.stories.tsx +8 -6
- package/src/components/Tabs/Tabs.stories.tsx +12 -9
- package/src/components/Tabs/Tabs.tsx +74 -72
- package/src/components/Toggle/Toggle.stories.tsx +27 -13
- package/src/components/Toggle/Toggle.test.tsx +65 -70
- package/src/components/Toggle/Toggle.tsx +4 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +24 -20
- package/src/components/Tooltip/Tooltip.tsx +104 -106
- package/src/components/Upload/Upload.stories.tsx +129 -127
- package/src/components/Upload/Upload.tsx +287 -283
- package/src/components/VideoPlayer/VideoPlayer.tsx +6 -1
- package/src/components/index.ts +13 -2
- package/src/layouts/Grid/Grid.stories.tsx +9 -3
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +5 -1
- package/src/lib/__tests__/theme-tools.test.ts +32 -6
- package/src/lib/composables/shared-mouse-tracker.ts +13 -14
- package/src/lib/composables/useAtomixGlass.ts +106 -49
- package/src/lib/composables/useChartExport.ts +1 -1
- package/src/lib/composables/useDataTable.ts +29 -17
- package/src/lib/composables/useHero.ts +58 -14
- package/src/lib/composables/useHeroBackgroundSlider.ts +2 -9
- package/src/lib/composables/useInput.ts +10 -8
- package/src/lib/composables/useSideMenu.ts +6 -5
- package/src/lib/composables/useTooltip.ts +1 -2
- package/src/lib/composables/useVideoPlayer.ts +44 -35
- package/src/lib/config/index.ts +154 -154
- package/src/lib/constants/cssVariables.ts +29 -29
- package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +2 -6
- package/src/lib/hooks/index.ts +1 -1
- package/src/lib/hooks/useComponentCustomization.ts +11 -17
- package/src/lib/hooks/usePerformanceMonitor.ts +6 -7
- package/src/lib/patterns/__tests__/slots.test.ts +1 -1
- package/src/lib/patterns/index.ts +1 -1
- package/src/lib/patterns/slots.tsx +8 -13
- package/src/lib/storybook/InteractiveDemo.tsx +13 -18
- package/src/lib/storybook/PreviewContainer.tsx +1 -1
- package/src/lib/storybook/VariantsGrid.tsx +3 -7
- package/src/lib/storybook/index.ts +1 -1
- package/src/lib/theme/adapters/cssVariableMapper.ts +47 -74
- package/src/lib/theme/adapters/index.ts +3 -9
- package/src/lib/theme/adapters/themeAdapter.ts +41 -26
- package/src/lib/theme/config/index.ts +1 -1
- package/src/lib/theme/config/types.ts +2 -2
- package/src/lib/theme/config/validator.ts +10 -5
- package/src/lib/theme/constants/constants.ts +2 -2
- package/src/lib/theme/constants/index.ts +1 -2
- package/src/lib/theme/core/__tests__/createTheme.test.ts +20 -22
- package/src/lib/theme/core/composeTheme.ts +32 -26
- package/src/lib/theme/core/createTheme.ts +1 -1
- package/src/lib/theme/core/createThemeObject.ts +308 -301
- package/src/lib/theme/core/index.ts +3 -3
- package/src/lib/theme/devtools/CLI.ts +106 -104
- package/src/lib/theme/devtools/Comparator.tsx +50 -32
- package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +50 -48
- package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +257 -63
- package/src/lib/theme/devtools/Inspector.tsx +75 -60
- package/src/lib/theme/devtools/LiveEditor.tsx +97 -76
- package/src/lib/theme/devtools/Preview.tsx +150 -106
- package/src/lib/theme/devtools/ThemeValidator.ts +29 -21
- package/src/lib/theme/devtools/index.ts +3 -9
- package/src/lib/theme/devtools/useHistory.ts +23 -21
- package/src/lib/theme/errors/errors.ts +12 -11
- package/src/lib/theme/errors/index.ts +2 -7
- package/src/lib/theme/generators/generateCSS.ts +9 -13
- package/src/lib/theme/generators/generateCSSNested.ts +1 -6
- package/src/lib/theme/generators/generateCSSVariables.ts +673 -630
- package/src/lib/theme/generators/index.ts +1 -4
- package/src/lib/theme/i18n/index.ts +1 -1
- package/src/lib/theme/i18n/rtl.ts +13 -13
- package/src/lib/theme/index.ts +7 -16
- package/src/lib/theme/runtime/ThemeApplicator.ts +4 -4
- package/src/lib/theme/runtime/ThemeContext.tsx +1 -1
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +19 -23
- package/src/lib/theme/runtime/ThemeProvider.tsx +230 -239
- package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +1 -1
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +24 -29
- package/src/lib/theme/runtime/index.ts +2 -5
- package/src/lib/theme/runtime/useTheme.ts +18 -18
- package/src/lib/theme/runtime/useThemeTokens.ts +22 -22
- package/src/lib/theme/test/testTheme.ts +15 -16
- package/src/lib/theme/tokens/index.ts +2 -7
- package/src/lib/theme/tokens/tokens.ts +25 -24
- package/src/lib/theme/types.ts +428 -411
- package/src/lib/theme/utils/__tests__/themeValidation.test.ts +3 -3
- package/src/lib/theme/utils/componentTheming.ts +18 -18
- package/src/lib/theme/utils/domUtils.ts +277 -289
- package/src/lib/theme/utils/index.ts +1 -2
- package/src/lib/theme/utils/injectCSS.ts +10 -14
- package/src/lib/theme/utils/naming.ts +20 -16
- package/src/lib/theme/utils/themeHelpers.ts +10 -12
- package/src/lib/theme/utils/themeUtils.ts +85 -86
- package/src/lib/theme/utils/themeValidation.ts +82 -33
- package/src/lib/theme-tools.ts +8 -6
- package/src/lib/types/components.ts +172 -71
- package/src/lib/types/partProps.ts +1 -1
- package/src/lib/utils/__tests__/csv.test.ts +1 -1
- package/src/lib/utils/componentUtils.ts +8 -12
- package/src/lib/utils/csv.ts +3 -1
- package/src/lib/utils/dataTableExport.ts +1 -5
- package/src/lib/utils/fontPreloader.ts +10 -19
- package/src/lib/utils/icons.ts +4 -1
- package/src/lib/utils/index.ts +2 -6
- package/src/lib/utils/memoryMonitor.ts +10 -8
- package/src/lib/utils/themeNaming.ts +2 -2
- package/src/styles/01-settings/_index.scss +0 -1
- package/src/styles/01-settings/_settings.colors.scss +8 -8
- package/src/styles/01-settings/_settings.design-tokens.scss +61 -50
- package/src/styles/01-settings/_settings.navbar.scss +1 -1
- package/src/styles/01-settings/_settings.spacing.scss +3 -4
- package/src/styles/01-settings/_settings.tooltip.scss +1 -1
- package/src/styles/01-settings/_settings.typography.scss +1 -1
- package/src/styles/02-tools/_tools.button.scss +51 -21
- package/src/styles/02-tools/_tools.utility-api.scss +30 -18
- package/src/styles/03-generic/_generic.root.scss +4 -3
- package/src/styles/06-components/_components.atomix-glass.scss +13 -9
- package/src/styles/06-components/_components.button.scss +16 -4
- package/src/styles/06-components/_components.callout.scss +27 -21
- package/src/styles/06-components/_components.card.scss +5 -14
- package/src/styles/06-components/_components.chart.scss +22 -19
- package/src/styles/06-components/_components.checkbox.scss +3 -1
- package/src/styles/06-components/_components.color-mode-toggle.scss +3 -1
- package/src/styles/06-components/_components.edge-panel.scss +9 -2
- package/src/styles/06-components/_components.footer.scss +1 -1
- package/src/styles/06-components/_components.side-menu.scss +5 -5
- package/src/styles/06-components/_components.toggle.scss +18 -0
- package/src/styles/06-components/_index.scss +1 -1
- package/src/styles/06-components/old.chart.styles.scss +0 -2
- package/src/styles/99-utilities/_utilities.border.scss +69 -27
- package/src/styles/99-utilities/_utilities.display.scss +1 -1
- package/src/styles/99-utilities/_utilities.opacity.scss +10 -0
- package/src/styles/99-utilities/_utilities.position.scss +16 -9
- package/src/styles/99-utilities/_utilities.scss +1 -1
- package/src/styles/99-utilities/_utilities.sizes.scss +47 -18
- package/src/styles/99-utilities/_utilities.spacing.scss +118 -66
- package/src/styles/99-utilities/_utilities.text-gradient.scss +30 -30
- package/src/styles/99-utilities/_utilities.text.scss +67 -46
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme Manager Utility Functions
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Helper functions for theme operations including CSS loading, DOM manipulation,
|
|
5
5
|
* and theme validation.
|
|
6
6
|
*/
|
|
@@ -13,40 +13,40 @@ import { ThemeError, ThemeErrorCode } from '../errors/errors';
|
|
|
13
13
|
* Check if code is running in a browser environment
|
|
14
14
|
*/
|
|
15
15
|
export const isBrowser = (): boolean => {
|
|
16
|
-
|
|
16
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Check if code is running on the server (SSR)
|
|
21
21
|
*/
|
|
22
22
|
export const isServer = (): boolean => {
|
|
23
|
-
|
|
23
|
+
return !isBrowser();
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Generate a unique ID for theme link elements
|
|
28
28
|
*/
|
|
29
29
|
export const getThemeLinkId = (themeName: string): string => {
|
|
30
|
-
|
|
30
|
+
return `${THEME_LINK_ID_PREFIX}${themeName}`;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Sanitize path to prevent path injection attacks
|
|
35
|
-
*
|
|
35
|
+
*
|
|
36
36
|
* @param path - Path to sanitize
|
|
37
37
|
* @returns Sanitized path
|
|
38
38
|
*/
|
|
39
39
|
const sanitizePath = (path: string): string => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
return path
|
|
41
|
+
.replace(/[<>"']/g, '') // Remove dangerous characters
|
|
42
|
+
.replace(/\.\./g, '') // Remove path traversal attempts
|
|
43
|
+
.replace(/\/+/g, '/') // Normalize multiple slashes
|
|
44
|
+
.replace(/^\/+|\/+$/g, ''); // Trim leading/trailing slashes
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Build the CSS file path for a theme
|
|
49
|
-
*
|
|
49
|
+
*
|
|
50
50
|
* @param themeName - Name of the theme
|
|
51
51
|
* @param basePath - Base path for theme files
|
|
52
52
|
* @param useMinified - Whether to use minified CSS
|
|
@@ -55,387 +55,375 @@ const sanitizePath = (path: string): string => {
|
|
|
55
55
|
* @throws Error if theme name is invalid
|
|
56
56
|
*/
|
|
57
57
|
export const buildThemePath = (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
themeName: string,
|
|
59
|
+
basePath: string = '/themes',
|
|
60
|
+
useMinified: boolean = false,
|
|
61
|
+
cdnPath: string | null = null
|
|
62
62
|
): string => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
63
|
+
// Validate theme name to prevent path injection
|
|
64
|
+
if (!isValidThemeName(themeName)) {
|
|
65
|
+
throw new ThemeError(
|
|
66
|
+
`Invalid theme name: "${themeName}". Theme names must be lowercase alphanumeric with hyphens (e.g., "my-theme").`,
|
|
67
|
+
ThemeErrorCode.INVALID_THEME_NAME,
|
|
68
|
+
{ themeName, pattern: /^[a-z0-9]+(-[a-z0-9]+)*$/ }
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const extension = useMinified ? '.min.css' : '.css';
|
|
73
|
+
const fileName = `${themeName}${extension}`;
|
|
74
|
+
|
|
75
|
+
if (cdnPath) {
|
|
76
|
+
// Sanitize CDN path to prevent path injection
|
|
77
|
+
const cleanCdnPath = sanitizePath(cdnPath);
|
|
78
|
+
return `${cleanCdnPath}/${fileName}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Sanitize basePath to prevent path injection
|
|
82
|
+
const cleanBasePath = sanitizePath(basePath);
|
|
83
|
+
const cleanFileName = fileName.replace(/^\//, '');
|
|
84
|
+
|
|
85
|
+
return `${cleanBasePath}/${cleanFileName}`;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
88
|
/**
|
|
91
89
|
* Load theme CSS from a full path
|
|
92
|
-
*
|
|
90
|
+
*
|
|
93
91
|
* @param fullPath - Full path to the CSS file
|
|
94
92
|
* @param linkId - ID for the link element
|
|
95
93
|
* @returns Promise that resolves when CSS is loaded
|
|
96
94
|
*/
|
|
97
|
-
export const loadThemeCSS = (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
export const loadThemeCSS = (fullPath: string, linkId: string): Promise<void> => {
|
|
96
|
+
if (isServer()) {
|
|
97
|
+
return Promise.resolve();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
// Check if theme is already loaded
|
|
102
|
+
const existingLink = document.getElementById(linkId);
|
|
103
|
+
if (existingLink) {
|
|
104
|
+
resolve();
|
|
105
|
+
return;
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
// Create link element
|
|
109
|
+
const link = document.createElement('link');
|
|
110
|
+
link.id = linkId;
|
|
111
|
+
link.rel = 'stylesheet';
|
|
112
|
+
link.type = 'text/css';
|
|
113
|
+
link.href = fullPath;
|
|
114
|
+
|
|
115
|
+
// Add data attribute for tracking
|
|
116
|
+
link.setAttribute('data-atomix-theme', 'true');
|
|
117
|
+
|
|
118
|
+
// Handle load success
|
|
119
|
+
link.onload = () => {
|
|
120
|
+
resolve();
|
|
121
|
+
};
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
link.onerror = () => {
|
|
130
|
-
// Remove failed link element
|
|
131
|
-
link.remove();
|
|
132
|
-
reject(new ThemeError(
|
|
133
|
-
`Failed to load theme CSS from: ${fullPath}. Please check that the file exists and is accessible.`,
|
|
134
|
-
ThemeErrorCode.THEME_LOAD_FAILED,
|
|
135
|
-
{ fullPath, linkId }
|
|
136
|
-
));
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Append to head
|
|
140
|
-
document.head.appendChild(link);
|
|
141
|
-
});
|
|
123
|
+
// Handle load error
|
|
124
|
+
link.onerror = () => {
|
|
125
|
+
// Remove failed link element
|
|
126
|
+
link.remove();
|
|
127
|
+
reject(
|
|
128
|
+
new ThemeError(
|
|
129
|
+
`Failed to load theme CSS from: ${fullPath}. Please check that the file exists and is accessible.`,
|
|
130
|
+
ThemeErrorCode.THEME_LOAD_FAILED,
|
|
131
|
+
{ fullPath, linkId }
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Append to head
|
|
137
|
+
document.head.appendChild(link);
|
|
138
|
+
});
|
|
142
139
|
};
|
|
143
140
|
|
|
144
141
|
/**
|
|
145
142
|
* Remove theme CSS from the DOM
|
|
146
|
-
*
|
|
143
|
+
*
|
|
147
144
|
* @param themeNameOrLinkId - Name of the theme or link ID to remove
|
|
148
145
|
*/
|
|
149
146
|
export const removeThemeCSS = (themeNameOrLinkId: string): void => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
147
|
+
if (isServer()) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Try as link ID first, then as theme name
|
|
152
|
+
let link = document.getElementById(themeNameOrLinkId);
|
|
153
|
+
if (!link) {
|
|
154
|
+
const linkId = getThemeLinkId(themeNameOrLinkId);
|
|
155
|
+
link = document.getElementById(linkId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (link) {
|
|
159
|
+
link.remove();
|
|
160
|
+
}
|
|
164
161
|
};
|
|
165
162
|
|
|
166
163
|
/**
|
|
167
164
|
* Remove all theme CSS files from the DOM
|
|
168
165
|
*/
|
|
169
166
|
export const removeAllThemeCSS = (): void => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
if (isServer()) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
173
170
|
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
const themeLinks = document.querySelectorAll('link[data-atomix-theme]');
|
|
172
|
+
themeLinks.forEach(link => link.remove());
|
|
176
173
|
};
|
|
177
174
|
|
|
178
175
|
/**
|
|
179
176
|
* Apply theme data attributes to the document
|
|
180
|
-
*
|
|
177
|
+
*
|
|
181
178
|
* @param dataAttribute - Data attribute name (default: 'data-theme')
|
|
182
179
|
* @param themeName - Name of the theme
|
|
183
180
|
*/
|
|
184
|
-
export const applyThemeAttributes = (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Also set on documentElement for broader compatibility
|
|
198
|
-
document.documentElement.setAttribute(dataAttribute, themeName);
|
|
181
|
+
export const applyThemeAttributes = (dataAttribute: string, themeName: string): void => {
|
|
182
|
+
if (isServer()) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Set data attribute on body (with null check)
|
|
187
|
+
if (document.body) {
|
|
188
|
+
document.body.setAttribute(dataAttribute, themeName);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Also set on documentElement for broader compatibility
|
|
192
|
+
document.documentElement.setAttribute(dataAttribute, themeName);
|
|
199
193
|
};
|
|
200
194
|
|
|
201
195
|
/**
|
|
202
196
|
* Remove theme data attributes from the document
|
|
203
|
-
*
|
|
197
|
+
*
|
|
204
198
|
* @param dataAttribute - Data attribute name (default: 'data-theme')
|
|
205
199
|
*/
|
|
206
|
-
export const removeThemeAttributes = (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Remove from documentElement
|
|
219
|
-
document.documentElement.removeAttribute(dataAttribute);
|
|
200
|
+
export const removeThemeAttributes = (dataAttribute: string = 'data-theme'): void => {
|
|
201
|
+
if (isServer()) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Remove from body (with null check)
|
|
206
|
+
if (document.body) {
|
|
207
|
+
document.body.removeAttribute(dataAttribute);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Remove from documentElement
|
|
211
|
+
document.documentElement.removeAttribute(dataAttribute);
|
|
220
212
|
};
|
|
221
213
|
|
|
222
214
|
/**
|
|
223
215
|
* Get the current theme from data attributes
|
|
224
|
-
*
|
|
216
|
+
*
|
|
225
217
|
* @param dataAttribute - Data attribute name (default: 'data-theme')
|
|
226
218
|
* @returns Current theme name or null
|
|
227
219
|
*/
|
|
228
|
-
export const getCurrentThemeFromDOM = (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const htmlTheme = document.documentElement?.getAttribute(dataAttribute);
|
|
238
|
-
return bodyTheme || htmlTheme || null;
|
|
220
|
+
export const getCurrentThemeFromDOM = (dataAttribute: string = 'data-theme'): string | null => {
|
|
221
|
+
if (isServer()) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Add null checks for SSR safety
|
|
226
|
+
const bodyTheme = document.body?.getAttribute(dataAttribute);
|
|
227
|
+
const htmlTheme = document.documentElement?.getAttribute(dataAttribute);
|
|
228
|
+
return bodyTheme || htmlTheme || null;
|
|
239
229
|
};
|
|
240
230
|
|
|
241
231
|
/**
|
|
242
232
|
* Detect system theme preference
|
|
243
|
-
*
|
|
233
|
+
*
|
|
244
234
|
* @returns 'dark' if system prefers dark mode, 'light' otherwise
|
|
245
235
|
*/
|
|
246
236
|
export const getSystemTheme = (): 'light' | 'dark' => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
237
|
+
if (isServer()) {
|
|
238
|
+
return 'light';
|
|
239
|
+
}
|
|
250
240
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
241
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
242
|
+
return 'dark';
|
|
243
|
+
}
|
|
254
244
|
|
|
255
|
-
|
|
245
|
+
return 'light';
|
|
256
246
|
};
|
|
257
247
|
|
|
258
248
|
/**
|
|
259
249
|
* Check if a theme is currently loaded in the DOM
|
|
260
|
-
*
|
|
250
|
+
*
|
|
261
251
|
* @param themeName - Name of the theme to check
|
|
262
252
|
* @returns True if theme CSS is loaded
|
|
263
253
|
*/
|
|
264
254
|
export const isThemeLoaded = (themeName: string): boolean => {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
255
|
+
if (isServer()) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
268
258
|
|
|
269
|
-
|
|
270
|
-
|
|
259
|
+
const linkId = getThemeLinkId(themeName);
|
|
260
|
+
return document.getElementById(linkId) !== null;
|
|
271
261
|
};
|
|
272
262
|
|
|
273
263
|
/**
|
|
274
264
|
* Validate theme metadata
|
|
275
|
-
*
|
|
265
|
+
*
|
|
276
266
|
* @param metadata - Theme metadata to validate
|
|
277
267
|
* @returns Validation result with errors and warnings
|
|
278
268
|
*/
|
|
279
|
-
export const validateThemeMetadata = (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
269
|
+
export const validateThemeMetadata = (metadata: unknown): ThemeValidationResult => {
|
|
270
|
+
const errors: string[] = [];
|
|
271
|
+
const warnings: string[] = [];
|
|
272
|
+
|
|
273
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
274
|
+
errors.push('Theme metadata must be an object');
|
|
275
|
+
return { valid: false, errors, warnings };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const theme = metadata as Partial<ThemeMetadata>;
|
|
279
|
+
|
|
280
|
+
// Required fields
|
|
281
|
+
if (!theme.name || typeof theme.name !== 'string') {
|
|
282
|
+
errors.push('Theme must have a valid name');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Optional but recommended fields
|
|
286
|
+
if (!theme.description) {
|
|
287
|
+
warnings.push('Theme should have a description');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!theme.version) {
|
|
291
|
+
warnings.push('Theme should have a version');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!theme.author) {
|
|
295
|
+
warnings.push('Theme should have an author');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Validate status if provided
|
|
299
|
+
if (theme.status) {
|
|
300
|
+
const validStatuses = ['stable', 'beta', 'experimental', 'deprecated'];
|
|
301
|
+
if (!validStatuses.includes(theme.status)) {
|
|
302
|
+
errors.push(`Invalid status: ${theme.status}. Must be one of: ${validStatuses.join(', ')}`);
|
|
295
303
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (theme.status) {
|
|
312
|
-
const validStatuses = ['stable', 'beta', 'experimental', 'deprecated'];
|
|
313
|
-
if (!validStatuses.includes(theme.status)) {
|
|
314
|
-
errors.push(`Invalid status: ${theme.status}. Must be one of: ${validStatuses.join(', ')}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Validate color if provided
|
|
307
|
+
if (theme.color && typeof theme.color !== 'string') {
|
|
308
|
+
errors.push('Theme color must be a string');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Validate a11y if provided
|
|
312
|
+
if (theme.a11y) {
|
|
313
|
+
if (typeof theme.a11y !== 'object') {
|
|
314
|
+
errors.push('Theme a11y must be an object');
|
|
315
|
+
} else {
|
|
316
|
+
if (theme.a11y.contrastTarget !== undefined) {
|
|
317
|
+
if (typeof theme.a11y.contrastTarget !== 'number' || theme.a11y.contrastTarget < 0) {
|
|
318
|
+
errors.push('Theme a11y.contrastTarget must be a positive number');
|
|
315
319
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
errors.push('Theme color must be a string');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Validate a11y if provided
|
|
324
|
-
if (theme.a11y) {
|
|
325
|
-
if (typeof theme.a11y !== 'object') {
|
|
326
|
-
errors.push('Theme a11y must be an object');
|
|
327
|
-
} else {
|
|
328
|
-
if (theme.a11y.contrastTarget !== undefined) {
|
|
329
|
-
if (typeof theme.a11y.contrastTarget !== 'number' || theme.a11y.contrastTarget < 0) {
|
|
330
|
-
errors.push('Theme a11y.contrastTarget must be a positive number');
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
if (theme.a11y.modes !== undefined) {
|
|
334
|
-
if (!Array.isArray(theme.a11y.modes)) {
|
|
335
|
-
errors.push('Theme a11y.modes must be an array');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
320
|
+
}
|
|
321
|
+
if (theme.a11y.modes !== undefined) {
|
|
322
|
+
if (!Array.isArray(theme.a11y.modes)) {
|
|
323
|
+
errors.push('Theme a11y.modes must be an array');
|
|
338
324
|
}
|
|
325
|
+
}
|
|
339
326
|
}
|
|
327
|
+
}
|
|
340
328
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
329
|
+
return {
|
|
330
|
+
valid: errors.length === 0,
|
|
331
|
+
errors,
|
|
332
|
+
warnings,
|
|
333
|
+
};
|
|
346
334
|
};
|
|
347
335
|
|
|
348
336
|
/**
|
|
349
337
|
* Validate theme name format
|
|
350
|
-
*
|
|
338
|
+
*
|
|
351
339
|
* @param themeName - Theme name to validate
|
|
352
340
|
* @returns True if valid
|
|
353
341
|
*/
|
|
354
342
|
export const isValidThemeName = (themeName: string): boolean => {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
343
|
+
if (!themeName || typeof themeName !== 'string') {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
358
346
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
347
|
+
// Theme names should be lowercase alphanumeric with hyphens
|
|
348
|
+
const validPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
349
|
+
return validPattern.test(themeName);
|
|
362
350
|
};
|
|
363
351
|
|
|
364
352
|
/**
|
|
365
353
|
* Create a storage adapter for localStorage
|
|
366
354
|
*/
|
|
367
355
|
export const createLocalStorageAdapter = () => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
356
|
+
return {
|
|
357
|
+
getItem: (key: string): string | null => {
|
|
358
|
+
if (isServer()) return null;
|
|
359
|
+
try {
|
|
360
|
+
return localStorage.getItem(key);
|
|
361
|
+
} catch {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
setItem: (key: string, value: string): void => {
|
|
366
|
+
if (isServer()) return;
|
|
367
|
+
try {
|
|
368
|
+
localStorage.setItem(key, value);
|
|
369
|
+
} catch {
|
|
370
|
+
// Silently fail if localStorage is not available
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
removeItem: (key: string): void => {
|
|
374
|
+
if (isServer()) return;
|
|
375
|
+
try {
|
|
376
|
+
localStorage.removeItem(key);
|
|
377
|
+
} catch {
|
|
378
|
+
// Silently fail
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
isAvailable: (): boolean => {
|
|
382
|
+
if (isServer()) return false;
|
|
383
|
+
try {
|
|
384
|
+
const test = '__atomix_storage_test__';
|
|
385
|
+
localStorage.setItem(test, test);
|
|
386
|
+
localStorage.removeItem(test);
|
|
387
|
+
return true;
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
};
|
|
405
393
|
};
|
|
406
394
|
|
|
407
395
|
/**
|
|
408
396
|
* Debounce function for performance optimization
|
|
409
|
-
*
|
|
397
|
+
*
|
|
410
398
|
* @param func - Function to debounce
|
|
411
399
|
* @param wait - Wait time in milliseconds
|
|
412
400
|
* @returns Debounced function with cancel method
|
|
413
401
|
*/
|
|
414
402
|
export const debounce = <T extends (...args: any[]) => any>(
|
|
415
|
-
|
|
416
|
-
|
|
403
|
+
func: T,
|
|
404
|
+
wait: number
|
|
417
405
|
): ((...args: Parameters<T>) => void) & { cancel: () => void } => {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const debounced = function executedFunction(...args: Parameters<T>) {
|
|
421
|
-
const later = () => {
|
|
422
|
-
timeout = null;
|
|
423
|
-
func(...args);
|
|
424
|
-
};
|
|
406
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
425
407
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
408
|
+
const debounced = function executedFunction(...args: Parameters<T>) {
|
|
409
|
+
const later = () => {
|
|
410
|
+
timeout = null;
|
|
411
|
+
func(...args);
|
|
430
412
|
};
|
|
431
413
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
414
|
+
if (timeout !== null) {
|
|
415
|
+
clearTimeout(timeout);
|
|
416
|
+
}
|
|
417
|
+
timeout = setTimeout(later, wait);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Add cancel method for cleanup
|
|
421
|
+
debounced.cancel = () => {
|
|
422
|
+
if (timeout !== null) {
|
|
423
|
+
clearTimeout(timeout);
|
|
424
|
+
timeout = null;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
439
427
|
|
|
440
|
-
|
|
428
|
+
return debounced;
|
|
441
429
|
};
|