@kilivi/payloadcms-theme-management 0.1.9 → 0.1.11
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/README.md +387 -97
- package/dist/components/ServerThemeInjector.d.ts +12 -3
- package/dist/components/ServerThemeInjector.d.ts.map +1 -1
- package/dist/components/ServerThemeInjector.js +5 -9
- package/dist/components/ServerThemeInjector.js.map +1 -1
- package/dist/utils/resolveThemeConfiguration.d.ts +40 -5
- package/dist/utils/resolveThemeConfiguration.d.ts.map +1 -1
- package/dist/utils/resolveThemeConfiguration.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,97 +1,387 @@
|
|
|
1
|
-
# Theme
|
|
2
|
-
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- 🎨 **Theme
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
pnpm add @payloadcms-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
# Theme Management Plugin for Payload CMS v3
|
|
2
|
+
|
|
3
|
+
A comprehensive theme management plugin for Payload CMS v3 that provides powerful theming capabilities with SSR support, preventing FOUC (Flash of Unstyled Content).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Multiple Theme Presets** - Cool, Brutal, Neon, Solar, and more
|
|
8
|
+
- 🎨 **Custom Color Palette** - Full HSL color customization
|
|
9
|
+
- 🔤 **Typography Control** - Google Fonts integration for headings, body, and code
|
|
10
|
+
- 📐 **Border Radius Presets** - Sharp, Rounded, or Pill styles
|
|
11
|
+
- 🌓 **Dark Mode Support** - Built-in light/dark/system mode toggle
|
|
12
|
+
- 🚀 **SSR Theme Injection** - Zero FOUC with server-side rendering
|
|
13
|
+
- ⚡ **Performance Optimized** - Critical CSS inlining, preload links
|
|
14
|
+
- 🎯 **Type Safe** - Full TypeScript support
|
|
15
|
+
- 🔧 **Highly Configurable** - Flexible plugin options
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @kilivi/payloadcms-theme-management
|
|
21
|
+
# or
|
|
22
|
+
npm install @kilivi/payloadcms-theme-management
|
|
23
|
+
# or
|
|
24
|
+
yarn add @kilivi/payloadcms-theme-management
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Add Plugin to Payload Config
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { buildConfig } from 'payload'
|
|
33
|
+
import { themeManagementPlugin } from '@kilivi/payloadcms-theme-management'
|
|
34
|
+
|
|
35
|
+
export default buildConfig({
|
|
36
|
+
// ... other config
|
|
37
|
+
|
|
38
|
+
globals: [
|
|
39
|
+
{
|
|
40
|
+
slug: 'site-settings',
|
|
41
|
+
fields: [
|
|
42
|
+
{
|
|
43
|
+
name: 'siteName',
|
|
44
|
+
type: 'text',
|
|
45
|
+
},
|
|
46
|
+
// Theme configuration will be injected here
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
plugins: [
|
|
52
|
+
themeManagementPlugin({
|
|
53
|
+
enabled: true,
|
|
54
|
+
targetCollection: 'site-settings',
|
|
55
|
+
defaultTheme: 'cool',
|
|
56
|
+
includeColorModeToggle: true,
|
|
57
|
+
enableLogging: true,
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Add Server Theme Injection (Next.js)
|
|
64
|
+
|
|
65
|
+
**⚠️ IMPORTANT:** Import from `/server` entry point!
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// app/layout.tsx
|
|
69
|
+
import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
70
|
+
import configPromise from '@payload-config'
|
|
71
|
+
import { getPayload } from 'payload'
|
|
72
|
+
|
|
73
|
+
export default async function RootLayout({
|
|
74
|
+
children,
|
|
75
|
+
}: {
|
|
76
|
+
children: React.ReactNode
|
|
77
|
+
}) {
|
|
78
|
+
const payload = await getPayload({ config: configPromise })
|
|
79
|
+
|
|
80
|
+
const { docs } = await payload.find({
|
|
81
|
+
collection: 'site-settings',
|
|
82
|
+
limit: 1,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<html lang="en">
|
|
87
|
+
<head>
|
|
88
|
+
<ServerThemeInjector themeConfiguration={docs[0]?.themeConfiguration} />
|
|
89
|
+
</head>
|
|
90
|
+
<body>{children}</body>
|
|
91
|
+
</html>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 3. Use Theme Variables in Your CSS
|
|
97
|
+
|
|
98
|
+
The plugin injects CSS custom properties you can use:
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
.my-component {
|
|
102
|
+
background-color: hsl(var(--background));
|
|
103
|
+
color: hsl(var(--foreground));
|
|
104
|
+
border-radius: var(--radius);
|
|
105
|
+
font-family: var(--font-body);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Or with Tailwind CSS:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<div className="bg-primary text-primary-foreground rounded-lg">
|
|
113
|
+
Themed Content
|
|
114
|
+
</div>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Plugin Configuration Options
|
|
119
|
+
|
|
120
|
+
| Option | Type | Default | Description |
|
|
121
|
+
|--------|------|---------|-------------|
|
|
122
|
+
| `enabled` | `boolean` | `true` | Enable/disable the plugin |
|
|
123
|
+
| `targetCollection` | `string` | `'site-settings'` | Global or collection slug to add theme field to |
|
|
124
|
+
| `themePresets` | `ThemePreset[]` | Built-in presets | Custom theme presets |
|
|
125
|
+
| `defaultTheme` | `string` | `'cool'` | Default theme preset name |
|
|
126
|
+
| `includeColorModeToggle` | `boolean` | `true` | Show light/dark mode toggle |
|
|
127
|
+
| `includeCustomCSS` | `boolean` | `true` | Allow custom CSS injection |
|
|
128
|
+
| `includeBrandIdentity` | `boolean` | `false` | Show brand identity fields |
|
|
129
|
+
| `enableAdvancedFeatures` | `boolean` | `true` | Enable advanced customization |
|
|
130
|
+
| `enableLogging` | `boolean` | `false` | Log plugin actions to console |
|
|
131
|
+
|
|
132
|
+
## Available Theme Presets
|
|
133
|
+
|
|
134
|
+
- **Cool** - Professional blue theme
|
|
135
|
+
- **Brutal** - High contrast, bold design
|
|
136
|
+
- **Neon** - Vibrant, energetic colors
|
|
137
|
+
- **Solar** - Warm, golden tones
|
|
138
|
+
- **Dealership** - Automotive-inspired
|
|
139
|
+
- **Real Estate** - Professional property theme (+ Gold, Neutral variants)
|
|
140
|
+
|
|
141
|
+
## API Reference
|
|
142
|
+
|
|
143
|
+
### Main Entry Point
|
|
144
|
+
|
|
145
|
+
Import from `@kilivi/payloadcms-theme-management` for client-safe code:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Plugin
|
|
149
|
+
import { themeManagementPlugin } from '@kilivi/payloadcms-theme-management'
|
|
150
|
+
|
|
151
|
+
// Client Components
|
|
152
|
+
import { ThemeProvider } from '@kilivi/payloadcms-theme-management'
|
|
153
|
+
|
|
154
|
+
// Utilities
|
|
155
|
+
import {
|
|
156
|
+
generateThemeColorsCss,
|
|
157
|
+
generateThemeCSS,
|
|
158
|
+
getThemeStyles,
|
|
159
|
+
resolveThemeConfiguration,
|
|
160
|
+
fetchThemeConfiguration,
|
|
161
|
+
} from '@kilivi/payloadcms-theme-management'
|
|
162
|
+
|
|
163
|
+
// Types
|
|
164
|
+
import type {
|
|
165
|
+
ThemePreset,
|
|
166
|
+
ThemeDefaults,
|
|
167
|
+
ThemeManagementPluginOptions,
|
|
168
|
+
} from '@kilivi/payloadcms-theme-management'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Server Entry Point
|
|
172
|
+
|
|
173
|
+
⚠️ **Import from `/server` for server components only:**
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import {
|
|
177
|
+
ServerThemeInjector,
|
|
178
|
+
getThemeCriticalCSS,
|
|
179
|
+
getThemeCSSPath,
|
|
180
|
+
generateThemePreloadLinks,
|
|
181
|
+
} from '@kilivi/payloadcms-theme-management/server'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Subpath Exports
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Direct field imports
|
|
188
|
+
import { ThemeColorPickerField } from '@kilivi/payloadcms-theme-management/fields/ThemeColorPickerField'
|
|
189
|
+
import { ThemeTokenSelectField } from '@kilivi/payloadcms-theme-management/fields/ThemeTokenSelectField'
|
|
190
|
+
|
|
191
|
+
// Direct component imports
|
|
192
|
+
import { ThemePreview } from '@kilivi/payloadcms-theme-management/components/ThemePreview'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Integration Examples
|
|
196
|
+
|
|
197
|
+
### With Tailwind CSS
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// tailwind.config.ts
|
|
201
|
+
import type { Config } from 'tailwindcss'
|
|
202
|
+
|
|
203
|
+
const config: Config = {
|
|
204
|
+
theme: {
|
|
205
|
+
extend: {
|
|
206
|
+
colors: {
|
|
207
|
+
background: 'hsl(var(--background))',
|
|
208
|
+
foreground: 'hsl(var(--foreground))',
|
|
209
|
+
primary: {
|
|
210
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
211
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
212
|
+
},
|
|
213
|
+
// ... other color variables
|
|
214
|
+
},
|
|
215
|
+
borderRadius: {
|
|
216
|
+
lg: 'var(--radius)',
|
|
217
|
+
md: 'calc(var(--radius) - 2px)',
|
|
218
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
219
|
+
},
|
|
220
|
+
fontFamily: {
|
|
221
|
+
heading: ['var(--font-heading)', 'sans-serif'],
|
|
222
|
+
body: ['var(--font-body)', 'sans-serif'],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Fetching Theme Configuration
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
|
|
233
|
+
|
|
234
|
+
// Simple usage
|
|
235
|
+
const theme = await fetchThemeConfiguration()
|
|
236
|
+
|
|
237
|
+
// With options
|
|
238
|
+
const theme = await fetchThemeConfiguration({
|
|
239
|
+
collectionSlug: 'site-settings',
|
|
240
|
+
depth: 2,
|
|
241
|
+
locale: 'en',
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Custom Theme Preset
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import type { ThemePreset } from '@kilivi/payloadcms-theme-management'
|
|
249
|
+
|
|
250
|
+
const myTheme: ThemePreset = {
|
|
251
|
+
name: 'my-custom-theme',
|
|
252
|
+
label: 'My Custom Theme',
|
|
253
|
+
colors: {
|
|
254
|
+
primary: { h: 220, s: 70, l: 50 },
|
|
255
|
+
secondary: { h: 180, s: 60, l: 45 },
|
|
256
|
+
// ... other colors
|
|
257
|
+
},
|
|
258
|
+
borderRadius: 'rounded',
|
|
259
|
+
typography: {
|
|
260
|
+
heading: 'Poppins',
|
|
261
|
+
body: 'Inter',
|
|
262
|
+
code: 'Fira Code',
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Use in plugin config
|
|
267
|
+
themeManagementPlugin({
|
|
268
|
+
themePresets: [myTheme],
|
|
269
|
+
defaultTheme: 'my-custom-theme',
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Migrating from Older Versions
|
|
274
|
+
|
|
275
|
+
See [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) for detailed migration instructions.
|
|
276
|
+
|
|
277
|
+
### Key Changes in v0.1.9+
|
|
278
|
+
|
|
279
|
+
- **Server components must import from `/server`**
|
|
280
|
+
- Added `server-only` package to prevent client bundling errors
|
|
281
|
+
- Removed `ServerThemeInjector` from main entry point
|
|
282
|
+
|
|
283
|
+
```diff
|
|
284
|
+
- import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management'
|
|
285
|
+
+ import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
## Testing
|
|
290
|
+
|
|
291
|
+
See [TEST_APP_GUIDE.md](./TEST_APP_GUIDE.md) for instructions on creating a test application.
|
|
292
|
+
|
|
293
|
+
## Troubleshooting
|
|
294
|
+
|
|
295
|
+
### `Module not found: Can't resolve 'fs/promises'`
|
|
296
|
+
|
|
297
|
+
**Solution:** Make sure you're using v0.1.9+ and importing server components from `/server`:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Then clear your cache:
|
|
304
|
+
```bash
|
|
305
|
+
rm -rf .next node_modules/.cache
|
|
306
|
+
pnpm install
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Theme Not Applying
|
|
310
|
+
|
|
311
|
+
1. Verify `ServerThemeInjector` is in your `<head>` tag
|
|
312
|
+
2. Check that site settings exist with theme configuration
|
|
313
|
+
3. Inspect page source - should see `<style>` tag with CSS variables
|
|
314
|
+
4. Ensure Tailwind/CSS is configured to use the CSS variables
|
|
315
|
+
|
|
316
|
+
### TypeScript Errors
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# Regenerate Payload types
|
|
320
|
+
pnpm payload generate:types
|
|
321
|
+
|
|
322
|
+
# Restart TypeScript server
|
|
323
|
+
# VS Code: Ctrl+Shift+P → "TypeScript: Restart TS Server"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Documentation
|
|
327
|
+
|
|
328
|
+
- [Migration Guide](./MIGRATION_GUIDE.md) - Upgrading from older versions
|
|
329
|
+
- [Test App Guide](./TEST_APP_GUIDE.md) - Create a test application
|
|
330
|
+
- [Server/Client Separation](./SERVER_CLIENT_SEPARATION.md) - Technical details
|
|
331
|
+
- [Build Setup](./BUILD_SETUP.md) - Build configuration details
|
|
332
|
+
|
|
333
|
+
## Development
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# Install dependencies
|
|
337
|
+
pnpm install
|
|
338
|
+
|
|
339
|
+
# Build the plugin
|
|
340
|
+
pnpm build
|
|
341
|
+
|
|
342
|
+
# Watch mode for development
|
|
343
|
+
pnpm dev
|
|
344
|
+
|
|
345
|
+
# Clean build artifacts
|
|
346
|
+
pnpm clean
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Contributing
|
|
350
|
+
|
|
351
|
+
Contributions are welcome! Please:
|
|
352
|
+
|
|
353
|
+
1. Fork the repository
|
|
354
|
+
2. Create a feature branch
|
|
355
|
+
3. Make your changes
|
|
356
|
+
4. Add tests if applicable
|
|
357
|
+
5. Submit a pull request
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
Apache-2.0
|
|
362
|
+
|
|
363
|
+
## Author
|
|
364
|
+
|
|
365
|
+
Created for Payload CMS v3 applications.
|
|
366
|
+
|
|
367
|
+
## Links
|
|
368
|
+
|
|
369
|
+
- [NPM Package](https://www.npmjs.com/package/@kilivi/payloadcms-theme-management)
|
|
370
|
+
- [GitHub Repository](https://github.com/vitakili/payload-plugins)
|
|
371
|
+
- [Payload CMS](https://payloadcms.com)
|
|
372
|
+
|
|
373
|
+
## Changelog
|
|
374
|
+
|
|
375
|
+
### v0.1.9 (Latest)
|
|
376
|
+
- ✅ **Fixed:** Server/client component separation
|
|
377
|
+
- ✅ **Added:** `server-only` package to prevent bundling errors
|
|
378
|
+
- ✅ **Changed:** `ServerThemeInjector` now exported from `/server` entry
|
|
379
|
+
- ✅ **Added:** Comprehensive documentation
|
|
380
|
+
|
|
381
|
+
### v0.1.7
|
|
382
|
+
- ✅ **Fixed:** ESM import resolution with `.js` extensions
|
|
383
|
+
- ✅ **Added:** SWC + TypeScript build pipeline
|
|
384
|
+
|
|
385
|
+
### v0.1.5
|
|
386
|
+
- Initial release with theme management features
|
|
387
|
+
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import type { SiteSetting } from '../payload-types.js';
|
|
2
1
|
interface ServerThemeInjectorProps {
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Theme configuration object from your site settings.
|
|
4
|
+
* Pass the themeConfiguration field directly, not the entire site settings object.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* const siteSettings = await payload.findGlobal({ slug: 'site-settings' })
|
|
9
|
+
* <ServerThemeInjector themeConfiguration={siteSettings.themeConfiguration} />
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
themeConfiguration?: unknown;
|
|
4
13
|
}
|
|
5
14
|
/**
|
|
6
15
|
* Server-side theme CSS injector to prevent FOUC
|
|
7
16
|
* This component injects critical theme CSS directly into the HTML head during SSR
|
|
8
17
|
*/
|
|
9
|
-
export declare function ServerThemeInjector({
|
|
18
|
+
export declare function ServerThemeInjector({ themeConfiguration }: Readonly<ServerThemeInjectorProps>): Promise<import("react").JSX.Element>;
|
|
10
19
|
export {};
|
|
11
20
|
//# sourceMappingURL=ServerThemeInjector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerThemeInjector.d.ts","sourceRoot":"","sources":["../../src/components/ServerThemeInjector.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ServerThemeInjector.d.ts","sourceRoot":"","sources":["../../src/components/ServerThemeInjector.tsx"],"names":[],"mappings":"AAaA,UAAU,wBAAwB;IAChC;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAMD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,EAAE,QAAQ,CAAC,wBAAwB,CAAC,wCAuFnG"}
|
|
@@ -7,17 +7,13 @@ import { generateThemeCSS } from '../utils/themeUtils.js';
|
|
|
7
7
|
/**
|
|
8
8
|
* Server-side theme CSS injector to prevent FOUC
|
|
9
9
|
* This component injects critical theme CSS directly into the HTML head during SSR
|
|
10
|
-
*/ export async function ServerThemeInjector({
|
|
11
|
-
|
|
10
|
+
*/ export async function ServerThemeInjector({ themeConfiguration }) {
|
|
11
|
+
// Accept unknown type to avoid type conflicts between app's payload-types and plugin's payload-types
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration);
|
|
12
14
|
const { theme, borderRadius, customCSS, lightMode, darkMode, fontScale, spacing, animationLevel, allowColorModeToggle, colorMode, typography } = resolvedConfiguration;
|
|
13
|
-
|
|
14
|
-
...siteSettings?.themeConfiguration ?? {}
|
|
15
|
-
};
|
|
16
|
-
if ('typography' in baseThemeConfiguration) {
|
|
17
|
-
delete baseThemeConfiguration.typography;
|
|
18
|
-
}
|
|
15
|
+
// Use resolved configuration directly
|
|
19
16
|
const normalizedThemeConfiguration = {
|
|
20
|
-
...baseThemeConfiguration,
|
|
21
17
|
theme,
|
|
22
18
|
colorMode,
|
|
23
19
|
allowColorModeToggle,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/ServerThemeInjector.tsx"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/components/ServerThemeInjector.tsx"],"sourcesContent":["import { getBorderRadiusConfig } from '../providers/Theme/themeConfig.js'\r\nimport type { BorderRadiusPreset, ThemeDefaults } from '../providers/Theme/types.js'\r\nimport type { ThemeTypographyPreset } from '../presets.js'\r\nimport { resolveThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\r\nimport type { ResolvedThemeConfiguration } from '../utils/resolveThemeConfiguration.js'\r\nimport {\r\n createFallbackCriticalCSS,\r\n getThemeCriticalCSS,\r\n getThemeCSSPath,\r\n} from '../utils/themeAssets.js'\r\nimport { generateThemeColorsCss } from '../utils/themeColors.js'\r\nimport { generateThemeCSS } from '../utils/themeUtils.js'\r\n\r\ninterface ServerThemeInjectorProps {\r\n /**\r\n * Theme configuration object from your site settings.\r\n * Pass the themeConfiguration field directly, not the entire site settings object.\r\n * \r\n * @example\r\n * ```tsx\r\n * const siteSettings = await payload.findGlobal({ slug: 'site-settings' })\r\n * <ServerThemeInjector themeConfiguration={siteSettings.themeConfiguration} />\r\n * ```\r\n */\r\n themeConfiguration?: unknown\r\n}\r\n\r\ntype RuntimeThemeConfiguration = Omit<ResolvedThemeConfiguration, 'typography'> & {\r\n typography?: ThemeTypographyPreset | null\r\n}\r\n\r\n/**\r\n * Server-side theme CSS injector to prevent FOUC\r\n * This component injects critical theme CSS directly into the HTML head during SSR\r\n */\r\nexport async function ServerThemeInjector({ themeConfiguration }: Readonly<ServerThemeInjectorProps>) {\r\n // Accept unknown type to avoid type conflicts between app's payload-types and plugin's payload-types\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const resolvedConfiguration = resolveThemeConfiguration(themeConfiguration as any)\r\n const {\r\n theme,\r\n borderRadius,\r\n customCSS,\r\n lightMode,\r\n darkMode,\r\n fontScale,\r\n spacing,\r\n animationLevel,\r\n allowColorModeToggle,\r\n colorMode,\r\n typography,\r\n } = resolvedConfiguration\r\n\r\n // Use resolved configuration directly\r\n const normalizedThemeConfiguration: RuntimeThemeConfiguration = {\r\n theme,\r\n colorMode,\r\n allowColorModeToggle,\r\n borderRadius,\r\n fontScale,\r\n spacing,\r\n animationLevel,\r\n customCSS: customCSS ?? undefined,\r\n lightMode,\r\n darkMode,\r\n typography: typography ?? undefined,\r\n }\r\n\r\n let criticalCSS = await getThemeCriticalCSS(theme as ThemeDefaults)\r\n criticalCSS ??= createFallbackCriticalCSS(theme as ThemeDefaults)\r\n\r\n const borderRadiusMap: Record<string, BorderRadiusPreset> = {\r\n none: 'none',\r\n small: 'small',\r\n medium: 'medium',\r\n large: 'large',\r\n xl: 'xl',\r\n full: 'xl',\r\n }\r\n const mappedBorderRadius = borderRadiusMap[borderRadius ?? 'medium'] ?? 'medium'\r\n const borderRadiusConfig = getBorderRadiusConfig(mappedBorderRadius)\r\n const cssRecord = typeof borderRadiusConfig?.css === 'string'\r\n ? { '--radius-default': borderRadiusConfig.css }\r\n : borderRadiusConfig?.css ?? {}\r\n\r\n const borderRadiusCSS = Object.entries(cssRecord)\r\n .map(([property, value]) => ` ${property}: ${value};`)\r\n .join('\\n')\r\n\r\n const borderRadiusBlock = borderRadiusCSS\r\n ? `:root {\r\n${borderRadiusCSS}\r\n}`\r\n : ''\r\n\r\n const themeConfigurationCSS = generateThemeCSS(normalizedThemeConfiguration)\r\n const colorModesCSS = generateThemeColorsCss({ themeName: theme, lightMode, darkMode })\r\n const customCSSBlock = typeof customCSS === 'string' ? customCSS.trim() : ''\r\n\r\n const combinedCSS = [\r\n criticalCSS,\r\n borderRadiusBlock,\r\n themeConfigurationCSS,\r\n colorModesCSS,\r\n customCSSBlock,\r\n ]\r\n .filter((block) => Boolean(block?.trim?.().length))\r\n .join('\\n\\n')\r\n\r\n const themeCSSPath = getThemeCSSPath(theme as ThemeDefaults)\r\n return (\r\n <>\r\n <style\r\n id=\"theme-critical-css\"\r\n dangerouslySetInnerHTML={{\r\n __html: combinedCSS,\r\n }}\r\n />\r\n <link rel=\"preload\" href={themeCSSPath} as=\"style\" />\r\n <link rel=\"stylesheet\" href={themeCSSPath} />\r\n </>\r\n )\r\n}\r\n"],"names":["getBorderRadiusConfig","resolveThemeConfiguration","createFallbackCriticalCSS","getThemeCriticalCSS","getThemeCSSPath","generateThemeColorsCss","generateThemeCSS","ServerThemeInjector","themeConfiguration","resolvedConfiguration","theme","borderRadius","customCSS","lightMode","darkMode","fontScale","spacing","animationLevel","allowColorModeToggle","colorMode","typography","normalizedThemeConfiguration","undefined","criticalCSS","borderRadiusMap","none","small","medium","large","xl","full","mappedBorderRadius","borderRadiusConfig","cssRecord","css","borderRadiusCSS","Object","entries","map","property","value","join","borderRadiusBlock","themeConfigurationCSS","colorModesCSS","themeName","customCSSBlock","trim","combinedCSS","filter","block","Boolean","length","themeCSSPath","style","id","dangerouslySetInnerHTML","__html","link","rel","href","as"],"mappings":";AAAA,SAASA,qBAAqB,QAAQ,oCAAmC;AAGzE,SAASC,yBAAyB,QAAQ,wCAAuC;AAEjF,SACEC,yBAAyB,EACzBC,mBAAmB,EACnBC,eAAe,QACV,0BAAyB;AAChC,SAASC,sBAAsB,QAAQ,0BAAyB;AAChE,SAASC,gBAAgB,QAAQ,yBAAwB;AAoBzD;;;CAGC,GACD,OAAO,eAAeC,oBAAoB,EAAEC,kBAAkB,EAAsC;IAClG,qGAAqG;IACrG,8DAA8D;IAC9D,MAAMC,wBAAwBR,0BAA0BO;IACxD,MAAM,EACJE,KAAK,EACLC,YAAY,EACZC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,OAAO,EACPC,cAAc,EACdC,oBAAoB,EACpBC,SAAS,EACTC,UAAU,EACX,GAAGX;IAEJ,sCAAsC;IACtC,MAAMY,+BAA0D;QAC9DX;QACAS;QACAD;QACAP;QACAI;QACAC;QACAC;QACAL,WAAWA,aAAaU;QACxBT;QACAC;QACAM,YAAYA,cAAcE;IAC5B;IAEA,IAAIC,cAAc,MAAMpB,oBAAoBO;IAC5Ca,gBAAgBrB,0BAA0BQ;IAE1C,MAAMc,kBAAsD;QAC1DC,MAAM;QACNC,OAAO;QACPC,QAAQ;QACRC,OAAO;QACPC,IAAI;QACJC,MAAM;IACR;IACA,MAAMC,qBAAqBP,eAAe,CAACb,gBAAgB,SAAS,IAAI;IACxE,MAAMqB,qBAAqBhC,sBAAsB+B;IACjD,MAAME,YAAY,OAAOD,oBAAoBE,QAAQ,WACjD;QAAE,oBAAoBF,mBAAmBE,GAAG;IAAC,IAC7CF,oBAAoBE,OAAO,CAAC;IAEhC,MAAMC,kBAAkBC,OAAOC,OAAO,CAACJ,WACpCK,GAAG,CAAC,CAAC,CAACC,UAAUC,MAAM,GAAK,CAAC,EAAE,EAAED,SAAS,EAAE,EAAEC,MAAM,CAAC,CAAC,EACrDC,IAAI,CAAC;IAER,MAAMC,oBAAoBP,kBACtB,CAAC;AACP,EAAEA,gBAAgB;CACjB,CAAC,GACI;IAEJ,MAAMQ,wBAAwBrC,iBAAiBe;IAC/C,MAAMuB,gBAAgBvC,uBAAuB;QAAEwC,WAAWnC;QAAOG;QAAWC;IAAS;IACrF,MAAMgC,iBAAiB,OAAOlC,cAAc,WAAWA,UAAUmC,IAAI,KAAK;IAE1E,MAAMC,cAAc;QAClBzB;QACAmB;QACAC;QACAC;QACAE;KACD,CACEG,MAAM,CAAC,CAACC,QAAUC,QAAQD,OAAOH,SAASK,SAC1CX,IAAI,CAAC;IAER,MAAMY,eAAejD,gBAAgBM;IACrC,qBACE;;0BACE,KAAC4C;gBACCC,IAAG;gBACHC,yBAAyB;oBACvBC,QAAQT;gBACV;;0BAEF,KAACU;gBAAKC,KAAI;gBAAUC,MAAMP;gBAAcQ,IAAG;;0BAC3C,KAACH;gBAAKC,KAAI;gBAAaC,MAAMP;;;;AAGnC"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { SiteSetting } from '../payload-types.js';
|
|
2
1
|
import type { Mode, ThemeDefaults } from '../providers/Theme/types.js';
|
|
3
2
|
import type { ThemeTypographyPreset } from '../presets.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generic theme configuration structure that matches Payload's generated types
|
|
5
|
+
* without depending on specific payload-types versions
|
|
6
|
+
*/
|
|
4
7
|
interface ThemeTypographyOverride {
|
|
5
8
|
bodyFont?: string | null;
|
|
6
9
|
headingFont?: string | null;
|
|
@@ -9,9 +12,41 @@ interface ThemeTypographyOverride {
|
|
|
9
12
|
baseFontSize?: string | null;
|
|
10
13
|
lineHeight?: string | null;
|
|
11
14
|
}
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Generic theme configuration interface that accepts any compatible structure
|
|
17
|
+
* This avoids type conflicts between different versions of payload-types
|
|
18
|
+
*/
|
|
19
|
+
interface GenericThemeConfiguration {
|
|
20
|
+
theme?: string | null;
|
|
21
|
+
colorMode?: string | null;
|
|
22
|
+
allowColorModeToggle?: boolean | null;
|
|
23
|
+
borderRadius?: string | null;
|
|
24
|
+
fontScale?: string | null;
|
|
25
|
+
spacing?: string | null;
|
|
26
|
+
animationLevel?: string | null;
|
|
27
|
+
customCSS?: string | null;
|
|
28
|
+
lightMode?: {
|
|
29
|
+
primary?: string | null;
|
|
30
|
+
secondary?: string | null;
|
|
31
|
+
accent?: string | null;
|
|
32
|
+
neutral?: string | null;
|
|
33
|
+
success?: string | null;
|
|
34
|
+
warning?: string | null;
|
|
35
|
+
error?: string | null;
|
|
36
|
+
info?: string | null;
|
|
37
|
+
} | null;
|
|
38
|
+
darkMode?: {
|
|
39
|
+
primary?: string | null;
|
|
40
|
+
secondary?: string | null;
|
|
41
|
+
accent?: string | null;
|
|
42
|
+
neutral?: string | null;
|
|
43
|
+
success?: string | null;
|
|
44
|
+
warning?: string | null;
|
|
45
|
+
error?: string | null;
|
|
46
|
+
info?: string | null;
|
|
47
|
+
} | null;
|
|
13
48
|
typography?: ThemeTypographyOverride | null;
|
|
14
|
-
}
|
|
49
|
+
}
|
|
15
50
|
declare const BORDER_RADIUS_VALUES: readonly ["none", "small", "medium", "large", "xl"];
|
|
16
51
|
declare const FONT_SCALE_VALUES: readonly ["small", "medium", "large"];
|
|
17
52
|
declare const SPACING_VALUES: readonly ["compact", "medium", "spacious"];
|
|
@@ -20,7 +55,7 @@ type BorderRadiusOption = (typeof BORDER_RADIUS_VALUES)[number];
|
|
|
20
55
|
type FontScaleOption = (typeof FONT_SCALE_VALUES)[number];
|
|
21
56
|
type SpacingOption = (typeof SPACING_VALUES)[number];
|
|
22
57
|
type AnimationLevelOption = (typeof ANIMATION_LEVEL_VALUES)[number];
|
|
23
|
-
type ThemeColorConfiguration = NonNullable<
|
|
58
|
+
type ThemeColorConfiguration = NonNullable<GenericThemeConfiguration['lightMode']>;
|
|
24
59
|
export interface ResolvedThemeConfiguration {
|
|
25
60
|
theme: ThemeDefaults;
|
|
26
61
|
colorMode: Mode;
|
|
@@ -35,6 +70,6 @@ export interface ResolvedThemeConfiguration {
|
|
|
35
70
|
typography: ThemeTypographyPreset | null;
|
|
36
71
|
}
|
|
37
72
|
export declare const DEFAULT_THEME_CONFIGURATION: ResolvedThemeConfiguration;
|
|
38
|
-
export declare function resolveThemeConfiguration(themeConfiguration?:
|
|
73
|
+
export declare function resolveThemeConfiguration(themeConfiguration?: GenericThemeConfiguration | null): ResolvedThemeConfiguration;
|
|
39
74
|
export {};
|
|
40
75
|
//# sourceMappingURL=resolveThemeConfiguration.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveThemeConfiguration.d.ts","sourceRoot":"","sources":["../../src/utils/resolveThemeConfiguration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"resolveThemeConfiguration.d.ts","sourceRoot":"","sources":["../../src/utils/resolveThemeConfiguration.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/resolveThemeConfiguration.ts"],"sourcesContent":["import { FONT_VALUE_TO_CSS } from '../constants/themeFonts.js'\r\nimport type { SiteSetting } from '../payload-types.js'\r\nimport type { Mode, ThemeDefaults } from '../providers/Theme/types.js'\r\nimport { themeIsValid } from '../providers/Theme/types.js'\r\nimport type { ThemeTypographyPreset } from '../presets.js'\r\nimport { defaultThemePresets } from '../presets.js'\r\n\r\ninterface ThemeTypographyOverride {\r\n bodyFont?: string | null\r\n headingFont?: string | null\r\n bodyFontCustom?: string | null\r\n headingFontCustom?: string | null\r\n baseFontSize?: string | null\r\n lineHeight?: string | null\r\n}\r\n\r\ntype SiteThemeConfiguration = SiteSetting['themeConfiguration'] & {\r\n typography?: ThemeTypographyOverride | null\r\n}\r\n\r\nconst BORDER_RADIUS_VALUES = ['none', 'small', 'medium', 'large', 'xl'] as const\r\nconst LEGACY_BORDER_RADIUS_ALIASES: Record<string, (typeof BORDER_RADIUS_VALUES)[number]> = {\r\n full: 'xl',\r\n}\r\nconst FONT_SCALE_VALUES = ['small', 'medium', 'large'] as const\r\nconst SPACING_VALUES = ['compact', 'medium', 'spacious'] as const\r\nconst ANIMATION_LEVEL_VALUES = ['none', 'reduced', 'medium', 'high'] as const\r\nconst LEGACY_ANIMATION_ALIASES: Record<string, (typeof ANIMATION_LEVEL_VALUES)[number]> = {\r\n full: 'high',\r\n}\r\nconst MODE_VALUES = ['auto', 'light', 'dark'] as const\r\n\r\ntype BorderRadiusOption = (typeof BORDER_RADIUS_VALUES)[number]\r\ntype FontScaleOption = (typeof FONT_SCALE_VALUES)[number]\r\ntype SpacingOption = (typeof SPACING_VALUES)[number]\r\ntype AnimationLevelOption = (typeof ANIMATION_LEVEL_VALUES)[number]\r\n\r\ntype ThemeModeOption = (typeof MODE_VALUES)[number]\r\n\r\ntype ThemeColorConfiguration = NonNullable<SiteThemeConfiguration['lightMode']>\r\n\r\nconst DEFAULT_THEME_NAME: ThemeDefaults = 'cool'\r\nconst DEFAULT_THEME_PRESET = defaultThemePresets.find(\r\n (preset) => preset.name === DEFAULT_THEME_NAME,\r\n)\r\n\r\nexport interface ResolvedThemeConfiguration {\r\n theme: ThemeDefaults\r\n colorMode: Mode\r\n allowColorModeToggle: boolean\r\n borderRadius: BorderRadiusOption\r\n fontScale: FontScaleOption\r\n spacing: SpacingOption\r\n animationLevel: AnimationLevelOption\r\n customCSS: string | null\r\n lightMode?: ThemeColorConfiguration\r\n darkMode?: ThemeColorConfiguration\r\n typography: ThemeTypographyPreset | null\r\n}\r\n\r\nexport const DEFAULT_THEME_CONFIGURATION: ResolvedThemeConfiguration = {\r\n theme: DEFAULT_THEME_NAME,\r\n colorMode: 'auto',\r\n allowColorModeToggle: true,\r\n borderRadius: 'medium',\r\n fontScale: 'medium',\r\n spacing: 'medium',\r\n animationLevel: 'medium',\r\n customCSS: null,\r\n lightMode: DEFAULT_THEME_PRESET?.lightMode,\r\n darkMode: DEFAULT_THEME_PRESET?.darkMode,\r\n typography: DEFAULT_THEME_PRESET?.typography ?? null,\r\n}\r\n\r\nfunction resolveBorderRadius(value: unknown): BorderRadiusOption {\r\n if (typeof value === 'string') {\r\n if (BORDER_RADIUS_VALUES.includes(value as BorderRadiusOption)) {\r\n return value as BorderRadiusOption\r\n }\r\n\r\n const legacyMatch = LEGACY_BORDER_RADIUS_ALIASES[value]\r\n if (legacyMatch) {\r\n return legacyMatch\r\n }\r\n }\r\n\r\n return DEFAULT_THEME_CONFIGURATION.borderRadius\r\n}\r\n\r\nfunction isFontScale(value: unknown): value is FontScaleOption {\r\n return typeof value === 'string' && FONT_SCALE_VALUES.includes(value as FontScaleOption)\r\n}\r\n\r\nfunction isSpacing(value: unknown): value is SpacingOption {\r\n return typeof value === 'string' && SPACING_VALUES.includes(value as SpacingOption)\r\n}\r\n\r\nfunction resolveAnimationLevel(value: unknown): AnimationLevelOption {\r\n if (typeof value === 'string') {\r\n if (ANIMATION_LEVEL_VALUES.includes(value as AnimationLevelOption)) {\r\n return value as AnimationLevelOption\r\n }\r\n\r\n const legacyMatch = LEGACY_ANIMATION_ALIASES[value]\r\n if (legacyMatch) {\r\n return legacyMatch\r\n }\r\n }\r\n\r\n return DEFAULT_THEME_CONFIGURATION.animationLevel\r\n}\r\n\r\nfunction isMode(value: unknown): value is ThemeModeOption {\r\n return typeof value === 'string' && MODE_VALUES.includes(value as ThemeModeOption)\r\n}\r\n\r\nexport function resolveThemeConfiguration(\r\n themeConfiguration?: SiteThemeConfiguration | null,\r\n): ResolvedThemeConfiguration {\r\n if (!themeConfiguration) {\r\n return DEFAULT_THEME_CONFIGURATION\r\n }\r\n\r\n const {\r\n theme,\r\n colorMode,\r\n allowColorModeToggle,\r\n borderRadius,\r\n fontScale,\r\n spacing,\r\n animationLevel,\r\n customCSS,\r\n lightMode,\r\n darkMode,\r\n typography,\r\n } = themeConfiguration\r\n\r\n const resolvedTheme =\r\n typeof theme === 'string' && themeIsValid(theme)\r\n ? (theme as ThemeDefaults)\r\n : DEFAULT_THEME_CONFIGURATION.theme\r\n const resolvedMode = isMode(colorMode)\r\n ? (colorMode as Mode)\r\n : DEFAULT_THEME_CONFIGURATION.colorMode\r\n const resolvedBorderRadius = resolveBorderRadius(borderRadius)\r\n const resolvedFontScale = isFontScale(fontScale)\r\n ? fontScale\r\n : DEFAULT_THEME_CONFIGURATION.fontScale\r\n const resolvedSpacing = isSpacing(spacing) ? spacing : DEFAULT_THEME_CONFIGURATION.spacing\r\n const resolvedAnimationLevel = resolveAnimationLevel(animationLevel)\r\n const resolvedAllowToggle =\r\n typeof allowColorModeToggle === 'boolean'\r\n ? allowColorModeToggle\r\n : DEFAULT_THEME_CONFIGURATION.allowColorModeToggle\r\n const resolvedCustomCSS = typeof customCSS === 'string' ? customCSS : null\r\n const preset = defaultThemePresets.find((candidate) => candidate.name === resolvedTheme)\r\n const baseLightMode = preset?.lightMode ?? DEFAULT_THEME_CONFIGURATION.lightMode\r\n const baseDarkMode = preset?.darkMode ?? DEFAULT_THEME_CONFIGURATION.darkMode\r\n const resolvedLightMode = mergeColorModes(baseLightMode, lightMode)\r\n const resolvedDarkMode = mergeColorModes(baseDarkMode, darkMode)\r\n const resolvedTypography =\r\n mergeTypography(preset?.typography, typography) ?? preset?.typography ?? null\r\n\r\n return {\r\n theme: resolvedTheme,\r\n colorMode: resolvedMode,\r\n allowColorModeToggle: resolvedAllowToggle,\r\n borderRadius: resolvedBorderRadius,\r\n fontScale: resolvedFontScale,\r\n spacing: resolvedSpacing,\r\n animationLevel: resolvedAnimationLevel,\r\n customCSS: resolvedCustomCSS,\r\n lightMode: resolvedLightMode,\r\n darkMode: resolvedDarkMode,\r\n typography: resolvedTypography,\r\n }\r\n}\r\n\r\nfunction mergeColorModes(\r\n base?: ThemeColorConfiguration,\r\n overrides?: SiteThemeConfiguration['lightMode'],\r\n): ThemeColorConfiguration | undefined {\r\n const merged: Record<string, string> = {}\r\n\r\n if (base) {\r\n Object.entries(base).forEach(([key, value]) => {\r\n if (typeof value === 'string' && value.trim().length > 0) {\r\n merged[key] = value\r\n }\r\n })\r\n }\r\n\r\n if (overrides) {\r\n Object.entries(overrides).forEach(([key, value]) => {\r\n if (typeof value === 'string' && value.trim().length > 0) {\r\n merged[key] = value\r\n } else if (value === null) {\r\n delete merged[key]\r\n }\r\n })\r\n }\r\n\r\n return Object.keys(merged).length > 0 ? (merged as ThemeColorConfiguration) : undefined\r\n}\r\n\r\nfunction mergeTypography(\r\n base?: ThemeTypographyPreset | null,\r\n overrides?: ThemeTypographyOverride | null,\r\n): ThemeTypographyPreset | null {\r\n if (!base && !overrides) {\r\n return base ?? null\r\n }\r\n\r\n const result: ThemeTypographyPreset = {\r\n fontFamily: base?.fontFamily ?? 'var(--font-geist-sans)',\r\n headingFamily: base?.headingFamily ?? 'var(--font-geist-sans)',\r\n baseFontSize: base?.baseFontSize ?? '16px',\r\n lineHeight: base?.lineHeight ?? '1.6',\r\n fontWeights: { ...(base?.fontWeights ?? {}) },\r\n letterSpacing: { ...(base?.letterSpacing ?? {}) },\r\n }\r\n\r\n if (overrides) {\r\n const resolvedBodyFont = resolveFontSelection(\r\n overrides.bodyFont,\r\n overrides.bodyFontCustom,\r\n base?.fontFamily,\r\n )\r\n if (resolvedBodyFont) {\r\n result.fontFamily = resolvedBodyFont\r\n }\r\n\r\n const resolvedHeadingFont = resolveFontSelection(\r\n overrides.headingFont,\r\n overrides.headingFontCustom,\r\n base?.headingFamily,\r\n )\r\n if (resolvedHeadingFont) {\r\n result.headingFamily = resolvedHeadingFont\r\n }\r\n\r\n if (overrides.baseFontSize && overrides.baseFontSize !== 'preset') {\r\n result.baseFontSize = overrides.baseFontSize\r\n }\r\n\r\n if (overrides.lineHeight && overrides.lineHeight !== 'preset') {\r\n result.lineHeight = overrides.lineHeight\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction resolveFontSelection(\r\n selection?: string | null,\r\n custom?: string | null,\r\n fallback?: string | null,\r\n): string | null {\r\n if (!selection || selection === 'preset') {\r\n return fallback ?? null\r\n }\r\n\r\n if (selection === 'custom') {\r\n const trimmed = custom?.trim()\r\n return trimmed && trimmed.length > 0 ? trimmed : (fallback ?? null)\r\n }\r\n\r\n const mapped = FONT_VALUE_TO_CSS[selection]\r\n return mapped ?? fallback ?? null\r\n}\r\n"],"names":["FONT_VALUE_TO_CSS","themeIsValid","defaultThemePresets","BORDER_RADIUS_VALUES","LEGACY_BORDER_RADIUS_ALIASES","full","FONT_SCALE_VALUES","SPACING_VALUES","ANIMATION_LEVEL_VALUES","LEGACY_ANIMATION_ALIASES","MODE_VALUES","DEFAULT_THEME_NAME","DEFAULT_THEME_PRESET","find","preset","name","DEFAULT_THEME_CONFIGURATION","theme","colorMode","allowColorModeToggle","borderRadius","fontScale","spacing","animationLevel","customCSS","lightMode","darkMode","typography","resolveBorderRadius","value","includes","legacyMatch","isFontScale","isSpacing","resolveAnimationLevel","isMode","resolveThemeConfiguration","themeConfiguration","resolvedTheme","resolvedMode","resolvedBorderRadius","resolvedFontScale","resolvedSpacing","resolvedAnimationLevel","resolvedAllowToggle","resolvedCustomCSS","candidate","baseLightMode","baseDarkMode","resolvedLightMode","mergeColorModes","resolvedDarkMode","resolvedTypography","mergeTypography","base","overrides","merged","Object","entries","forEach","key","trim","length","keys","undefined","result","fontFamily","headingFamily","baseFontSize","lineHeight","fontWeights","letterSpacing","resolvedBodyFont","resolveFontSelection","bodyFont","bodyFontCustom","resolvedHeadingFont","headingFont","headingFontCustom","selection","custom","fallback","trimmed","mapped"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/resolveThemeConfiguration.ts"],"sourcesContent":["import { FONT_VALUE_TO_CSS } from '../constants/themeFonts.js'\r\nimport type { Mode, ThemeDefaults } from '../providers/Theme/types.js'\r\nimport { themeIsValid } from '../providers/Theme/types.js'\r\nimport type { ThemeTypographyPreset } from '../presets.js'\r\nimport { defaultThemePresets } from '../presets.js'\r\n\r\n/**\r\n * Generic theme configuration structure that matches Payload's generated types\r\n * without depending on specific payload-types versions\r\n */\r\ninterface ThemeTypographyOverride {\r\n bodyFont?: string | null\r\n headingFont?: string | null\r\n bodyFontCustom?: string | null\r\n headingFontCustom?: string | null\r\n baseFontSize?: string | null\r\n lineHeight?: string | null\r\n}\r\n\r\n/**\r\n * Generic theme configuration interface that accepts any compatible structure\r\n * This avoids type conflicts between different versions of payload-types\r\n */\r\ninterface GenericThemeConfiguration {\r\n theme?: string | null\r\n colorMode?: string | null\r\n allowColorModeToggle?: boolean | null\r\n borderRadius?: string | null\r\n fontScale?: string | null\r\n spacing?: string | null\r\n animationLevel?: string | null\r\n customCSS?: string | null\r\n lightMode?: {\r\n primary?: string | null\r\n secondary?: string | null\r\n accent?: string | null\r\n neutral?: string | null\r\n success?: string | null\r\n warning?: string | null\r\n error?: string | null\r\n info?: string | null\r\n } | null\r\n darkMode?: {\r\n primary?: string | null\r\n secondary?: string | null\r\n accent?: string | null\r\n neutral?: string | null\r\n success?: string | null\r\n warning?: string | null\r\n error?: string | null\r\n info?: string | null\r\n } | null\r\n typography?: ThemeTypographyOverride | null\r\n}\r\n\r\nconst BORDER_RADIUS_VALUES = ['none', 'small', 'medium', 'large', 'xl'] as const\r\nconst LEGACY_BORDER_RADIUS_ALIASES: Record<string, (typeof BORDER_RADIUS_VALUES)[number]> = {\r\n full: 'xl',\r\n}\r\nconst FONT_SCALE_VALUES = ['small', 'medium', 'large'] as const\r\nconst SPACING_VALUES = ['compact', 'medium', 'spacious'] as const\r\nconst ANIMATION_LEVEL_VALUES = ['none', 'reduced', 'medium', 'high'] as const\r\nconst LEGACY_ANIMATION_ALIASES: Record<string, (typeof ANIMATION_LEVEL_VALUES)[number]> = {\r\n full: 'high',\r\n}\r\nconst MODE_VALUES = ['auto', 'light', 'dark'] as const\r\n\r\ntype BorderRadiusOption = (typeof BORDER_RADIUS_VALUES)[number]\r\ntype FontScaleOption = (typeof FONT_SCALE_VALUES)[number]\r\ntype SpacingOption = (typeof SPACING_VALUES)[number]\r\ntype AnimationLevelOption = (typeof ANIMATION_LEVEL_VALUES)[number]\r\n\r\ntype ThemeModeOption = (typeof MODE_VALUES)[number]\r\n\r\ntype ThemeColorConfiguration = NonNullable<GenericThemeConfiguration['lightMode']>\r\n\r\nconst DEFAULT_THEME_NAME: ThemeDefaults = 'cool'\r\nconst DEFAULT_THEME_PRESET = defaultThemePresets.find(\r\n (preset) => preset.name === DEFAULT_THEME_NAME,\r\n)\r\n\r\nexport interface ResolvedThemeConfiguration {\r\n theme: ThemeDefaults\r\n colorMode: Mode\r\n allowColorModeToggle: boolean\r\n borderRadius: BorderRadiusOption\r\n fontScale: FontScaleOption\r\n spacing: SpacingOption\r\n animationLevel: AnimationLevelOption\r\n customCSS: string | null\r\n lightMode?: ThemeColorConfiguration\r\n darkMode?: ThemeColorConfiguration\r\n typography: ThemeTypographyPreset | null\r\n}\r\n\r\nexport const DEFAULT_THEME_CONFIGURATION: ResolvedThemeConfiguration = {\r\n theme: DEFAULT_THEME_NAME,\r\n colorMode: 'auto',\r\n allowColorModeToggle: true,\r\n borderRadius: 'medium',\r\n fontScale: 'medium',\r\n spacing: 'medium',\r\n animationLevel: 'medium',\r\n customCSS: null,\r\n lightMode: DEFAULT_THEME_PRESET?.lightMode,\r\n darkMode: DEFAULT_THEME_PRESET?.darkMode,\r\n typography: DEFAULT_THEME_PRESET?.typography ?? null,\r\n}\r\n\r\nfunction resolveBorderRadius(value: unknown): BorderRadiusOption {\r\n if (typeof value === 'string') {\r\n if (BORDER_RADIUS_VALUES.includes(value as BorderRadiusOption)) {\r\n return value as BorderRadiusOption\r\n }\r\n\r\n const legacyMatch = LEGACY_BORDER_RADIUS_ALIASES[value]\r\n if (legacyMatch) {\r\n return legacyMatch\r\n }\r\n }\r\n\r\n return DEFAULT_THEME_CONFIGURATION.borderRadius\r\n}\r\n\r\nfunction isFontScale(value: unknown): value is FontScaleOption {\r\n return typeof value === 'string' && FONT_SCALE_VALUES.includes(value as FontScaleOption)\r\n}\r\n\r\nfunction isSpacing(value: unknown): value is SpacingOption {\r\n return typeof value === 'string' && SPACING_VALUES.includes(value as SpacingOption)\r\n}\r\n\r\nfunction resolveAnimationLevel(value: unknown): AnimationLevelOption {\r\n if (typeof value === 'string') {\r\n if (ANIMATION_LEVEL_VALUES.includes(value as AnimationLevelOption)) {\r\n return value as AnimationLevelOption\r\n }\r\n\r\n const legacyMatch = LEGACY_ANIMATION_ALIASES[value]\r\n if (legacyMatch) {\r\n return legacyMatch\r\n }\r\n }\r\n\r\n return DEFAULT_THEME_CONFIGURATION.animationLevel\r\n}\r\n\r\nfunction isMode(value: unknown): value is ThemeModeOption {\r\n return typeof value === 'string' && MODE_VALUES.includes(value as ThemeModeOption)\r\n}\r\n\r\nexport function resolveThemeConfiguration(\r\n themeConfiguration?: GenericThemeConfiguration | null,\r\n): ResolvedThemeConfiguration {\r\n if (!themeConfiguration) {\r\n return DEFAULT_THEME_CONFIGURATION\r\n }\r\n\r\n const {\r\n theme,\r\n colorMode,\r\n allowColorModeToggle,\r\n borderRadius,\r\n fontScale,\r\n spacing,\r\n animationLevel,\r\n customCSS,\r\n lightMode,\r\n darkMode,\r\n typography,\r\n } = themeConfiguration\r\n\r\n const resolvedTheme =\r\n typeof theme === 'string' && themeIsValid(theme)\r\n ? (theme as ThemeDefaults)\r\n : DEFAULT_THEME_CONFIGURATION.theme\r\n const resolvedMode = isMode(colorMode)\r\n ? (colorMode as Mode)\r\n : DEFAULT_THEME_CONFIGURATION.colorMode\r\n const resolvedBorderRadius = resolveBorderRadius(borderRadius)\r\n const resolvedFontScale = isFontScale(fontScale)\r\n ? fontScale\r\n : DEFAULT_THEME_CONFIGURATION.fontScale\r\n const resolvedSpacing = isSpacing(spacing) ? spacing : DEFAULT_THEME_CONFIGURATION.spacing\r\n const resolvedAnimationLevel = resolveAnimationLevel(animationLevel)\r\n const resolvedAllowToggle =\r\n typeof allowColorModeToggle === 'boolean'\r\n ? allowColorModeToggle\r\n : DEFAULT_THEME_CONFIGURATION.allowColorModeToggle\r\n const resolvedCustomCSS = typeof customCSS === 'string' ? customCSS : null\r\n const preset = defaultThemePresets.find((candidate) => candidate.name === resolvedTheme)\r\n const baseLightMode = preset?.lightMode ?? DEFAULT_THEME_CONFIGURATION.lightMode\r\n const baseDarkMode = preset?.darkMode ?? DEFAULT_THEME_CONFIGURATION.darkMode\r\n const resolvedLightMode = mergeColorModes(baseLightMode, lightMode)\r\n const resolvedDarkMode = mergeColorModes(baseDarkMode, darkMode)\r\n const resolvedTypography =\r\n mergeTypography(preset?.typography, typography) ?? preset?.typography ?? null\r\n\r\n return {\r\n theme: resolvedTheme,\r\n colorMode: resolvedMode,\r\n allowColorModeToggle: resolvedAllowToggle,\r\n borderRadius: resolvedBorderRadius,\r\n fontScale: resolvedFontScale,\r\n spacing: resolvedSpacing,\r\n animationLevel: resolvedAnimationLevel,\r\n customCSS: resolvedCustomCSS,\r\n lightMode: resolvedLightMode,\r\n darkMode: resolvedDarkMode,\r\n typography: resolvedTypography,\r\n }\r\n}\r\n\r\nfunction mergeColorModes(\r\n base?: ThemeColorConfiguration,\r\n overrides?: GenericThemeConfiguration['lightMode'],\r\n): ThemeColorConfiguration | undefined {\r\n const merged: Record<string, string> = {}\r\n\r\n if (base) {\r\n Object.entries(base).forEach(([key, value]) => {\r\n if (typeof value === 'string' && value.trim().length > 0) {\r\n merged[key] = value\r\n }\r\n })\r\n }\r\n\r\n if (overrides) {\r\n Object.entries(overrides).forEach(([key, value]) => {\r\n if (typeof value === 'string' && value.trim().length > 0) {\r\n merged[key] = value\r\n } else if (value === null) {\r\n delete merged[key]\r\n }\r\n })\r\n }\r\n\r\n return Object.keys(merged).length > 0 ? (merged as ThemeColorConfiguration) : undefined\r\n}\r\n\r\nfunction mergeTypography(\r\n base?: ThemeTypographyPreset | null,\r\n overrides?: ThemeTypographyOverride | null,\r\n): ThemeTypographyPreset | null {\r\n if (!base && !overrides) {\r\n return base ?? null\r\n }\r\n\r\n const result: ThemeTypographyPreset = {\r\n fontFamily: base?.fontFamily ?? 'var(--font-geist-sans)',\r\n headingFamily: base?.headingFamily ?? 'var(--font-geist-sans)',\r\n baseFontSize: base?.baseFontSize ?? '16px',\r\n lineHeight: base?.lineHeight ?? '1.6',\r\n fontWeights: { ...(base?.fontWeights ?? {}) },\r\n letterSpacing: { ...(base?.letterSpacing ?? {}) },\r\n }\r\n\r\n if (overrides) {\r\n const resolvedBodyFont = resolveFontSelection(\r\n overrides.bodyFont,\r\n overrides.bodyFontCustom,\r\n base?.fontFamily,\r\n )\r\n if (resolvedBodyFont) {\r\n result.fontFamily = resolvedBodyFont\r\n }\r\n\r\n const resolvedHeadingFont = resolveFontSelection(\r\n overrides.headingFont,\r\n overrides.headingFontCustom,\r\n base?.headingFamily,\r\n )\r\n if (resolvedHeadingFont) {\r\n result.headingFamily = resolvedHeadingFont\r\n }\r\n\r\n if (overrides.baseFontSize && overrides.baseFontSize !== 'preset') {\r\n result.baseFontSize = overrides.baseFontSize\r\n }\r\n\r\n if (overrides.lineHeight && overrides.lineHeight !== 'preset') {\r\n result.lineHeight = overrides.lineHeight\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction resolveFontSelection(\r\n selection?: string | null,\r\n custom?: string | null,\r\n fallback?: string | null,\r\n): string | null {\r\n if (!selection || selection === 'preset') {\r\n return fallback ?? null\r\n }\r\n\r\n if (selection === 'custom') {\r\n const trimmed = custom?.trim()\r\n return trimmed && trimmed.length > 0 ? trimmed : (fallback ?? null)\r\n }\r\n\r\n const mapped = FONT_VALUE_TO_CSS[selection]\r\n return mapped ?? fallback ?? null\r\n}\r\n"],"names":["FONT_VALUE_TO_CSS","themeIsValid","defaultThemePresets","BORDER_RADIUS_VALUES","LEGACY_BORDER_RADIUS_ALIASES","full","FONT_SCALE_VALUES","SPACING_VALUES","ANIMATION_LEVEL_VALUES","LEGACY_ANIMATION_ALIASES","MODE_VALUES","DEFAULT_THEME_NAME","DEFAULT_THEME_PRESET","find","preset","name","DEFAULT_THEME_CONFIGURATION","theme","colorMode","allowColorModeToggle","borderRadius","fontScale","spacing","animationLevel","customCSS","lightMode","darkMode","typography","resolveBorderRadius","value","includes","legacyMatch","isFontScale","isSpacing","resolveAnimationLevel","isMode","resolveThemeConfiguration","themeConfiguration","resolvedTheme","resolvedMode","resolvedBorderRadius","resolvedFontScale","resolvedSpacing","resolvedAnimationLevel","resolvedAllowToggle","resolvedCustomCSS","candidate","baseLightMode","baseDarkMode","resolvedLightMode","mergeColorModes","resolvedDarkMode","resolvedTypography","mergeTypography","base","overrides","merged","Object","entries","forEach","key","trim","length","keys","undefined","result","fontFamily","headingFamily","baseFontSize","lineHeight","fontWeights","letterSpacing","resolvedBodyFont","resolveFontSelection","bodyFont","bodyFontCustom","resolvedHeadingFont","headingFont","headingFontCustom","selection","custom","fallback","trimmed","mapped"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,6BAA4B;AAE9D,SAASC,YAAY,QAAQ,8BAA6B;AAE1D,SAASC,mBAAmB,QAAQ,gBAAe;AAmDnD,MAAMC,uBAAuB;IAAC;IAAQ;IAAS;IAAU;IAAS;CAAK;AACvE,MAAMC,+BAAsF;IAC1FC,MAAM;AACR;AACA,MAAMC,oBAAoB;IAAC;IAAS;IAAU;CAAQ;AACtD,MAAMC,iBAAiB;IAAC;IAAW;IAAU;CAAW;AACxD,MAAMC,yBAAyB;IAAC;IAAQ;IAAW;IAAU;CAAO;AACpE,MAAMC,2BAAoF;IACxFJ,MAAM;AACR;AACA,MAAMK,cAAc;IAAC;IAAQ;IAAS;CAAO;AAW7C,MAAMC,qBAAoC;AAC1C,MAAMC,uBAAuBV,oBAAoBW,IAAI,CACnD,CAACC,SAAWA,OAAOC,IAAI,KAAKJ;AAiB9B,OAAO,MAAMK,8BAA0D;IACrEC,OAAON;IACPO,WAAW;IACXC,sBAAsB;IACtBC,cAAc;IACdC,WAAW;IACXC,SAAS;IACTC,gBAAgB;IAChBC,WAAW;IACXC,WAAWb,sBAAsBa;IACjCC,UAAUd,sBAAsBc;IAChCC,YAAYf,sBAAsBe,cAAc;AAClD,EAAC;AAED,SAASC,oBAAoBC,KAAc;IACzC,IAAI,OAAOA,UAAU,UAAU;QAC7B,IAAI1B,qBAAqB2B,QAAQ,CAACD,QAA8B;YAC9D,OAAOA;QACT;QAEA,MAAME,cAAc3B,4BAA4B,CAACyB,MAAM;QACvD,IAAIE,aAAa;YACf,OAAOA;QACT;IACF;IAEA,OAAOf,4BAA4BI,YAAY;AACjD;AAEA,SAASY,YAAYH,KAAc;IACjC,OAAO,OAAOA,UAAU,YAAYvB,kBAAkBwB,QAAQ,CAACD;AACjE;AAEA,SAASI,UAAUJ,KAAc;IAC/B,OAAO,OAAOA,UAAU,YAAYtB,eAAeuB,QAAQ,CAACD;AAC9D;AAEA,SAASK,sBAAsBL,KAAc;IAC3C,IAAI,OAAOA,UAAU,UAAU;QAC7B,IAAIrB,uBAAuBsB,QAAQ,CAACD,QAAgC;YAClE,OAAOA;QACT;QAEA,MAAME,cAActB,wBAAwB,CAACoB,MAAM;QACnD,IAAIE,aAAa;YACf,OAAOA;QACT;IACF;IAEA,OAAOf,4BAA4BO,cAAc;AACnD;AAEA,SAASY,OAAON,KAAc;IAC5B,OAAO,OAAOA,UAAU,YAAYnB,YAAYoB,QAAQ,CAACD;AAC3D;AAEA,OAAO,SAASO,0BACdC,kBAAqD;IAErD,IAAI,CAACA,oBAAoB;QACvB,OAAOrB;IACT;IAEA,MAAM,EACJC,KAAK,EACLC,SAAS,EACTC,oBAAoB,EACpBC,YAAY,EACZC,SAAS,EACTC,OAAO,EACPC,cAAc,EACdC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,UAAU,EACX,GAAGU;IAEJ,MAAMC,gBACJ,OAAOrB,UAAU,YAAYhB,aAAagB,SACrCA,QACDD,4BAA4BC,KAAK;IACvC,MAAMsB,eAAeJ,OAAOjB,aACvBA,YACDF,4BAA4BE,SAAS;IACzC,MAAMsB,uBAAuBZ,oBAAoBR;IACjD,MAAMqB,oBAAoBT,YAAYX,aAClCA,YACAL,4BAA4BK,SAAS;IACzC,MAAMqB,kBAAkBT,UAAUX,WAAWA,UAAUN,4BAA4BM,OAAO;IAC1F,MAAMqB,yBAAyBT,sBAAsBX;IACrD,MAAMqB,sBACJ,OAAOzB,yBAAyB,YAC5BA,uBACAH,4BAA4BG,oBAAoB;IACtD,MAAM0B,oBAAoB,OAAOrB,cAAc,WAAWA,YAAY;IACtE,MAAMV,SAASZ,oBAAoBW,IAAI,CAAC,CAACiC,YAAcA,UAAU/B,IAAI,KAAKuB;IAC1E,MAAMS,gBAAgBjC,QAAQW,aAAaT,4BAA4BS,SAAS;IAChF,MAAMuB,eAAelC,QAAQY,YAAYV,4BAA4BU,QAAQ;IAC7E,MAAMuB,oBAAoBC,gBAAgBH,eAAetB;IACzD,MAAM0B,mBAAmBD,gBAAgBF,cAActB;IACvD,MAAM0B,qBACJC,gBAAgBvC,QAAQa,YAAYA,eAAeb,QAAQa,cAAc;IAE3E,OAAO;QACLV,OAAOqB;QACPpB,WAAWqB;QACXpB,sBAAsByB;QACtBxB,cAAcoB;QACdnB,WAAWoB;QACXnB,SAASoB;QACTnB,gBAAgBoB;QAChBnB,WAAWqB;QACXpB,WAAWwB;QACXvB,UAAUyB;QACVxB,YAAYyB;IACd;AACF;AAEA,SAASF,gBACPI,IAA8B,EAC9BC,SAAkD;IAElD,MAAMC,SAAiC,CAAC;IAExC,IAAIF,MAAM;QACRG,OAAOC,OAAO,CAACJ,MAAMK,OAAO,CAAC,CAAC,CAACC,KAAK/B,MAAM;YACxC,IAAI,OAAOA,UAAU,YAAYA,MAAMgC,IAAI,GAAGC,MAAM,GAAG,GAAG;gBACxDN,MAAM,CAACI,IAAI,GAAG/B;YAChB;QACF;IACF;IAEA,IAAI0B,WAAW;QACbE,OAAOC,OAAO,CAACH,WAAWI,OAAO,CAAC,CAAC,CAACC,KAAK/B,MAAM;YAC7C,IAAI,OAAOA,UAAU,YAAYA,MAAMgC,IAAI,GAAGC,MAAM,GAAG,GAAG;gBACxDN,MAAM,CAACI,IAAI,GAAG/B;YAChB,OAAO,IAAIA,UAAU,MAAM;gBACzB,OAAO2B,MAAM,CAACI,IAAI;YACpB;QACF;IACF;IAEA,OAAOH,OAAOM,IAAI,CAACP,QAAQM,MAAM,GAAG,IAAKN,SAAqCQ;AAChF;AAEA,SAASX,gBACPC,IAAmC,EACnCC,SAA0C;IAE1C,IAAI,CAACD,QAAQ,CAACC,WAAW;QACvB,OAAOD,QAAQ;IACjB;IAEA,MAAMW,SAAgC;QACpCC,YAAYZ,MAAMY,cAAc;QAChCC,eAAeb,MAAMa,iBAAiB;QACtCC,cAAcd,MAAMc,gBAAgB;QACpCC,YAAYf,MAAMe,cAAc;QAChCC,aAAa;YAAE,GAAIhB,MAAMgB,eAAe,CAAC,CAAC;QAAE;QAC5CC,eAAe;YAAE,GAAIjB,MAAMiB,iBAAiB,CAAC,CAAC;QAAE;IAClD;IAEA,IAAIhB,WAAW;QACb,MAAMiB,mBAAmBC,qBACvBlB,UAAUmB,QAAQ,EAClBnB,UAAUoB,cAAc,EACxBrB,MAAMY;QAER,IAAIM,kBAAkB;YACpBP,OAAOC,UAAU,GAAGM;QACtB;QAEA,MAAMI,sBAAsBH,qBAC1BlB,UAAUsB,WAAW,EACrBtB,UAAUuB,iBAAiB,EAC3BxB,MAAMa;QAER,IAAIS,qBAAqB;YACvBX,OAAOE,aAAa,GAAGS;QACzB;QAEA,IAAIrB,UAAUa,YAAY,IAAIb,UAAUa,YAAY,KAAK,UAAU;YACjEH,OAAOG,YAAY,GAAGb,UAAUa,YAAY;QAC9C;QAEA,IAAIb,UAAUc,UAAU,IAAId,UAAUc,UAAU,KAAK,UAAU;YAC7DJ,OAAOI,UAAU,GAAGd,UAAUc,UAAU;QAC1C;IACF;IAEA,OAAOJ;AACT;AAEA,SAASQ,qBACPM,SAAyB,EACzBC,MAAsB,EACtBC,QAAwB;IAExB,IAAI,CAACF,aAAaA,cAAc,UAAU;QACxC,OAAOE,YAAY;IACrB;IAEA,IAAIF,cAAc,UAAU;QAC1B,MAAMG,UAAUF,QAAQnB;QACxB,OAAOqB,WAAWA,QAAQpB,MAAM,GAAG,IAAIoB,UAAWD,YAAY;IAChE;IAEA,MAAME,SAASnF,iBAAiB,CAAC+E,UAAU;IAC3C,OAAOI,UAAUF,YAAY;AAC/B"}
|