|
1 | 1 | import type { Color, CssClassMap } from '../interface'; |
2 | 2 |
|
3 | | -import { deepMerge } from './helpers'; |
4 | | - |
5 | | -export const CSS_PROPS_PREFIX = '--ion-'; |
6 | | -export const CSS_ROOT_SELECTOR = ':root'; |
7 | | - |
8 | 3 | export const hostContext = (selector: string, el: HTMLElement): boolean => { |
9 | 4 | return el.closest(selector) !== null; |
10 | 5 | }; |
@@ -38,192 +33,3 @@ export const getClassMap = (classes: string | string[] | undefined): CssClassMap |
38 | 33 | getClassList(classes).forEach((c) => (map[c] = true)); |
39 | 34 | return map; |
40 | 35 | }; |
41 | | - |
42 | | -/** |
43 | | - * Flattens the theme object into CSS custom properties |
44 | | - * @param theme The theme object to flatten |
45 | | - * @param prefix The CSS prefix to use (e.g., '--ion-') |
46 | | - * @returns CSS string with custom properties |
47 | | - */ |
48 | | -export const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX): string => { |
49 | | - const cssProps = Object.entries(theme) |
50 | | - .flatMap(([key, val]) => { |
51 | | - // Skip invalid keys or values |
52 | | - if (!key || typeof key !== 'string' || val === null || val === undefined) { |
53 | | - return []; |
54 | | - } |
55 | | - |
56 | | - // if key is camelCase, convert to kebab-case |
57 | | - if (key.match(/([a-z])([A-Z])/g)) { |
58 | | - key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); |
59 | | - } |
60 | | - |
61 | | - // Special handling for 'base' property - don't add suffix |
62 | | - if (key === 'base') { |
63 | | - return [`${prefix.slice(0, -1)}: ${val};`]; |
64 | | - } |
65 | | - |
66 | | - // If it's a font-sizes key, create rem version |
67 | | - // This is necessary to support the dynamic font size feature |
68 | | - if (key === 'font-sizes' && typeof val === 'object' && val !== null) { |
69 | | - // Access the root font size from the global theme context |
70 | | - const fontSizeBase = parseFloat((window as any).Ionic?.config?.get?.('theme')?.fontSizes?.root ?? '16'); |
71 | | - return Object.entries(val).flatMap(([sizeKey, sizeValue]) => { |
72 | | - if (!sizeKey || sizeValue == null) return []; |
73 | | - const remValue = `${parseFloat(sizeValue) / fontSizeBase}rem`; |
74 | | - // Return both px and rem values as separate array items |
75 | | - return [ |
76 | | - `${prefix}${key}-${sizeKey}: ${sizeValue};`, // original px value |
77 | | - `${prefix}${key}-${sizeKey}-rem: ${remValue};`, // rem value |
78 | | - ]; |
79 | | - }); |
80 | | - } |
81 | | - |
82 | | - return typeof val === 'object' && val !== null |
83 | | - ? generateCSSVars(val, `${prefix}${key}-`) |
84 | | - : [`${prefix}${key}: ${val};`]; |
85 | | - }) |
86 | | - .filter(Boolean); |
87 | | - |
88 | | - return cssProps.join('\n'); |
89 | | -}; |
90 | | - |
91 | | -/** |
92 | | - * Creates a style element and injects its CSS into a target element |
93 | | - * @param css The CSS string to inject |
94 | | - * @param target The target element to inject into |
95 | | - */ |
96 | | -export const injectCSS = (css: string, target: Element | ShadowRoot = document.head) => { |
97 | | - const style = document.createElement('style'); |
98 | | - style.innerHTML = css; |
99 | | - target.appendChild(style); |
100 | | -}; |
101 | | - |
102 | | -/** |
103 | | - * Generates global CSS variables from a theme object |
104 | | - * @param theme The theme object to generate CSS for |
105 | | - * @returns The generated CSS string |
106 | | - */ |
107 | | -export const generateGlobalThemeCSS = (theme: any): string => { |
108 | | - if (typeof theme !== 'object' || Array.isArray(theme)) { |
109 | | - console.warn('generateGlobalThemeCSS: Invalid theme object provided', theme); |
110 | | - return ''; |
111 | | - } |
112 | | - |
113 | | - if (Object.keys(theme).length === 0) { |
114 | | - console.warn('generateGlobalThemeCSS: Empty theme object provided'); |
115 | | - return ''; |
116 | | - } |
117 | | - |
118 | | - // Exclude components and palette from the default tokens |
119 | | - const { palette, components, ...defaultTokens } = theme; |
120 | | - |
121 | | - // Generate CSS variables for the default design tokens |
122 | | - const defaultTokensCSS = generateCSSVars(defaultTokens); |
123 | | - |
124 | | - // Generate CSS variables for the light color palette |
125 | | - const lightTokensCSS = generateCSSVars(palette.light); |
126 | | - |
127 | | - let css = ` |
128 | | - ${CSS_ROOT_SELECTOR} { |
129 | | - ${defaultTokensCSS} |
130 | | - ${lightTokensCSS} |
131 | | - } |
132 | | - `; |
133 | | - |
134 | | - // Generate CSS variables for the dark color palette if it |
135 | | - // is enabled for system preference |
136 | | - if (palette.dark.enabled === 'system') { |
137 | | - const darkTokensCSS = generateCSSVars(palette.dark); |
138 | | - if (darkTokensCSS.length > 0) { |
139 | | - css += ` |
140 | | - @media (prefers-color-scheme: dark) { |
141 | | - ${CSS_ROOT_SELECTOR} { |
142 | | - ${darkTokensCSS} |
143 | | - } |
144 | | - } |
145 | | - `; |
146 | | - } |
147 | | - } |
148 | | - |
149 | | - return css; |
150 | | -}; |
151 | | - |
152 | | -/** |
153 | | - * Applies the global theme from the provided base theme and user theme |
154 | | - * @param baseTheme The default theme |
155 | | - * @param userTheme The user's custom theme (optional) |
156 | | - * @returns The combined theme object (or base theme if no user theme was provided) |
157 | | - */ |
158 | | -export const applyGlobalTheme = (baseTheme: any, userTheme?: any): any => { |
159 | | - // If no base theme provided, error |
160 | | - if (typeof baseTheme !== 'object' || Array.isArray(baseTheme)) { |
161 | | - console.error('applyGlobalTheme: Valid base theme object is required', baseTheme); |
162 | | - return {}; |
163 | | - } |
164 | | - |
165 | | - // If no user theme provided or it is invalid, apply base theme |
166 | | - if (!userTheme || typeof userTheme !== 'object' || Array.isArray(userTheme)) { |
167 | | - if (userTheme) { |
168 | | - console.error('applyGlobalTheme: Invalid user theme provided', userTheme); |
169 | | - } |
170 | | - injectCSS(generateGlobalThemeCSS(baseTheme)); |
171 | | - return baseTheme; |
172 | | - } |
173 | | - |
174 | | - // Merge themes and apply |
175 | | - const mergedTheme = deepMerge(baseTheme, userTheme); |
176 | | - injectCSS(generateGlobalThemeCSS(mergedTheme)); |
177 | | - return mergedTheme; |
178 | | -}; |
179 | | - |
180 | | -/** |
181 | | - * Generates component's themed CSS class with CSS variables |
182 | | - * from its theme object |
183 | | - * @param componentTheme The component's object to generate CSS for (e.g., IonChip { }) |
184 | | - * @param componentName The component name without any prefixes (e.g., 'chip') |
185 | | - * @returns string containing the component's themed CSS variables |
186 | | - */ |
187 | | -export const generateComponentThemeCSS = (componentTheme: any, componentName: string): string => { |
188 | | - const cssProps = generateCSSVars(componentTheme, `${CSS_PROPS_PREFIX}${componentName}-`); |
189 | | - |
190 | | - return ` |
191 | | - :host(.${componentName}-themed) { |
192 | | - ${cssProps} |
193 | | - } |
194 | | - `; |
195 | | -}; |
196 | | - |
197 | | -/** |
198 | | - * Applies a component theme to an element if it exists in the custom theme |
199 | | - * @param element The element to apply the theme to |
200 | | - * @returns true if theme was applied, false otherwise |
201 | | - */ |
202 | | -export const applyComponentTheme = (element: HTMLElement): void => { |
203 | | - const customTheme = (window as any).Ionic?.config?.get?.('customTheme'); |
204 | | - |
205 | | - // Convert 'ION-CHIP' to 'ion-chip' and split into parts |
206 | | - const parts = element.tagName.toLowerCase().split('-'); |
207 | | - |
208 | | - // Get the component name 'chip' from the second part |
209 | | - const componentName = parts[1]; |
210 | | - |
211 | | - // Convert to 'IonChip' by capitalizing each part |
212 | | - const themeLookupName = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(''); |
213 | | - |
214 | | - if (customTheme?.components?.[themeLookupName]) { |
215 | | - const componentTheme = customTheme.components[themeLookupName]; |
216 | | - |
217 | | - // Add the theme class to the element (e.g., 'chip-themed') |
218 | | - const themeClass = `${componentName}-themed`; |
219 | | - element.classList.add(themeClass); |
220 | | - |
221 | | - // Generate CSS custom properties inside a theme class selector |
222 | | - const css = generateComponentThemeCSS(componentTheme, componentName); |
223 | | - |
224 | | - // Inject styles into shadow root if available, |
225 | | - // otherwise into the element itself |
226 | | - const root = element.shadowRoot ?? element; |
227 | | - injectCSS(css, root); |
228 | | - } |
229 | | -}; |
0 commit comments