@kilivi/payloadcms-theme-management 1.0.15 → 1.1.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/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
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
4
 
5
- ## 🎉 Version 0.6.0 - New Features!
5
+ ## 🎉 Version 1.0.2 - Production Ready!
6
6
 
7
7
  - **✅ Standalone Global Support** - Create separate appearance settings as a global
8
8
  - **✅ Auto-Populate Light/Dark Colors** - Theme selection hook automatically fills color fields
9
- - **✅ Multi-Tenant Ready** - Full support for multi-tenant applications
10
- - **✅ Cleaner Data Structure** - Fixed nesting issues for better API integration
9
+ - **✅ Multi-Tenant Ready** - Full support for multi-tenant applications with isolated themes
10
+ - **✅ Cache Invalidation** - Automatic cache invalidation after saving appearance settings
11
11
  - **✅ Live Theme Preview** - Real-time theme preview in admin panel at `/admin/theme-preview`
12
12
  - **✅ Professional Color Picker** - Enhanced color picker with react-colorful library
13
13
  - **✅ shadcn/ui Compatible** - Works with https://ui.shadcn.com/themes
@@ -147,7 +147,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
147
147
  const payload = await getPayload({ config: configPromise })
148
148
 
149
149
  // Fetch from standalone global
150
- const appearanceSettings = await payload.globals.findBySlug({
150
+ const appearanceSettings = await payload.findGlobal({
151
151
  slug: 'appearance-settings', // Your standalone global slug
152
152
  })
153
153
 
@@ -162,6 +162,62 @@ export default async function RootLayout({ children }: { children: React.ReactNo
162
162
  }
163
163
  ```
164
164
 
165
+ #### Cache Optimization with Next.js
166
+
167
+ When using standalone global, the plugin automatically invalidates cache after saving appearance settings using the tag `global_appearance-settings` (or `global_{your-slug}` if you use a custom slug).
168
+
169
+ You can optimize your theme fetching by adding cache tags to your layout:
170
+
171
+ ```tsx
172
+ // app/layout.tsx
173
+ import { ServerThemeInjector } from '@kilivi/payloadcms-theme-management/server'
174
+ import configPromise from '@payload-config'
175
+ import { getPayload } from 'payload'
176
+ import { unstable_cache } from 'next/cache'
177
+
178
+ // Create cached theme fetcher with automatic invalidation
179
+ const getCachedTheme = unstable_cache(
180
+ async () => {
181
+ const payload = await getPayload({ config: configPromise })
182
+ const appearanceSettings = await payload.findGlobal({
183
+ slug: 'appearance-settings',
184
+ })
185
+ return appearanceSettings?.themeConfiguration
186
+ },
187
+ ['appearance-settings-theme'],
188
+ {
189
+ tags: ['global_appearance-settings'], // Matches the auto-invalidation tag from plugin
190
+ revalidate: 3600, // Optional: revalidate every hour as fallback
191
+ },
192
+ )
193
+
194
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
195
+ const themeConfiguration = await getCachedTheme()
196
+
197
+ return (
198
+ <html lang="en">
199
+ <head>
200
+ <ServerThemeInjector themeConfiguration={themeConfiguration} />
201
+ </head>
202
+ <body>{children}</body>
203
+ </html>
204
+ )
205
+ }
206
+ ```
207
+
208
+ **Benefits:**
209
+
210
+ - ⚡ Theme settings are cached and reused across requests
211
+ - 🔄 Cache automatically invalidates when you save appearance settings in admin
212
+ - 🚀 Reduces database queries and improves performance
213
+
214
+ **Cache Tag Format:**
215
+
216
+ - Default: `global_appearance-settings`
217
+ - Custom slug: `global_{standaloneCollectionSlug}`
218
+
219
+ > **Note:** The cache invalidation hook is only available for standalone global mode. When using the tab mode (default), implement your own caching strategy based on your collection structure.
220
+
165
221
  ### 3. Use Theme Variables in Your CSS
166
222
 
167
223
  The plugin injects CSS custom properties you can use:
@@ -196,7 +252,9 @@ Or with Tailwind CSS:
196
252
  | `includeCustomCSS` | `boolean` | `true` | Allow custom CSS injection |
197
253
  | `includeBrandIdentity` | `boolean` | `false` | Show brand identity fields |
198
254
  | `enableAdvancedFeatures` | `boolean` | `true` | Enable advanced customization |
199
- | `enableLogging` | `boolean` | `false` | Log plugin actions to console |
255
+ | `enableLogging` | `boolean` | `false` | Log plugin actions to console (includes cache invalidation logs) |
256
+
257
+ **Cache Invalidation Note:** When using `useStandaloneCollection: true`, the plugin automatically invalidates the cache tag `global_{standaloneCollectionSlug}` after saving appearance settings. Use `unstable_cache` with matching tags for optimal performance.
200
258
 
201
259
  ## Available Theme Presets
202
260
 
@@ -720,37 +778,52 @@ export default {
720
778
 
721
779
  ### Fetching Theme Configuration
722
780
 
781
+ The recommended way to fetch theme configuration is using Payload's native API with Next.js caching:
782
+
723
783
  ```typescript
724
- import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
784
+ import configPromise from '@payload-config'
785
+ import { getPayload } from 'payload'
786
+ import { unstable_cache } from 'next/cache'
787
+
788
+ // Cached theme fetcher with automatic invalidation
789
+ const getCachedTheme = unstable_cache(
790
+ async () => {
791
+ const payload = await getPayload({ config: configPromise })
792
+ const global = await payload.findGlobal({
793
+ slug: 'appearance-settings', // or 'your-global-slug'
794
+ })
795
+ return global?.themeConfiguration
796
+ },
797
+ ['appearance-settings'],
798
+ {
799
+ tags: ['global_appearance-settings'],
800
+ revalidate: 3600, // Revalidate every hour
801
+ },
802
+ )
725
803
 
726
- // Default: fetch from 'site-settings' collection
727
- const theme = await fetchThemeConfiguration()
804
+ // Use in your layout
805
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
806
+ const themeConfiguration = await getCachedTheme()
807
+ // Use themeConfiguration...
808
+ }
809
+ ```
728
810
 
729
- // Fetch from standalone global
730
- const theme = await fetchThemeConfiguration({
731
- collectionSlug: 'appearance-settings', // Your standalone global slug
732
- useGlobal: true, // Set to true when using standalone global
733
- depth: 2,
734
- locale: 'en',
735
- })
811
+ **Legacy Helper (still available):**
812
+
813
+ If you need the helper function, `fetchThemeConfiguration` is still exported:
736
814
 
737
- // With multi-tenant support
815
+ ```typescript
816
+ import { fetchThemeConfiguration } from '@kilivi/payloadcms-theme-management'
817
+
818
+ // Simple fetch without caching
738
819
  const theme = await fetchThemeConfiguration({
739
- tenantSlug: 'tenant-123', // Optional: tenant identifier
740
820
  collectionSlug: 'appearance-settings',
741
821
  useGlobal: true,
742
- depth: 2,
743
- })
744
-
745
- // From collection (default behavior)
746
- const theme = await fetchThemeConfiguration({
747
- collectionSlug: 'site-settings', // Collection slug
748
- useGlobal: false, // Use collection endpoint
749
- depth: 2,
750
- locale: 'en',
751
822
  })
752
823
  ```
753
824
 
825
+ However, prefer the native Payload API + `unstable_cache` approach for better performance and automatic invalidation support.
826
+
754
827
  ### Custom Theme Preset
755
828
 
756
829
  ```typescript
@@ -906,7 +979,15 @@ Created for Payload CMS v3 applications.
906
979
 
907
980
  ## Changelog
908
981
 
909
- ### v0.6.0 (Latest)
982
+ ### v1.0.2 (Latest)
983
+
984
+ - ✅ **Added:** Automatic cache invalidation for standalone globals
985
+ - ✅ **Fixed:** Correct Payload CMS v3 API usage (findGlobal instead of non-existent findBySlug)
986
+ - ✅ **Improved:** Multi-tenant documentation with proper examples
987
+ - ✅ **Improved:** Cache optimization examples with unstable_cache
988
+ - ✅ **Updated:** README and Multi-Tenant guide with accurate API methods
989
+
990
+ ### v1.0.0
910
991
 
911
992
  - ✅ **Added:** Standalone Global Support — create separate appearance settings as a global
912
993
  - ✅ **Added:** Auto-populate Light/Dark colors when selecting a theme
@@ -916,20 +997,10 @@ Created for Payload CMS v3 applications.
916
997
  - ✅ **Added:** Professional color picker (react-colorful)
917
998
  - ✅ **Improved:** shadcn/ui and TweakCN compatibility
918
999
  - ✅ **Improved:** SSR theme injection, zero FOUC, and performance optimizations
919
- - ✅ **Updated:** Documentation and examples
920
-
921
- ### v0.1.9
922
-
923
- - ✅ **Fixed:** Server/client component separation
1000
+ - ✅ **Added:** Server/client component separation
924
1001
  - ✅ **Added:** `server-only` package to prevent bundling errors
925
1002
  - ✅ **Changed:** `ServerThemeInjector` now exported from `/server` entry
926
- - ✅ **Added:** Comprehensive documentation
927
-
928
- ### v0.1.7
929
-
930
- - ✅ **Fixed:** ESM import resolution with `.js` extensions
931
- - ✅ **Added:** SWC + TypeScript build pipeline
932
1003
 
933
- ### v0.1.5
1004
+ ### v0.6.0
934
1005
 
935
- - Initial release with theme management features
1006
+ - Initial release with core theming functionality
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeColorPickerField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeColorPickerField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAiGvD,QAAA,MAAM,qBAAqB,EAAE,wBAsI5B,CAAA;AAED,eAAe,qBAAqB,CAAA"}
1
+ {"version":3,"file":"ThemeColorPickerField.d.ts","sourceRoot":"","sources":["../../src/fields/ThemeColorPickerField.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAmCvD,QAAA,MAAM,qBAAqB,EAAE,wBAsI5B,CAAA;AAED,eAAe,qBAAqB,CAAA"}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useField } from '@payloadcms/ui';
4
+ import Color from 'color';
4
5
  import { HexColorInput, HexColorPicker } from 'react-colorful';
5
6
  import { useCallback, useEffect, useRef, useState } from 'react';
6
7
  // Import CSS for styling
@@ -25,60 +26,7 @@ function resolveLocalizedValue(value, fallback) {
25
26
  * Convert various color formats to HEX for the color picker
26
27
  */ function toHex(color) {
27
28
  if (!color) return '#000000';
28
- // Already hex
29
- if (color.startsWith('#')) return color;
30
- // OKLCH format - extract hue and convert to approximate hex
31
- if (color.startsWith('oklch')) {
32
- // For now, return a default - proper conversion requires color-conversion library
33
- return '#3b82f6' // blue-500 as default
34
- ;
35
- }
36
- // HSL format
37
- if (color.startsWith('hsl')) {
38
- const regex = /hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/;
39
- const match = regex.exec(color);
40
- if (match) {
41
- const [, h, s, l] = match.map(Number);
42
- return hslToHex(h, s, l);
43
- }
44
- }
45
- return '#000000';
46
- }
47
- /**
48
- * Convert HSL to HEX
49
- */ function hslToHex(h, s, l) {
50
- s /= 100;
51
- l /= 100;
52
- const c = (1 - Math.abs(2 * l - 1)) * s;
53
- const x = c * (1 - Math.abs(h / 60 % 2 - 1));
54
- const m = l - c / 2;
55
- let r = 0;
56
- let g = 0;
57
- let b = 0;
58
- if (h >= 0 && h < 60) {
59
- r = c;
60
- g = x;
61
- } else if (h >= 60 && h < 120) {
62
- r = x;
63
- g = c;
64
- } else if (h >= 120 && h < 180) {
65
- g = c;
66
- b = x;
67
- } else if (h >= 180 && h < 240) {
68
- g = x;
69
- b = c;
70
- } else if (h >= 240 && h < 300) {
71
- r = x;
72
- b = c;
73
- } else if (h >= 300 && h < 360) {
74
- r = c;
75
- b = x;
76
- }
77
- const toHex = (n)=>{
78
- const hex = Math.round((n + m) * 255).toString(16);
79
- return hex.length === 1 ? '0' + hex : hex;
80
- };
81
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
29
+ return Color(color).hex().toString();
82
30
  }
83
31
  const ThemeColorPickerField = ({ field, path })=>{
84
32
  const { value, setValue } = useField({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport type { TextFieldClientComponent } from 'payload'\nimport { HexColorInput, HexColorPicker } from 'react-colorful'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n// Import CSS for styling\nif (typeof window !== 'undefined') {\n // @ts-expect-error - Dynamic CSS import\n import('./ThemeColorPickerField.css').catch(() => {})\n}\n\nfunction resolveLocalizedValue(value: unknown, fallback: string) {\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'object' && value !== null) {\n const localized = value as Record<string, unknown>\n const resolved = localized.en ?? localized.cs ?? localized['']\n if (typeof resolved === 'string') {\n return resolved\n }\n }\n\n return fallback\n}\n\n/**\n * Convert various color formats to HEX for the color picker\n */\nfunction toHex(color: string): string {\n if (!color) return '#000000'\n\n // Already hex\n if (color.startsWith('#')) return color\n\n // OKLCH format - extract hue and convert to approximate hex\n if (color.startsWith('oklch')) {\n // For now, return a default - proper conversion requires color-conversion library\n return '#3b82f6' // blue-500 as default\n }\n\n // HSL format\n if (color.startsWith('hsl')) {\n const regex = /hsl\\((\\d+),\\s*(\\d+)%,\\s*(\\d+)%\\)/\n const match = regex.exec(color)\n if (match) {\n const [, h, s, l] = match.map(Number)\n return hslToHex(h, s, l)\n }\n }\n\n return '#000000'\n}\n\n/**\n * Convert HSL to HEX\n */\nfunction hslToHex(h: number, s: number, l: number): string {\n s /= 100\n l /= 100\n\n const c = (1 - Math.abs(2 * l - 1)) * s\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = l - c / 2\n\n let r = 0\n let g = 0\n let b = 0\n\n if (h >= 0 && h < 60) {\n r = c\n g = x\n } else if (h >= 60 && h < 120) {\n r = x\n g = c\n } else if (h >= 120 && h < 180) {\n g = c\n b = x\n } else if (h >= 180 && h < 240) {\n g = x\n b = c\n } else if (h >= 240 && h < 300) {\n r = x\n b = c\n } else if (h >= 300 && h < 360) {\n r = c\n b = x\n }\n\n const toHex = (n: number) => {\n const hex = Math.round((n + m) * 255).toString(16)\n return hex.length === 1 ? '0' + hex : hex\n }\n\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\nconst ThemeColorPickerField: TextFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [localValue, setLocalValue] = useState(value || '')\n const [showPicker, setShowPicker] = useState(false)\n const [hexValue, setHexValue] = useState(toHex(value || ''))\n const pickerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n setLocalValue(value || '')\n setHexValue(toHex(value || ''))\n }, [value])\n\n // Close picker when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {\n setShowPicker(false)\n }\n }\n\n if (showPicker) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [showPicker])\n\n const handleHexChange = useCallback(\n (hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n setValue(hex)\n },\n [setValue],\n )\n\n const handleTextChange = useCallback(\n (nextValue: string) => {\n setLocalValue(nextValue)\n setValue(nextValue)\n setHexValue(toHex(nextValue))\n },\n [setValue],\n )\n\n const fieldName = path.split('.').pop() || ''\n const label = resolveLocalizedValue(field.label, fieldName)\n const description = resolveLocalizedValue(field.admin?.description, '')\n\n return (\n <div className=\"theme-color-picker-field\">\n <div className=\"field-header\">\n <label className=\"field-label\">{label}</label>\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n\n <div className=\"color-picker-container\">\n <div className=\"color-input-row\">\n <button\n type=\"button\"\n className=\"color-swatch-btn\"\n style={{ backgroundColor: hexValue }}\n onClick={() => setShowPicker(!showPicker)}\n aria-label=\"Open color picker\"\n title={hexValue}\n />\n <input\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleTextChange(event.target.value)}\n placeholder=\"oklch(...) or #hex\"\n className=\"color-text-input\"\n />\n </div>\n\n {showPicker && (\n <div ref={pickerRef} className=\"color-picker-popover\">\n <div className=\"picker-header\">\n <span className=\"picker-title\">Color Picker</span>\n <button\n type=\"button\"\n onClick={() => setShowPicker(false)}\n className=\"picker-close-btn\"\n aria-label=\"Close\"\n >\n ✕\n </button>\n </div>\n\n <HexColorPicker color={hexValue} onChange={handleHexChange} className=\"hex-picker\" />\n\n <div className=\"hex-input-row\">\n <HexColorInput\n color={hexValue}\n onChange={handleHexChange}\n prefixed\n placeholder=\"#000000\"\n className=\"hex-input\"\n />\n </div>\n\n <div className=\"color-swatches\">\n <div className=\"swatches-label\">Quick Colors</div>\n <div className=\"swatches-grid\">\n {[\n '#ef4444',\n '#f97316',\n '#f59e0b',\n '#84cc16',\n '#10b981',\n '#06b6d4',\n '#3b82f6',\n '#8b5cf6',\n '#ec4899',\n '#64748b',\n '#000000',\n '#ffffff',\n ].map((color) => (\n <button\n key={color}\n type=\"button\"\n onClick={() => handleHexChange(color)}\n className={`swatch-btn ${hexValue.toLowerCase() === color.toLowerCase() ? 'selected' : ''}`}\n style={{ backgroundColor: color }}\n aria-label={`Select ${color}`}\n title={color}\n />\n ))}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","HexColorInput","HexColorPicker","useCallback","useEffect","useRef","useState","window","catch","resolveLocalizedValue","value","fallback","localized","resolved","en","cs","toHex","color","startsWith","regex","match","exec","h","s","l","map","Number","hslToHex","c","Math","abs","x","m","r","g","b","n","hex","round","toString","length","ThemeColorPickerField","field","path","setValue","localValue","setLocalValue","showPicker","setShowPicker","hexValue","setHexValue","pickerRef","handleClickOutside","event","current","contains","target","document","addEventListener","removeEventListener","handleHexChange","handleTextChange","nextValue","fieldName","split","pop","label","description","admin","div","className","button","type","style","backgroundColor","onClick","aria-label","title","input","onChange","placeholder","ref","span","prefixed","toLowerCase"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/fields/ThemeColorPickerField.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport Color from 'color'\nimport type { TextFieldClientComponent } from 'payload'\nimport { HexColorInput, HexColorPicker } from 'react-colorful'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n// Import CSS for styling\nif (typeof window !== 'undefined') {\n // @ts-expect-error - Dynamic CSS import\n import('./ThemeColorPickerField.css').catch(() => {})\n}\n\nfunction resolveLocalizedValue(value: unknown, fallback: string) {\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'object' && value !== null) {\n const localized = value as Record<string, unknown>\n const resolved = localized.en ?? localized.cs ?? localized['']\n if (typeof resolved === 'string') {\n return resolved\n }\n }\n\n return fallback\n}\n\n/**\n * Convert various color formats to HEX for the color picker\n */\nfunction toHex(color: string): string {\n if (!color) return '#000000'\n\n return Color(color).hex().toString()\n}\n\nconst ThemeColorPickerField: TextFieldClientComponent = ({ field, path }) => {\n const { value, setValue } = useField<string>({ path })\n const [localValue, setLocalValue] = useState(value || '')\n const [showPicker, setShowPicker] = useState(false)\n const [hexValue, setHexValue] = useState(toHex(value || ''))\n const pickerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n setLocalValue(value || '')\n setHexValue(toHex(value || ''))\n }, [value])\n\n // Close picker when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {\n setShowPicker(false)\n }\n }\n\n if (showPicker) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [showPicker])\n\n const handleHexChange = useCallback(\n (hex: string) => {\n setHexValue(hex)\n setLocalValue(hex)\n setValue(hex)\n },\n [setValue],\n )\n\n const handleTextChange = useCallback(\n (nextValue: string) => {\n setLocalValue(nextValue)\n setValue(nextValue)\n setHexValue(toHex(nextValue))\n },\n [setValue],\n )\n\n const fieldName = path.split('.').pop() || ''\n const label = resolveLocalizedValue(field.label, fieldName)\n const description = resolveLocalizedValue(field.admin?.description, '')\n\n return (\n <div className=\"theme-color-picker-field\">\n <div className=\"field-header\">\n <label className=\"field-label\">{label}</label>\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n\n <div className=\"color-picker-container\">\n <div className=\"color-input-row\">\n <button\n type=\"button\"\n className=\"color-swatch-btn\"\n style={{ backgroundColor: hexValue }}\n onClick={() => setShowPicker(!showPicker)}\n aria-label=\"Open color picker\"\n title={hexValue}\n />\n <input\n type=\"text\"\n value={localValue || ''}\n onChange={(event) => handleTextChange(event.target.value)}\n placeholder=\"oklch(...) or #hex\"\n className=\"color-text-input\"\n />\n </div>\n\n {showPicker && (\n <div ref={pickerRef} className=\"color-picker-popover\">\n <div className=\"picker-header\">\n <span className=\"picker-title\">Color Picker</span>\n <button\n type=\"button\"\n onClick={() => setShowPicker(false)}\n className=\"picker-close-btn\"\n aria-label=\"Close\"\n >\n ✕\n </button>\n </div>\n\n <HexColorPicker color={hexValue} onChange={handleHexChange} className=\"hex-picker\" />\n\n <div className=\"hex-input-row\">\n <HexColorInput\n color={hexValue}\n onChange={handleHexChange}\n prefixed\n placeholder=\"#000000\"\n className=\"hex-input\"\n />\n </div>\n\n <div className=\"color-swatches\">\n <div className=\"swatches-label\">Quick Colors</div>\n <div className=\"swatches-grid\">\n {[\n '#ef4444',\n '#f97316',\n '#f59e0b',\n '#84cc16',\n '#10b981',\n '#06b6d4',\n '#3b82f6',\n '#8b5cf6',\n '#ec4899',\n '#64748b',\n '#000000',\n '#ffffff',\n ].map((color) => (\n <button\n key={color}\n type=\"button\"\n onClick={() => handleHexChange(color)}\n className={`swatch-btn ${hexValue.toLowerCase() === color.toLowerCase() ? 'selected' : ''}`}\n style={{ backgroundColor: color }}\n aria-label={`Select ${color}`}\n title={color}\n />\n ))}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default ThemeColorPickerField\n"],"names":["useField","Color","HexColorInput","HexColorPicker","useCallback","useEffect","useRef","useState","window","catch","resolveLocalizedValue","value","fallback","localized","resolved","en","cs","toHex","color","hex","toString","ThemeColorPickerField","field","path","setValue","localValue","setLocalValue","showPicker","setShowPicker","hexValue","setHexValue","pickerRef","handleClickOutside","event","current","contains","target","document","addEventListener","removeEventListener","handleHexChange","handleTextChange","nextValue","fieldName","split","pop","label","description","admin","div","className","button","type","style","backgroundColor","onClick","aria-label","title","input","onChange","placeholder","ref","span","prefixed","map","toLowerCase"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiD,MAAM,EAAa,MAAM,SAAS,CAAA;AAG/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,KAAK,EAAE,8BAA8B,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AAqG9F,eAAO,MAAM,qBAAqB,GAAI,UAAS,4BAAiC,KAAG,MA4KlF,CAAA;AAED,KAAK,iBAAiB,GAAG,MAAM,GAAG,8BAA8B,CAAA;AAEhE,eAAO,MAAM,uBAAuB,GAClC,UAAU,iBAAiB,KAC1B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CA0EvC,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,KAAG,WAAW,GAAG,IAEhE,CAAA;AAED,eAAO,MAAM,wBAAwB,QAAO,WAAW,EAEtD,CAAA;AAED,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACtE,YAAY,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACnF,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EAAE,4BAA4B,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAA;AAC9F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAC7C,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAA;AACtF,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACzE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAGvE,OAAO,EACL,gBAAgB,IAAI,iBAAiB,EACrC,oBAAoB,GACrB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEhF,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAG1D,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,4BAA4B,CAAA;AAMnC,eAAO,MAAM,qBAAqB,aAxTa,4BAA4B,KAAQ,MAwTzB,CAAA;AAC1D,oDAAoD;AACpD,eAAO,MAAM,wBAAwB,aA1TU,4BAA4B,KAAQ,MA0TtB,CAAA;AAG7D,OAAO,EACL,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAE1B,eAAe,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiD,MAAM,EAAa,MAAM,SAAS,CAAA;AAG/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,KAAK,EAAE,8BAA8B,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AAqG9F,eAAO,MAAM,qBAAqB,GAAI,UAAS,4BAAiC,KAAG,MAsMlF,CAAA;AAED,KAAK,iBAAiB,GAAG,MAAM,GAAG,8BAA8B,CAAA;AAEhE,eAAO,MAAM,uBAAuB,GAClC,UAAU,iBAAiB,KAC1B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CA6EvC,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,KAAG,WAAW,GAAG,IAEhE,CAAA;AAED,eAAO,MAAM,wBAAwB,QAAO,WAAW,EAEtD,CAAA;AAED,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACtE,YAAY,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACnF,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EAAE,4BAA4B,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAA;AAC9F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAC7C,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAA;AACtF,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACzE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAGvE,OAAO,EACL,gBAAgB,IAAI,iBAAiB,EACrC,oBAAoB,GACrB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEhF,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAG1D,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,4BAA4B,CAAA;AAMnC,eAAO,MAAM,qBAAqB,aArVa,4BAA4B,KAAQ,MAqVzB,CAAA;AAC1D,oDAAoD;AACpD,eAAO,MAAM,wBAAwB,aAvVU,4BAA4B,KAAQ,MAuVtB,CAAA;AAG7D,OAAO,EACL,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAE1B,eAAe,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -155,6 +155,27 @@ export const themeManagementPlugin = (options = {})=>{
155
155
  hooks: {
156
156
  beforeChange: [
157
157
  beforeChangeHook
158
+ ],
159
+ afterChange: [
160
+ async ({ doc, req })=>{
161
+ // Invalidate cache after appearance settings change
162
+ if (!req?.context?.disableRevalidate) {
163
+ try {
164
+ const cacheTag = `global_${standaloneCollectionSlug}`;
165
+ // Import revalidateTag dynamically to avoid issues in non-Next.js environments
166
+ const { revalidateTag } = await import('next/cache');
167
+ revalidateTag(cacheTag);
168
+ if (enableLogging) {
169
+ req.payload.logger.info(`🎨 Theme Management Plugin: cache invalidated for tag "${cacheTag}"`);
170
+ }
171
+ } catch (error) {
172
+ if (enableLogging) {
173
+ req.payload.logger.error('🎨 Theme Management Plugin: cache invalidation failed:', error);
174
+ }
175
+ }
176
+ }
177
+ return doc;
178
+ }
158
179
  ]
159
180
  }
160
181
  };
@@ -214,9 +235,11 @@ export const fetchThemeConfiguration = async (options)=>{
214
235
  // Fetch from global endpoint
215
236
  const params = new URLSearchParams();
216
237
  if (typeof depth === 'number') params.set('depth', String(depth));
217
- if (tenantSlug) params.set('tenant', tenantSlug);
218
- if (locale) params.set('locale', locale);
219
- if (draft) params.set('draft', 'true');
238
+ // For globals, keep tenant param for backwards compatibility, and also add where[...] filter
239
+ if (tenantSlug) {
240
+ params.set('tenant', tenantSlug);
241
+ params.set('where[tenant][equals]', String(tenantSlug));
242
+ }
220
243
  Object.entries(queryParams).forEach(([key, value])=>{
221
244
  if (value === undefined) return;
222
245
  params.set(key, String(value));
@@ -235,7 +258,8 @@ export const fetchThemeConfiguration = async (options)=>{
235
258
  limit: '1'
236
259
  });
237
260
  if (typeof depth === 'number') params.set('depth', String(depth));
238
- if (tenantSlug) params.set('tenant', tenantSlug);
261
+ // For collections, use a where[tenant][equals] filter so Payload's find endpoint applies tenant scoping
262
+ if (tenantSlug) params.set('where[tenant][equals]', String(tenantSlug));
239
263
  if (locale) params.set('locale', locale);
240
264
  if (draft) params.set('draft', 'true');
241
265
  Object.entries(queryParams).forEach(([key, value])=>{
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig, Plugin, TabsField } from 'payload'\nimport { createThemeConfigurationField } from './fields/themeConfigurationField.js'\nimport type { ThemeTab } from './fields/themeConfigurationField.js'\nimport type { SiteThemeConfiguration } from './payload-types.js'\nimport type { ThemePreset } from './presets.js'\nimport { allThemePresets } from './presets.js'\nimport { getTranslations, translations } from './translations.js'\nimport type { FetchThemeConfigurationOptions, ThemeManagementPluginOptions } from './types.js'\n\nconst THEME_FIELD_NAME = 'themeConfiguration'\n\n/**\n * Removes existing theme configuration from a group field (legacy)\n */\nconst removeExistingThemeField = (fields: Field[] = []): Field[] =>\n fields.filter((field) => {\n const candidate = field as { name?: unknown }\n return candidate?.name !== THEME_FIELD_NAME\n })\n\n/**\n * Finds the tabs field in the collection fields array\n */\nconst findTabsField = (fields: Field[] = []): TabsField | null => {\n for (const field of fields) {\n if (field.type === 'tabs') {\n return field as TabsField\n }\n }\n return null\n}\n\n/**\n * Removes existing Appearance Settings tab if it exists\n */\nconst removeExistingThemeTab = (\n tabs: NonNullable<TabsField['tabs']>,\n): NonNullable<TabsField['tabs']> => {\n const knownLabels = Object.values(translations).map((t) => t.tabLabel)\n return tabs.filter((tab) => {\n // Normalize label to string (support localized object or plain string)\n let labelValues: string[] = []\n if (typeof tab.label === 'object' && tab.label !== null) {\n labelValues = Object.values(tab.label as Record<string, string>)\n } else if (typeof tab.label === 'string') {\n labelValues = [tab.label]\n }\n\n // If any of the label values match a known plugin tab label, remove it\n const matchesKnown = labelValues.some((v) => knownLabels.includes(v))\n\n return !matchesKnown\n })\n}\n\n/**\n * Injects theme tab into existing tabs field\n */\nconst injectThemeTab = (\n fields: Field[] | undefined,\n themeTab: ThemeTab,\n enableLogging: boolean,\n): Field[] => {\n // First, remove any legacy theme field (group-based)\n const sanitizedFields = removeExistingThemeField(fields ?? [])\n\n // Find the tabs field\n const tabsField = findTabsField(sanitizedFields)\n\n if (!tabsField || !tabsField.tabs) {\n if (enableLogging) {\n console.warn(\n '🎨 Theme Management Plugin: No tabs field found. Creating a new tabs structure.',\n )\n }\n\n // If no tabs exist, create a basic tabs structure with just the theme tab\n const newTabsField: TabsField = {\n type: 'tabs',\n tabs: [themeTab],\n }\n\n return [...sanitizedFields, newTabsField]\n }\n\n // Remove existing theme tab if present\n const sanitizedTabs = removeExistingThemeTab(tabsField.tabs)\n\n // Add theme tab to the end\n const updatedTabs = [...sanitizedTabs, themeTab]\n\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: injecting Appearance Settings tab')\n }\n\n // Update the tabs field\n const updatedTabsField: TabsField = {\n ...tabsField,\n tabs: updatedTabs,\n }\n\n // Replace the old tabs field with the updated one\n return sanitizedFields.map((field) => (field.type === 'tabs' ? updatedTabsField : field))\n}\n\nconst ensureCollectionsArray = (collections: Config['collections']): CollectionConfig[] =>\n Array.isArray(collections) ? collections : []\n\nexport const themeManagementPlugin = (options: ThemeManagementPluginOptions = {}): Plugin => {\n return (config: Config): Config => {\n const {\n enabled = true,\n targetCollection = 'site-settings',\n useStandaloneCollection = false,\n standaloneCollectionSlug = 'appearance-settings',\n // Allow either string or localized object; default comes from plugin translations\n standaloneCollectionLabel = getTranslations().standaloneCollectionLabel,\n themePresets = allThemePresets,\n defaultTheme = 'cool',\n includeColorModeToggle = true,\n includeCustomCSS = true,\n includeBrandIdentity = false,\n enableAdvancedFeatures = true,\n enableLogging = false,\n } = options\n\n if (!enabled) {\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: disabled via options, skipping.')\n }\n return config\n }\n\n const themeTab = createThemeConfigurationField({\n themePresets,\n defaultTheme,\n includeColorModeToggle,\n includeCustomCSS,\n includeBrandIdentity: includeBrandIdentity ?? false,\n enableAdvancedFeatures,\n })\n\n // If using standalone collection, create a new global\n if (useStandaloneCollection) {\n if (enableLogging) {\n console.log(\n `🎨 Theme Management Plugin: creating standalone global \"${standaloneCollectionSlug}\"`,\n )\n }\n\n // Extract the fields from the tab directly (no wrapping in group)\n const tabFields = (themeTab.fields ?? []) as Field[]\n\n /**\n * Hook to auto-populate lightMode and darkMode colors when theme changes\n */\n type BeforeChangeArgs = {\n data?: {\n themeConfiguration?: {\n theme?: string\n lightMode?: Record<string, unknown>\n darkMode?: Record<string, unknown>\n }\n }\n }\n\n const beforeChangeHook = async (args: BeforeChangeArgs) => {\n const { data } = args\n\n // If theme is being changed, auto-populate color modes\n if (data?.themeConfiguration?.theme) {\n const themeName = data.themeConfiguration.theme\n const selectedPreset = themePresets.find((p) => p.name === themeName)\n\n if (selectedPreset) {\n // Initialize light and dark mode colors from preset if not already set\n if (!data.themeConfiguration.lightMode) {\n data.themeConfiguration.lightMode = {}\n }\n if (!data.themeConfiguration.darkMode) {\n data.themeConfiguration.darkMode = {}\n }\n\n // Only populate if not manually edited (first time or empty)\n const isLightModeEmpty = !data.themeConfiguration.lightMode?.background\n const isDarkModeEmpty = !data.themeConfiguration.darkMode?.background\n\n if (isLightModeEmpty && selectedPreset.lightMode) {\n data.themeConfiguration.lightMode = { ...selectedPreset.lightMode }\n }\n\n if (isDarkModeEmpty && selectedPreset.darkMode) {\n data.themeConfiguration.darkMode = { ...selectedPreset.darkMode }\n }\n }\n }\n\n return data\n }\n\n const standaloneGlobal: GlobalConfig = {\n slug: standaloneCollectionSlug,\n label: standaloneCollectionLabel,\n admin: {\n group: 'Settings',\n },\n // Provide a broad access object but cast to GlobalConfig['access'] to satisfy types\n access: ((): GlobalConfig['access'] => {\n const accessObj = {\n read: () => true,\n create: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n update: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n delete: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n } as unknown as GlobalConfig['access']\n return accessObj\n })(),\n fields: tabFields,\n hooks: {\n beforeChange: [beforeChangeHook],\n },\n }\n\n const globals = Array.isArray(config.globals) ? config.globals : []\n\n // Check if global already exists\n const existingIndex = globals.findIndex((g) => g.slug === standaloneCollectionSlug)\n\n if (existingIndex !== -1) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: global \"${standaloneCollectionSlug}\" already exists, skipping creation.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n globals: [...globals, standaloneGlobal],\n }\n }\n\n // Otherwise, add as tab to existing collection\n let collectionTouched = false\n\n const collections = ensureCollectionsArray(config.collections).map((collection) => {\n if (collection.slug !== targetCollection) {\n return collection\n }\n\n collectionTouched = true\n\n if (enableLogging) {\n console.log(`🎨 Theme Management Plugin: enhancing collection \"${collection.slug}\"`)\n }\n\n const existingFields = Array.isArray(collection.fields) ? collection.fields : []\n\n const fields = injectThemeTab(existingFields, themeTab, enableLogging)\n\n return {\n ...collection,\n fields,\n }\n })\n\n if (!collectionTouched) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: collection \"${targetCollection}\" was not found, leaving config untouched.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n collections,\n }\n }\n}\n\ntype FetchOptionsInput = string | FetchThemeConfigurationOptions\n\nexport const fetchThemeConfiguration = async (\n options?: FetchOptionsInput,\n): Promise<SiteThemeConfiguration | null> => {\n const normalizedOptions = typeof options === 'string' ? { tenantSlug: options } : (options ?? {})\n\n const {\n tenantSlug,\n collectionSlug = 'site-settings', // Can be overridden to 'appearance-settings' if using standalone\n depth,\n locale,\n draft,\n queryParams = {},\n useGlobal = false, // Set to true if using standalone global instead of collection\n } = normalizedOptions\n\n try {\n if (useGlobal) {\n // Fetch from global endpoint\n const params = new URLSearchParams()\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n if (tenantSlug) params.set('tenant', tenantSlug)\n if (locale) params.set('locale', locale)\n if (draft) params.set('draft', 'true')\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const queryString = params.toString()\n const url = `/api/globals/${collectionSlug}${queryString ? `?${queryString}` : ''}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme global (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n themeConfiguration?: SiteThemeConfiguration\n }\n\n return data?.themeConfiguration ?? null\n } else {\n // Fetch from collection endpoint (legacy/default behavior)\n const params = new URLSearchParams({ limit: '1' })\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n if (tenantSlug) params.set('tenant', tenantSlug)\n if (locale) params.set('locale', locale)\n if (draft) params.set('draft', 'true')\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const url = `/api/${collectionSlug}?${params.toString()}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme configuration (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n docs?: Array<{ themeConfiguration?: SiteThemeConfiguration }>\n }\n\n const doc = data.docs?.[0]\n\n return doc?.themeConfiguration ?? null\n }\n } catch (error) {\n console.warn('Failed to fetch theme configuration:', error)\n return null\n }\n}\n\nexport const getThemePreset = (themeName: string): ThemePreset | null => {\n return allThemePresets.find((preset) => preset.name === themeName) || null\n}\n\nexport const getAvailableThemePresets = (): ThemePreset[] => {\n return allThemePresets\n}\n\nexport { defaultThemePresets, allThemePresets } from './presets.js'\nexport type { ThemePreset, ThemeTypographyPreset } from './presets.js'\nexport type { ThemeTab } from './fields/themeConfigurationField.js'\nexport {\n extendedThemePresets,\n allExtendedThemePresets,\n extendedThemeToCSSVariables,\n} from './extended-presets.js'\nexport type { ExtendedThemePreset, ShadcnColorTokens } from './extended-presets.js'\nexport {\n applyExtendedTheme,\n generateExtendedThemeCSS,\n resetExtendedTheme,\n getExtendedThemeTokens,\n createUseExtendedTheme,\n getTailwindVarReferences,\n} from './utils/extendedThemeHelpers.js'\nexport type { ThemeManagementPluginOptions, FetchThemeConfigurationOptions } from './types.js'\nexport {\n DEFAULT_THEME_CONFIGURATION,\n resolveThemeConfiguration,\n} from './utils/resolveThemeConfiguration.js'\nexport type { ResolvedThemeConfiguration } from './utils/resolveThemeConfiguration.js'\nexport { generateThemeColorsCss, hexToHsl } from './utils/themeColors.js'\nexport { generateThemeCSS, getThemeStyles } from './utils/themeUtils.js'\nexport { getThemeHtmlAttributes } from './utils/themeHtmlAttributes.js'\n\n// Dynamic theme CSS generation\nexport {\n generateThemeCSS as generatePresetCSS,\n generateAllThemesCSS,\n} from './utils/generateThemeCSS.js'\nexport { getThemeDynamicCSS, getAllDynamicThemesCSS } from './utils/themeCSS.js'\n\nexport type { Mode, ThemeDefaults } from './providers/Theme/types.js'\nexport { ThemeProvider } from './providers/Theme/index.js'\n\n// Font loading utilities for Next.js integration\nexport {\n getFontLoaderCode,\n getThemeFontFamilies,\n ThemeFontProvider,\n useThemeFonts,\n FONT_IMPORT_MAP,\n} from './providers/font-loader.js'\n\n// Note: Do NOT re-export client components from the root entry to avoid\n// server-side evaluation during `payload generate:importmap`.\n// If needed, import directly from subpath: `@kilivi/payloadcms-theme-management/fields/ThemeLivePreview`\n\nexport const ThemeManagementPlugin = themeManagementPlugin\n/** @deprecated use {@link themeManagementPlugin} */\nexport const themeConfigurationPlugin = themeManagementPlugin\n\n// Export plugin translations for consumers to merge into their payload translations\nexport {\n translations,\n getTranslations,\n registerTranslations,\n availableLanguages,\n} from './translations.js'\n\nexport default themeManagementPlugin\n"],"names":["createThemeConfigurationField","allThemePresets","getTranslations","translations","THEME_FIELD_NAME","removeExistingThemeField","fields","filter","field","candidate","name","findTabsField","type","removeExistingThemeTab","tabs","knownLabels","Object","values","map","t","tabLabel","tab","labelValues","label","matchesKnown","some","v","includes","injectThemeTab","themeTab","enableLogging","sanitizedFields","tabsField","console","warn","newTabsField","sanitizedTabs","updatedTabs","log","updatedTabsField","ensureCollectionsArray","collections","Array","isArray","themeManagementPlugin","options","config","enabled","targetCollection","useStandaloneCollection","standaloneCollectionSlug","standaloneCollectionLabel","themePresets","defaultTheme","includeColorModeToggle","includeCustomCSS","includeBrandIdentity","enableAdvancedFeatures","tabFields","beforeChangeHook","args","data","themeConfiguration","theme","themeName","selectedPreset","find","p","lightMode","darkMode","isLightModeEmpty","background","isDarkModeEmpty","standaloneGlobal","slug","admin","group","access","accessObj","read","create","req","user","update","delete","hooks","beforeChange","globals","existingIndex","findIndex","g","collectionTouched","collection","existingFields","fetchThemeConfiguration","normalizedOptions","tenantSlug","collectionSlug","depth","locale","draft","queryParams","useGlobal","params","URLSearchParams","set","String","entries","forEach","key","value","undefined","queryString","toString","url","response","fetch","ok","Error","status","json","limit","doc","docs","error","getThemePreset","preset","getAvailableThemePresets","defaultThemePresets","extendedThemePresets","allExtendedThemePresets","extendedThemeToCSSVariables","applyExtendedTheme","generateExtendedThemeCSS","resetExtendedTheme","getExtendedThemeTokens","createUseExtendedTheme","getTailwindVarReferences","DEFAULT_THEME_CONFIGURATION","resolveThemeConfiguration","generateThemeColorsCss","hexToHsl","generateThemeCSS","getThemeStyles","getThemeHtmlAttributes","generatePresetCSS","generateAllThemesCSS","getThemeDynamicCSS","getAllDynamicThemesCSS","ThemeProvider","getFontLoaderCode","getThemeFontFamilies","ThemeFontProvider","useThemeFonts","FONT_IMPORT_MAP","ThemeManagementPlugin","themeConfigurationPlugin","registerTranslations","availableLanguages"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig, Plugin, TabsField } from 'payload'\nimport { createThemeConfigurationField } from './fields/themeConfigurationField.js'\nimport type { ThemeTab } from './fields/themeConfigurationField.js'\nimport type { SiteThemeConfiguration } from './payload-types.js'\nimport type { ThemePreset } from './presets.js'\nimport { allThemePresets } from './presets.js'\nimport { getTranslations, translations } from './translations.js'\nimport type { FetchThemeConfigurationOptions, ThemeManagementPluginOptions } from './types.js'\n\nconst THEME_FIELD_NAME = 'themeConfiguration'\n\n/**\n * Removes existing theme configuration from a group field (legacy)\n */\nconst removeExistingThemeField = (fields: Field[] = []): Field[] =>\n fields.filter((field) => {\n const candidate = field as { name?: unknown }\n return candidate?.name !== THEME_FIELD_NAME\n })\n\n/**\n * Finds the tabs field in the collection fields array\n */\nconst findTabsField = (fields: Field[] = []): TabsField | null => {\n for (const field of fields) {\n if (field.type === 'tabs') {\n return field as TabsField\n }\n }\n return null\n}\n\n/**\n * Removes existing Appearance Settings tab if it exists\n */\nconst removeExistingThemeTab = (\n tabs: NonNullable<TabsField['tabs']>,\n): NonNullable<TabsField['tabs']> => {\n const knownLabels = Object.values(translations).map((t) => t.tabLabel)\n return tabs.filter((tab) => {\n // Normalize label to string (support localized object or plain string)\n let labelValues: string[] = []\n if (typeof tab.label === 'object' && tab.label !== null) {\n labelValues = Object.values(tab.label as Record<string, string>)\n } else if (typeof tab.label === 'string') {\n labelValues = [tab.label]\n }\n\n // If any of the label values match a known plugin tab label, remove it\n const matchesKnown = labelValues.some((v) => knownLabels.includes(v))\n\n return !matchesKnown\n })\n}\n\n/**\n * Injects theme tab into existing tabs field\n */\nconst injectThemeTab = (\n fields: Field[] | undefined,\n themeTab: ThemeTab,\n enableLogging: boolean,\n): Field[] => {\n // First, remove any legacy theme field (group-based)\n const sanitizedFields = removeExistingThemeField(fields ?? [])\n\n // Find the tabs field\n const tabsField = findTabsField(sanitizedFields)\n\n if (!tabsField || !tabsField.tabs) {\n if (enableLogging) {\n console.warn(\n '🎨 Theme Management Plugin: No tabs field found. Creating a new tabs structure.',\n )\n }\n\n // If no tabs exist, create a basic tabs structure with just the theme tab\n const newTabsField: TabsField = {\n type: 'tabs',\n tabs: [themeTab],\n }\n\n return [...sanitizedFields, newTabsField]\n }\n\n // Remove existing theme tab if present\n const sanitizedTabs = removeExistingThemeTab(tabsField.tabs)\n\n // Add theme tab to the end\n const updatedTabs = [...sanitizedTabs, themeTab]\n\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: injecting Appearance Settings tab')\n }\n\n // Update the tabs field\n const updatedTabsField: TabsField = {\n ...tabsField,\n tabs: updatedTabs,\n }\n\n // Replace the old tabs field with the updated one\n return sanitizedFields.map((field) => (field.type === 'tabs' ? updatedTabsField : field))\n}\n\nconst ensureCollectionsArray = (collections: Config['collections']): CollectionConfig[] =>\n Array.isArray(collections) ? collections : []\n\nexport const themeManagementPlugin = (options: ThemeManagementPluginOptions = {}): Plugin => {\n return (config: Config): Config => {\n const {\n enabled = true,\n targetCollection = 'site-settings',\n useStandaloneCollection = false,\n standaloneCollectionSlug = 'appearance-settings',\n // Allow either string or localized object; default comes from plugin translations\n standaloneCollectionLabel = getTranslations().standaloneCollectionLabel,\n themePresets = allThemePresets,\n defaultTheme = 'cool',\n includeColorModeToggle = true,\n includeCustomCSS = true,\n includeBrandIdentity = false,\n enableAdvancedFeatures = true,\n enableLogging = false,\n } = options\n\n if (!enabled) {\n if (enableLogging) {\n console.log('🎨 Theme Management Plugin: disabled via options, skipping.')\n }\n return config\n }\n\n const themeTab = createThemeConfigurationField({\n themePresets,\n defaultTheme,\n includeColorModeToggle,\n includeCustomCSS,\n includeBrandIdentity: includeBrandIdentity ?? false,\n enableAdvancedFeatures,\n })\n\n // If using standalone collection, create a new global\n if (useStandaloneCollection) {\n if (enableLogging) {\n console.log(\n `🎨 Theme Management Plugin: creating standalone global \"${standaloneCollectionSlug}\"`,\n )\n }\n\n // Extract the fields from the tab directly (no wrapping in group)\n const tabFields = (themeTab.fields ?? []) as Field[]\n\n /**\n * Hook to auto-populate lightMode and darkMode colors when theme changes\n */\n type BeforeChangeArgs = {\n data?: {\n themeConfiguration?: {\n theme?: string\n lightMode?: Record<string, unknown>\n darkMode?: Record<string, unknown>\n }\n }\n }\n\n const beforeChangeHook = async (args: BeforeChangeArgs) => {\n const { data } = args\n\n // If theme is being changed, auto-populate color modes\n if (data?.themeConfiguration?.theme) {\n const themeName = data.themeConfiguration.theme\n const selectedPreset = themePresets.find((p) => p.name === themeName)\n\n if (selectedPreset) {\n // Initialize light and dark mode colors from preset if not already set\n if (!data.themeConfiguration.lightMode) {\n data.themeConfiguration.lightMode = {}\n }\n if (!data.themeConfiguration.darkMode) {\n data.themeConfiguration.darkMode = {}\n }\n\n // Only populate if not manually edited (first time or empty)\n const isLightModeEmpty = !data.themeConfiguration.lightMode?.background\n const isDarkModeEmpty = !data.themeConfiguration.darkMode?.background\n\n if (isLightModeEmpty && selectedPreset.lightMode) {\n data.themeConfiguration.lightMode = { ...selectedPreset.lightMode }\n }\n\n if (isDarkModeEmpty && selectedPreset.darkMode) {\n data.themeConfiguration.darkMode = { ...selectedPreset.darkMode }\n }\n }\n }\n\n return data\n }\n\n const standaloneGlobal: GlobalConfig = {\n slug: standaloneCollectionSlug,\n label: standaloneCollectionLabel,\n admin: {\n group: 'Settings',\n },\n // Provide a broad access object but cast to GlobalConfig['access'] to satisfy types\n access: ((): GlobalConfig['access'] => {\n const accessObj = {\n read: () => true,\n create: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n update: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n delete: ({ req }: { req?: { user?: unknown } }) => !!req?.user,\n } as unknown as GlobalConfig['access']\n return accessObj\n })(),\n fields: tabFields,\n hooks: {\n beforeChange: [beforeChangeHook],\n afterChange: [\n async ({ doc, req }) => {\n // Invalidate cache after appearance settings change\n if (!req?.context?.disableRevalidate) {\n try {\n const cacheTag = `global_${standaloneCollectionSlug}`\n // Import revalidateTag dynamically to avoid issues in non-Next.js environments\n const { revalidateTag } = await import('next/cache')\n revalidateTag(cacheTag)\n if (enableLogging) {\n req.payload.logger.info(\n `🎨 Theme Management Plugin: cache invalidated for tag \"${cacheTag}\"`,\n )\n }\n } catch (error) {\n if (enableLogging) {\n req.payload.logger.error(\n '🎨 Theme Management Plugin: cache invalidation failed:',\n error,\n )\n }\n }\n }\n return doc\n },\n ],\n },\n }\n\n const globals = Array.isArray(config.globals) ? config.globals : []\n\n // Check if global already exists\n const existingIndex = globals.findIndex((g) => g.slug === standaloneCollectionSlug)\n\n if (existingIndex !== -1) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: global \"${standaloneCollectionSlug}\" already exists, skipping creation.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n globals: [...globals, standaloneGlobal],\n }\n }\n\n // Otherwise, add as tab to existing collection\n let collectionTouched = false\n\n const collections = ensureCollectionsArray(config.collections).map((collection) => {\n if (collection.slug !== targetCollection) {\n return collection\n }\n\n collectionTouched = true\n\n if (enableLogging) {\n console.log(`🎨 Theme Management Plugin: enhancing collection \"${collection.slug}\"`)\n }\n\n const existingFields = Array.isArray(collection.fields) ? collection.fields : []\n\n const fields = injectThemeTab(existingFields, themeTab, enableLogging)\n\n return {\n ...collection,\n fields,\n }\n })\n\n if (!collectionTouched) {\n if (enableLogging) {\n console.warn(\n `🎨 Theme Management Plugin: collection \"${targetCollection}\" was not found, leaving config untouched.`,\n )\n }\n return config\n }\n\n return {\n ...config,\n collections,\n }\n }\n}\n\ntype FetchOptionsInput = string | FetchThemeConfigurationOptions\n\nexport const fetchThemeConfiguration = async (\n options?: FetchOptionsInput,\n): Promise<SiteThemeConfiguration | null> => {\n const normalizedOptions = typeof options === 'string' ? { tenantSlug: options } : (options ?? {})\n\n const {\n tenantSlug,\n collectionSlug = 'site-settings', // Can be overridden to 'appearance-settings' if using standalone\n depth,\n locale,\n draft,\n queryParams = {},\n useGlobal = false, // Set to true if using standalone global instead of collection\n } = normalizedOptions\n\n try {\n if (useGlobal) {\n // Fetch from global endpoint\n const params = new URLSearchParams()\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n // For globals, keep tenant param for backwards compatibility, and also add where[...] filter\n if (tenantSlug) {\n params.set('tenant', tenantSlug)\n params.set('where[tenant][equals]', String(tenantSlug))\n }\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const queryString = params.toString()\n const url = `/api/globals/${collectionSlug}${queryString ? `?${queryString}` : ''}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme global (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n themeConfiguration?: SiteThemeConfiguration\n }\n\n return data?.themeConfiguration ?? null\n } else {\n // Fetch from collection endpoint (legacy/default behavior)\n const params = new URLSearchParams({ limit: '1' })\n\n if (typeof depth === 'number') params.set('depth', String(depth))\n // For collections, use a where[tenant][equals] filter so Payload's find endpoint applies tenant scoping\n if (tenantSlug) params.set('where[tenant][equals]', String(tenantSlug))\n if (locale) params.set('locale', locale)\n if (draft) params.set('draft', 'true')\n\n Object.entries(queryParams).forEach(([key, value]) => {\n if (value === undefined) return\n params.set(key, String(value))\n })\n\n const url = `/api/${collectionSlug}?${params.toString()}`\n\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`Failed to fetch theme configuration (status ${response.status})`)\n }\n\n const data = (await response.json()) as {\n docs?: Array<{ themeConfiguration?: SiteThemeConfiguration }>\n }\n\n const doc = data.docs?.[0]\n\n return doc?.themeConfiguration ?? null\n }\n } catch (error) {\n console.warn('Failed to fetch theme configuration:', error)\n return null\n }\n}\n\nexport const getThemePreset = (themeName: string): ThemePreset | null => {\n return allThemePresets.find((preset) => preset.name === themeName) || null\n}\n\nexport const getAvailableThemePresets = (): ThemePreset[] => {\n return allThemePresets\n}\n\nexport { defaultThemePresets, allThemePresets } from './presets.js'\nexport type { ThemePreset, ThemeTypographyPreset } from './presets.js'\nexport type { ThemeTab } from './fields/themeConfigurationField.js'\nexport {\n extendedThemePresets,\n allExtendedThemePresets,\n extendedThemeToCSSVariables,\n} from './extended-presets.js'\nexport type { ExtendedThemePreset, ShadcnColorTokens } from './extended-presets.js'\nexport {\n applyExtendedTheme,\n generateExtendedThemeCSS,\n resetExtendedTheme,\n getExtendedThemeTokens,\n createUseExtendedTheme,\n getTailwindVarReferences,\n} from './utils/extendedThemeHelpers.js'\nexport type { ThemeManagementPluginOptions, FetchThemeConfigurationOptions } from './types.js'\nexport {\n DEFAULT_THEME_CONFIGURATION,\n resolveThemeConfiguration,\n} from './utils/resolveThemeConfiguration.js'\nexport type { ResolvedThemeConfiguration } from './utils/resolveThemeConfiguration.js'\nexport { generateThemeColorsCss, hexToHsl } from './utils/themeColors.js'\nexport { generateThemeCSS, getThemeStyles } from './utils/themeUtils.js'\nexport { getThemeHtmlAttributes } from './utils/themeHtmlAttributes.js'\n\n// Dynamic theme CSS generation\nexport {\n generateThemeCSS as generatePresetCSS,\n generateAllThemesCSS,\n} from './utils/generateThemeCSS.js'\nexport { getThemeDynamicCSS, getAllDynamicThemesCSS } from './utils/themeCSS.js'\n\nexport type { Mode, ThemeDefaults } from './providers/Theme/types.js'\nexport { ThemeProvider } from './providers/Theme/index.js'\n\n// Font loading utilities for Next.js integration\nexport {\n getFontLoaderCode,\n getThemeFontFamilies,\n ThemeFontProvider,\n useThemeFonts,\n FONT_IMPORT_MAP,\n} from './providers/font-loader.js'\n\n// Note: Do NOT re-export client components from the root entry to avoid\n// server-side evaluation during `payload generate:importmap`.\n// If needed, import directly from subpath: `@kilivi/payloadcms-theme-management/fields/ThemeLivePreview`\n\nexport const ThemeManagementPlugin = themeManagementPlugin\n/** @deprecated use {@link themeManagementPlugin} */\nexport const themeConfigurationPlugin = themeManagementPlugin\n\n// Export plugin translations for consumers to merge into their payload translations\nexport {\n translations,\n getTranslations,\n registerTranslations,\n availableLanguages,\n} from './translations.js'\n\nexport default themeManagementPlugin\n"],"names":["createThemeConfigurationField","allThemePresets","getTranslations","translations","THEME_FIELD_NAME","removeExistingThemeField","fields","filter","field","candidate","name","findTabsField","type","removeExistingThemeTab","tabs","knownLabels","Object","values","map","t","tabLabel","tab","labelValues","label","matchesKnown","some","v","includes","injectThemeTab","themeTab","enableLogging","sanitizedFields","tabsField","console","warn","newTabsField","sanitizedTabs","updatedTabs","log","updatedTabsField","ensureCollectionsArray","collections","Array","isArray","themeManagementPlugin","options","config","enabled","targetCollection","useStandaloneCollection","standaloneCollectionSlug","standaloneCollectionLabel","themePresets","defaultTheme","includeColorModeToggle","includeCustomCSS","includeBrandIdentity","enableAdvancedFeatures","tabFields","beforeChangeHook","args","data","themeConfiguration","theme","themeName","selectedPreset","find","p","lightMode","darkMode","isLightModeEmpty","background","isDarkModeEmpty","standaloneGlobal","slug","admin","group","access","accessObj","read","create","req","user","update","delete","hooks","beforeChange","afterChange","doc","context","disableRevalidate","cacheTag","revalidateTag","payload","logger","info","error","globals","existingIndex","findIndex","g","collectionTouched","collection","existingFields","fetchThemeConfiguration","normalizedOptions","tenantSlug","collectionSlug","depth","locale","draft","queryParams","useGlobal","params","URLSearchParams","set","String","entries","forEach","key","value","undefined","queryString","toString","url","response","fetch","ok","Error","status","json","limit","docs","getThemePreset","preset","getAvailableThemePresets","defaultThemePresets","extendedThemePresets","allExtendedThemePresets","extendedThemeToCSSVariables","applyExtendedTheme","generateExtendedThemeCSS","resetExtendedTheme","getExtendedThemeTokens","createUseExtendedTheme","getTailwindVarReferences","DEFAULT_THEME_CONFIGURATION","resolveThemeConfiguration","generateThemeColorsCss","hexToHsl","generateThemeCSS","getThemeStyles","getThemeHtmlAttributes","generatePresetCSS","generateAllThemesCSS","getThemeDynamicCSS","getAllDynamicThemesCSS","ThemeProvider","getFontLoaderCode","getThemeFontFamilies","ThemeFontProvider","useThemeFonts","FONT_IMPORT_MAP","ThemeManagementPlugin","themeConfigurationPlugin","registerTranslations","availableLanguages"],"mappings":"AACA,SAASA,6BAA6B,QAAQ,sCAAqC;AAInF,SAASC,eAAe,QAAQ,eAAc;AAC9C,SAASC,eAAe,EAAEC,YAAY,QAAQ,oBAAmB;AAGjE,MAAMC,mBAAmB;AAEzB;;CAEC,GACD,MAAMC,2BAA2B,CAACC,SAAkB,EAAE,GACpDA,OAAOC,MAAM,CAAC,CAACC;QACb,MAAMC,YAAYD;QAClB,OAAOC,WAAWC,SAASN;IAC7B;AAEF;;CAEC,GACD,MAAMO,gBAAgB,CAACL,SAAkB,EAAE;IACzC,KAAK,MAAME,SAASF,OAAQ;QAC1B,IAAIE,MAAMI,IAAI,KAAK,QAAQ;YACzB,OAAOJ;QACT;IACF;IACA,OAAO;AACT;AAEA;;CAEC,GACD,MAAMK,yBAAyB,CAC7BC;IAEA,MAAMC,cAAcC,OAAOC,MAAM,CAACd,cAAce,GAAG,CAAC,CAACC,IAAMA,EAAEC,QAAQ;IACrE,OAAON,KAAKP,MAAM,CAAC,CAACc;QAClB,uEAAuE;QACvE,IAAIC,cAAwB,EAAE;QAC9B,IAAI,OAAOD,IAAIE,KAAK,KAAK,YAAYF,IAAIE,KAAK,KAAK,MAAM;YACvDD,cAAcN,OAAOC,MAAM,CAACI,IAAIE,KAAK;QACvC,OAAO,IAAI,OAAOF,IAAIE,KAAK,KAAK,UAAU;YACxCD,cAAc;gBAACD,IAAIE,KAAK;aAAC;QAC3B;QAEA,uEAAuE;QACvE,MAAMC,eAAeF,YAAYG,IAAI,CAAC,CAACC,IAAMX,YAAYY,QAAQ,CAACD;QAElE,OAAO,CAACF;IACV;AACF;AAEA;;CAEC,GACD,MAAMI,iBAAiB,CACrBtB,QACAuB,UACAC;IAEA,qDAAqD;IACrD,MAAMC,kBAAkB1B,yBAAyBC,UAAU,EAAE;IAE7D,sBAAsB;IACtB,MAAM0B,YAAYrB,cAAcoB;IAEhC,IAAI,CAACC,aAAa,CAACA,UAAUlB,IAAI,EAAE;QACjC,IAAIgB,eAAe;YACjBG,QAAQC,IAAI,CACV;QAEJ;QAEA,0EAA0E;QAC1E,MAAMC,eAA0B;YAC9BvB,MAAM;YACNE,MAAM;gBAACe;aAAS;QAClB;QAEA,OAAO;eAAIE;YAAiBI;SAAa;IAC3C;IAEA,uCAAuC;IACvC,MAAMC,gBAAgBvB,uBAAuBmB,UAAUlB,IAAI;IAE3D,2BAA2B;IAC3B,MAAMuB,cAAc;WAAID;QAAeP;KAAS;IAEhD,IAAIC,eAAe;QACjBG,QAAQK,GAAG,CAAC;IACd;IAEA,wBAAwB;IACxB,MAAMC,mBAA8B;QAClC,GAAGP,SAAS;QACZlB,MAAMuB;IACR;IAEA,kDAAkD;IAClD,OAAON,gBAAgBb,GAAG,CAAC,CAACV,QAAWA,MAAMI,IAAI,KAAK,SAAS2B,mBAAmB/B;AACpF;AAEA,MAAMgC,yBAAyB,CAACC,cAC9BC,MAAMC,OAAO,CAACF,eAAeA,cAAc,EAAE;AAE/C,OAAO,MAAMG,wBAAwB,CAACC,UAAwC,CAAC,CAAC;IAC9E,OAAO,CAACC;QACN,MAAM,EACJC,UAAU,IAAI,EACdC,mBAAmB,eAAe,EAClCC,0BAA0B,KAAK,EAC/BC,2BAA2B,qBAAqB,EAChD,kFAAkF;QAClFC,4BAA4BjD,kBAAkBiD,yBAAyB,EACvEC,eAAenD,eAAe,EAC9BoD,eAAe,MAAM,EACrBC,yBAAyB,IAAI,EAC7BC,mBAAmB,IAAI,EACvBC,uBAAuB,KAAK,EAC5BC,yBAAyB,IAAI,EAC7B3B,gBAAgB,KAAK,EACtB,GAAGe;QAEJ,IAAI,CAACE,SAAS;YACZ,IAAIjB,eAAe;gBACjBG,QAAQK,GAAG,CAAC;YACd;YACA,OAAOQ;QACT;QAEA,MAAMjB,WAAW7B,8BAA8B;YAC7CoD;YACAC;YACAC;YACAC;YACAC,sBAAsBA,wBAAwB;YAC9CC;QACF;QAEA,sDAAsD;QACtD,IAAIR,yBAAyB;YAC3B,IAAInB,eAAe;gBACjBG,QAAQK,GAAG,CACT,CAAC,wDAAwD,EAAEY,yBAAyB,CAAC,CAAC;YAE1F;YAEA,kEAAkE;YAClE,MAAMQ,YAAa7B,SAASvB,MAAM,IAAI,EAAE;YAexC,MAAMqD,mBAAmB,OAAOC;gBAC9B,MAAM,EAAEC,IAAI,EAAE,GAAGD;gBAEjB,uDAAuD;gBACvD,IAAIC,MAAMC,oBAAoBC,OAAO;oBACnC,MAAMC,YAAYH,KAAKC,kBAAkB,CAACC,KAAK;oBAC/C,MAAME,iBAAiBb,aAAac,IAAI,CAAC,CAACC,IAAMA,EAAEzD,IAAI,KAAKsD;oBAE3D,IAAIC,gBAAgB;wBAClB,uEAAuE;wBACvE,IAAI,CAACJ,KAAKC,kBAAkB,CAACM,SAAS,EAAE;4BACtCP,KAAKC,kBAAkB,CAACM,SAAS,GAAG,CAAC;wBACvC;wBACA,IAAI,CAACP,KAAKC,kBAAkB,CAACO,QAAQ,EAAE;4BACrCR,KAAKC,kBAAkB,CAACO,QAAQ,GAAG,CAAC;wBACtC;wBAEA,6DAA6D;wBAC7D,MAAMC,mBAAmB,CAACT,KAAKC,kBAAkB,CAACM,SAAS,EAAEG;wBAC7D,MAAMC,kBAAkB,CAACX,KAAKC,kBAAkB,CAACO,QAAQ,EAAEE;wBAE3D,IAAID,oBAAoBL,eAAeG,SAAS,EAAE;4BAChDP,KAAKC,kBAAkB,CAACM,SAAS,GAAG;gCAAE,GAAGH,eAAeG,SAAS;4BAAC;wBACpE;wBAEA,IAAII,mBAAmBP,eAAeI,QAAQ,EAAE;4BAC9CR,KAAKC,kBAAkB,CAACO,QAAQ,GAAG;gCAAE,GAAGJ,eAAeI,QAAQ;4BAAC;wBAClE;oBACF;gBACF;gBAEA,OAAOR;YACT;YAEA,MAAMY,mBAAiC;gBACrCC,MAAMxB;gBACN3B,OAAO4B;gBACPwB,OAAO;oBACLC,OAAO;gBACT;gBACA,oFAAoF;gBACpFC,QAAQ,AAAC,CAAA;oBACP,MAAMC,YAAY;wBAChBC,MAAM,IAAM;wBACZC,QAAQ,CAAC,EAAEC,GAAG,EAAgC,GAAK,CAAC,CAACA,KAAKC;wBAC1DC,QAAQ,CAAC,EAAEF,GAAG,EAAgC,GAAK,CAAC,CAACA,KAAKC;wBAC1DE,QAAQ,CAAC,EAAEH,GAAG,EAAgC,GAAK,CAAC,CAACA,KAAKC;oBAC5D;oBACA,OAAOJ;gBACT,CAAA;gBACAxE,QAAQoD;gBACR2B,OAAO;oBACLC,cAAc;wBAAC3B;qBAAiB;oBAChC4B,aAAa;wBACX,OAAO,EAAEC,GAAG,EAAEP,GAAG,EAAE;4BACjB,oDAAoD;4BACpD,IAAI,CAACA,KAAKQ,SAASC,mBAAmB;gCACpC,IAAI;oCACF,MAAMC,WAAW,CAAC,OAAO,EAAEzC,0BAA0B;oCACrD,+EAA+E;oCAC/E,MAAM,EAAE0C,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC;oCACvCA,cAAcD;oCACd,IAAI7D,eAAe;wCACjBmD,IAAIY,OAAO,CAACC,MAAM,CAACC,IAAI,CACrB,CAAC,uDAAuD,EAAEJ,SAAS,CAAC,CAAC;oCAEzE;gCACF,EAAE,OAAOK,OAAO;oCACd,IAAIlE,eAAe;wCACjBmD,IAAIY,OAAO,CAACC,MAAM,CAACE,KAAK,CACtB,0DACAA;oCAEJ;gCACF;4BACF;4BACA,OAAOR;wBACT;qBACD;gBACH;YACF;YAEA,MAAMS,UAAUvD,MAAMC,OAAO,CAACG,OAAOmD,OAAO,IAAInD,OAAOmD,OAAO,GAAG,EAAE;YAEnE,iCAAiC;YACjC,MAAMC,gBAAgBD,QAAQE,SAAS,CAAC,CAACC,IAAMA,EAAE1B,IAAI,KAAKxB;YAE1D,IAAIgD,kBAAkB,CAAC,GAAG;gBACxB,IAAIpE,eAAe;oBACjBG,QAAQC,IAAI,CACV,CAAC,oCAAoC,EAAEgB,yBAAyB,oCAAoC,CAAC;gBAEzG;gBACA,OAAOJ;YACT;YAEA,OAAO;gBACL,GAAGA,MAAM;gBACTmD,SAAS;uBAAIA;oBAASxB;iBAAiB;YACzC;QACF;QAEA,+CAA+C;QAC/C,IAAI4B,oBAAoB;QAExB,MAAM5D,cAAcD,uBAAuBM,OAAOL,WAAW,EAAEvB,GAAG,CAAC,CAACoF;YAClE,IAAIA,WAAW5B,IAAI,KAAK1B,kBAAkB;gBACxC,OAAOsD;YACT;YAEAD,oBAAoB;YAEpB,IAAIvE,eAAe;gBACjBG,QAAQK,GAAG,CAAC,CAAC,kDAAkD,EAAEgE,WAAW5B,IAAI,CAAC,CAAC,CAAC;YACrF;YAEA,MAAM6B,iBAAiB7D,MAAMC,OAAO,CAAC2D,WAAWhG,MAAM,IAAIgG,WAAWhG,MAAM,GAAG,EAAE;YAEhF,MAAMA,SAASsB,eAAe2E,gBAAgB1E,UAAUC;YAExD,OAAO;gBACL,GAAGwE,UAAU;gBACbhG;YACF;QACF;QAEA,IAAI,CAAC+F,mBAAmB;YACtB,IAAIvE,eAAe;gBACjBG,QAAQC,IAAI,CACV,CAAC,wCAAwC,EAAEc,iBAAiB,0CAA0C,CAAC;YAE3G;YACA,OAAOF;QACT;QAEA,OAAO;YACL,GAAGA,MAAM;YACTL;QACF;IACF;AACF,EAAC;AAID,OAAO,MAAM+D,0BAA0B,OACrC3D;IAEA,MAAM4D,oBAAoB,OAAO5D,YAAY,WAAW;QAAE6D,YAAY7D;IAAQ,IAAKA,WAAW,CAAC;IAE/F,MAAM,EACJ6D,UAAU,EACVC,iBAAiB,eAAe,EAChCC,KAAK,EACLC,MAAM,EACNC,KAAK,EACLC,cAAc,CAAC,CAAC,EAChBC,YAAY,KAAK,EAClB,GAAGP;IAEJ,IAAI;QACF,IAAIO,WAAW;YACb,6BAA6B;YAC7B,MAAMC,SAAS,IAAIC;YAEnB,IAAI,OAAON,UAAU,UAAUK,OAAOE,GAAG,CAAC,SAASC,OAAOR;YAC1D,6FAA6F;YAC7F,IAAIF,YAAY;gBACdO,OAAOE,GAAG,CAAC,UAAUT;gBACrBO,OAAOE,GAAG,CAAC,yBAAyBC,OAAOV;YAC7C;YAEA1F,OAAOqG,OAAO,CAACN,aAAaO,OAAO,CAAC,CAAC,CAACC,KAAKC,MAAM;gBAC/C,IAAIA,UAAUC,WAAW;gBACzBR,OAAOE,GAAG,CAACI,KAAKH,OAAOI;YACzB;YAEA,MAAME,cAAcT,OAAOU,QAAQ;YACnC,MAAMC,MAAM,CAAC,aAAa,EAAEjB,iBAAiBe,cAAc,CAAC,CAAC,EAAEA,aAAa,GAAG,IAAI;YAEnF,MAAMG,WAAW,MAAMC,MAAMF;YAC7B,IAAI,CAACC,SAASE,EAAE,EAAE;gBAChB,MAAM,IAAIC,MAAM,CAAC,qCAAqC,EAAEH,SAASI,MAAM,CAAC,CAAC,CAAC;YAC5E;YAEA,MAAMpE,OAAQ,MAAMgE,SAASK,IAAI;YAIjC,OAAOrE,MAAMC,sBAAsB;QACrC,OAAO;YACL,2DAA2D;YAC3D,MAAMmD,SAAS,IAAIC,gBAAgB;gBAAEiB,OAAO;YAAI;YAEhD,IAAI,OAAOvB,UAAU,UAAUK,OAAOE,GAAG,CAAC,SAASC,OAAOR;YAC1D,wGAAwG;YACxG,IAAIF,YAAYO,OAAOE,GAAG,CAAC,yBAAyBC,OAAOV;YAC3D,IAAIG,QAAQI,OAAOE,GAAG,CAAC,UAAUN;YACjC,IAAIC,OAAOG,OAAOE,GAAG,CAAC,SAAS;YAE/BnG,OAAOqG,OAAO,CAACN,aAAaO,OAAO,CAAC,CAAC,CAACC,KAAKC,MAAM;gBAC/C,IAAIA,UAAUC,WAAW;gBACzBR,OAAOE,GAAG,CAACI,KAAKH,OAAOI;YACzB;YAEA,MAAMI,MAAM,CAAC,KAAK,EAAEjB,eAAe,CAAC,EAAEM,OAAOU,QAAQ,IAAI;YAEzD,MAAME,WAAW,MAAMC,MAAMF;YAC7B,IAAI,CAACC,SAASE,EAAE,EAAE;gBAChB,MAAM,IAAIC,MAAM,CAAC,4CAA4C,EAAEH,SAASI,MAAM,CAAC,CAAC,CAAC;YACnF;YAEA,MAAMpE,OAAQ,MAAMgE,SAASK,IAAI;YAIjC,MAAM1C,MAAM3B,KAAKuE,IAAI,EAAE,CAAC,EAAE;YAE1B,OAAO5C,KAAK1B,sBAAsB;QACpC;IACF,EAAE,OAAOkC,OAAO;QACd/D,QAAQC,IAAI,CAAC,wCAAwC8D;QACrD,OAAO;IACT;AACF,EAAC;AAED,OAAO,MAAMqC,iBAAiB,CAACrE;IAC7B,OAAO/D,gBAAgBiE,IAAI,CAAC,CAACoE,SAAWA,OAAO5H,IAAI,KAAKsD,cAAc;AACxE,EAAC;AAED,OAAO,MAAMuE,2BAA2B;IACtC,OAAOtI;AACT,EAAC;AAED,SAASuI,mBAAmB,EAAEvI,eAAe,QAAQ,eAAc;AAGnE,SACEwI,oBAAoB,EACpBC,uBAAuB,EACvBC,2BAA2B,QACtB,wBAAuB;AAE9B,SACEC,kBAAkB,EAClBC,wBAAwB,EACxBC,kBAAkB,EAClBC,sBAAsB,EACtBC,sBAAsB,EACtBC,wBAAwB,QACnB,kCAAiC;AAExC,SACEC,2BAA2B,EAC3BC,yBAAyB,QACpB,uCAAsC;AAE7C,SAASC,sBAAsB,EAAEC,QAAQ,QAAQ,yBAAwB;AACzE,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,wBAAuB;AACxE,SAASC,sBAAsB,QAAQ,iCAAgC;AAEvE,+BAA+B;AAC/B,SACEF,oBAAoBG,iBAAiB,EACrCC,oBAAoB,QACf,8BAA6B;AACpC,SAASC,kBAAkB,EAAEC,sBAAsB,QAAQ,sBAAqB;AAGhF,SAASC,aAAa,QAAQ,6BAA4B;AAE1D,iDAAiD;AACjD,SACEC,iBAAiB,EACjBC,oBAAoB,EACpBC,iBAAiB,EACjBC,aAAa,EACbC,eAAe,QACV,6BAA4B;AAEnC,wEAAwE;AACxE,8DAA8D;AAC9D,yGAAyG;AAEzG,OAAO,MAAMC,wBAAwBvH,sBAAqB;AAC1D,kDAAkD,GAClD,OAAO,MAAMwH,2BAA2BxH,sBAAqB;AAE7D,oFAAoF;AACpF,SACEzC,YAAY,EACZD,eAAe,EACfmK,oBAAoB,EACpBC,kBAAkB,QACb,oBAAmB;AAE1B,eAAe1H,sBAAqB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kilivi/payloadcms-theme-management",
3
- "version": "1.0.15",
3
+ "version": "1.1.0",
4
4
  "description": "Theme configuration plugin for Payload CMS v3 with 60+ professional themes and standalone collection support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -112,6 +112,7 @@
112
112
  "access": "public"
113
113
  },
114
114
  "dependencies": {
115
+ "color": "^5.0.3",
115
116
  "react-colorful": "^5.6.1",
116
117
  "server-only": "^0.0.1"
117
118
  },