@nuasite/cms 0.7.1 → 0.7.3
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/dist/editor.js +3865 -3866
- package/package.json +1 -1
- package/src/color-patterns.ts +312 -0
- package/src/editor/color-utils.ts +45 -235
- package/src/editor/dom.ts +9 -0
- package/src/html-processor.ts +8 -2
- package/src/tailwind-colors.ts +21 -310
package/package.json
CHANGED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import type { Attribute } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default Tailwind CSS v4 color names.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_TAILWIND_COLORS = [
|
|
7
|
+
'slate',
|
|
8
|
+
'gray',
|
|
9
|
+
'zinc',
|
|
10
|
+
'neutral',
|
|
11
|
+
'stone',
|
|
12
|
+
'red',
|
|
13
|
+
'orange',
|
|
14
|
+
'amber',
|
|
15
|
+
'yellow',
|
|
16
|
+
'lime',
|
|
17
|
+
'green',
|
|
18
|
+
'emerald',
|
|
19
|
+
'teal',
|
|
20
|
+
'cyan',
|
|
21
|
+
'sky',
|
|
22
|
+
'blue',
|
|
23
|
+
'indigo',
|
|
24
|
+
'violet',
|
|
25
|
+
'purple',
|
|
26
|
+
'fuchsia',
|
|
27
|
+
'pink',
|
|
28
|
+
'rose',
|
|
29
|
+
] as const
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Standard Tailwind color shades.
|
|
33
|
+
*/
|
|
34
|
+
export const STANDARD_SHADES = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] as const
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Special color values that don't have shades.
|
|
38
|
+
*/
|
|
39
|
+
export const SPECIAL_COLORS = ['transparent', 'current', 'inherit', 'white', 'black'] as const
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Non-color utility suffixes that should not be matched as custom colors.
|
|
43
|
+
* These follow the pattern `prefix-word-number` but are not colors.
|
|
44
|
+
*/
|
|
45
|
+
const NON_COLOR_SUFFIXES = ['opacity'] as const
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build a regex pattern for matching color classes.
|
|
49
|
+
* Matches:
|
|
50
|
+
* - Default colors with optional shades: bg-blue-500, bg-white
|
|
51
|
+
* - Custom theme colors with shades: bg-primary-500
|
|
52
|
+
* - Arbitrary hex values: bg-[#41b883], bg-[#fff]
|
|
53
|
+
* - Arbitrary rgb/hsl values: bg-[rgb(255,0,0)], bg-[hsl(0,100%,50%)]
|
|
54
|
+
* Excludes non-color utilities like opacity.
|
|
55
|
+
*/
|
|
56
|
+
function buildColorPattern(prefix: string): RegExp {
|
|
57
|
+
const colorNames = [...DEFAULT_TAILWIND_COLORS, ...SPECIAL_COLORS].join('|')
|
|
58
|
+
const excluded = NON_COLOR_SUFFIXES.join('|')
|
|
59
|
+
// Arbitrary value patterns for colors
|
|
60
|
+
const arbitraryHex = '\\[#[0-9a-fA-F]{3,8}\\]'
|
|
61
|
+
const arbitraryFunc = '\\[(?:rgba?|hsla?)\\([^\\]]+\\)\\]'
|
|
62
|
+
// Match: prefix-(colorName[-shade]?) OR prefix-(customColor-shade) OR prefix-[arbitrary] but NOT prefix-(excluded-number)
|
|
63
|
+
return new RegExp(`^${prefix}-((?:${colorNames})(?:-(\\d+))?|(?!(?:${excluded})-)(\\w+)-(\\d+)|${arbitraryHex}|${arbitraryFunc})$`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build a regex pattern for matching opacity classes.
|
|
68
|
+
*/
|
|
69
|
+
function buildOpacityPattern(prefix: string): RegExp {
|
|
70
|
+
return new RegExp(`^${prefix}-opacity-(\\d+)$`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Regex patterns to match Tailwind color classes.
|
|
75
|
+
*/
|
|
76
|
+
export const COLOR_CLASS_PATTERNS = {
|
|
77
|
+
bg: buildColorPattern('bg'),
|
|
78
|
+
text: buildColorPattern('text'),
|
|
79
|
+
border: buildColorPattern('border'),
|
|
80
|
+
hoverBg: buildColorPattern('hover:bg'),
|
|
81
|
+
hoverText: buildColorPattern('hover:text'),
|
|
82
|
+
hoverBorder: buildColorPattern('hover:border'),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Regex patterns to match Tailwind opacity classes.
|
|
87
|
+
*/
|
|
88
|
+
export const OPACITY_CLASS_PATTERNS = {
|
|
89
|
+
bgOpacity: buildOpacityPattern('bg'),
|
|
90
|
+
textOpacity: buildOpacityPattern('text'),
|
|
91
|
+
borderOpacity: buildOpacityPattern('border'),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Regex patterns to match Tailwind gradient color classes.
|
|
96
|
+
*/
|
|
97
|
+
export const GRADIENT_CLASS_PATTERNS = {
|
|
98
|
+
from: buildColorPattern('from'),
|
|
99
|
+
via: buildColorPattern('via'),
|
|
100
|
+
to: buildColorPattern('to'),
|
|
101
|
+
hoverFrom: buildColorPattern('hover:from'),
|
|
102
|
+
hoverVia: buildColorPattern('hover:via'),
|
|
103
|
+
hoverTo: buildColorPattern('hover:to'),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Flat key names for color class categories */
|
|
107
|
+
const COLOR_FLAT_KEYS: Record<string, string> = {
|
|
108
|
+
bg: 'bg',
|
|
109
|
+
text: 'text',
|
|
110
|
+
border: 'border',
|
|
111
|
+
hoverBg: 'hoverBg',
|
|
112
|
+
hoverText: 'hoverText',
|
|
113
|
+
hoverBorder: 'hoverBorder',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const GRADIENT_FLAT_KEYS: Record<string, string> = {
|
|
117
|
+
from: 'gradientFrom',
|
|
118
|
+
via: 'gradientVia',
|
|
119
|
+
to: 'gradientTo',
|
|
120
|
+
hoverFrom: 'hoverGradientFrom',
|
|
121
|
+
hoverVia: 'hoverGradientVia',
|
|
122
|
+
hoverTo: 'hoverGradientTo',
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract color classes from an element's class attribute.
|
|
127
|
+
* Returns a flat Record<string, Attribute> with keys like bg, text, gradientFrom, bgOpacity, etc.
|
|
128
|
+
*/
|
|
129
|
+
export function extractColorClasses(classAttr: string | null | undefined): Record<string, Attribute> | undefined {
|
|
130
|
+
if (!classAttr) return undefined
|
|
131
|
+
|
|
132
|
+
const classes = classAttr.split(/\s+/).filter(Boolean)
|
|
133
|
+
const result: Record<string, Attribute> = {}
|
|
134
|
+
|
|
135
|
+
for (const cls of classes) {
|
|
136
|
+
let matched = false
|
|
137
|
+
|
|
138
|
+
// Check color patterns
|
|
139
|
+
for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
|
|
140
|
+
if (pattern.test(cls)) {
|
|
141
|
+
const flatKey = COLOR_FLAT_KEYS[key]
|
|
142
|
+
if (flatKey && !(flatKey in result)) {
|
|
143
|
+
result[flatKey] = { value: cls }
|
|
144
|
+
}
|
|
145
|
+
matched = true
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check gradient patterns
|
|
151
|
+
if (!matched) {
|
|
152
|
+
for (const [key, pattern] of Object.entries(GRADIENT_CLASS_PATTERNS)) {
|
|
153
|
+
if (pattern.test(cls)) {
|
|
154
|
+
const flatKey = GRADIENT_FLAT_KEYS[key]
|
|
155
|
+
if (flatKey && !(flatKey in result)) {
|
|
156
|
+
result[flatKey] = { value: cls }
|
|
157
|
+
}
|
|
158
|
+
matched = true
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check opacity patterns
|
|
165
|
+
if (!matched) {
|
|
166
|
+
for (const [key, pattern] of Object.entries(OPACITY_CLASS_PATTERNS)) {
|
|
167
|
+
if (pattern.test(cls)) {
|
|
168
|
+
if (!(key in result)) {
|
|
169
|
+
result[key] = { value: cls }
|
|
170
|
+
}
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return Object.keys(result).length > 0 ? result : undefined
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Regex patterns for matching text style classes on elements.
|
|
182
|
+
*/
|
|
183
|
+
export const TEXT_STYLE_CLASS_PATTERNS: Record<string, RegExp> = {
|
|
184
|
+
fontWeight: /^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,
|
|
185
|
+
fontStyle: /^(italic|not-italic)$/,
|
|
186
|
+
textDecoration: /^(underline|overline|line-through|no-underline)$/,
|
|
187
|
+
fontSize: /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extract text style classes from an element's class attribute.
|
|
192
|
+
* Returns a Record<string, Attribute> with keys: fontWeight, fontStyle, textDecoration, fontSize.
|
|
193
|
+
*/
|
|
194
|
+
export function extractTextStyleClasses(classAttr: string | null | undefined): Record<string, Attribute> | undefined {
|
|
195
|
+
if (!classAttr) return undefined
|
|
196
|
+
|
|
197
|
+
const classes = classAttr.split(/\s+/).filter(Boolean)
|
|
198
|
+
const result: Record<string, Attribute> = {}
|
|
199
|
+
|
|
200
|
+
for (const cls of classes) {
|
|
201
|
+
for (const [key, pattern] of Object.entries(TEXT_STYLE_CLASS_PATTERNS)) {
|
|
202
|
+
if (pattern.test(cls) && !(key in result)) {
|
|
203
|
+
result[key] = { value: cls }
|
|
204
|
+
break
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return Object.keys(result).length > 0 ? result : undefined
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check if a class is a color class (including gradient colors).
|
|
214
|
+
*/
|
|
215
|
+
export function isColorClass(className: string): boolean {
|
|
216
|
+
return Object.values(COLOR_CLASS_PATTERNS).some(pattern => pattern.test(className))
|
|
217
|
+
|| Object.values(GRADIENT_CLASS_PATTERNS).some(pattern => pattern.test(className))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generate a new class string with a color class replaced.
|
|
222
|
+
*/
|
|
223
|
+
export function replaceColorClass(
|
|
224
|
+
currentClasses: string,
|
|
225
|
+
oldColorClass: string,
|
|
226
|
+
newColorClass: string,
|
|
227
|
+
): string {
|
|
228
|
+
const classes = currentClasses.split(/\s+/).filter(Boolean)
|
|
229
|
+
const newClasses = classes.map(cls => cls === oldColorClass ? newColorClass : cls)
|
|
230
|
+
return newClasses.join(' ')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the color type from a color class.
|
|
235
|
+
* Returns the flat key name (e.g., 'bg', 'gradientFrom', 'bgOpacity').
|
|
236
|
+
*/
|
|
237
|
+
export function getColorType(colorClass: string): string | undefined {
|
|
238
|
+
for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
|
|
239
|
+
if (pattern.test(colorClass)) {
|
|
240
|
+
return COLOR_FLAT_KEYS[key]
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const [key, pattern] of Object.entries(GRADIENT_CLASS_PATTERNS)) {
|
|
244
|
+
if (pattern.test(colorClass)) {
|
|
245
|
+
return GRADIENT_FLAT_KEYS[key]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const [key, pattern] of Object.entries(OPACITY_CLASS_PATTERNS)) {
|
|
249
|
+
if (pattern.test(colorClass)) {
|
|
250
|
+
return key
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Parse a color class into its components.
|
|
258
|
+
*/
|
|
259
|
+
export function parseColorClass(colorClass: string): {
|
|
260
|
+
prefix: string
|
|
261
|
+
colorName: string
|
|
262
|
+
shade?: string
|
|
263
|
+
isHover: boolean
|
|
264
|
+
isArbitrary?: boolean
|
|
265
|
+
} | undefined {
|
|
266
|
+
// Verify this is actually a color class using the comprehensive patterns
|
|
267
|
+
const isColor = Object.values(COLOR_CLASS_PATTERNS).some(p => p.test(colorClass))
|
|
268
|
+
|| Object.values(GRADIENT_CLASS_PATTERNS).some(p => p.test(colorClass))
|
|
269
|
+
if (!isColor) return undefined
|
|
270
|
+
|
|
271
|
+
const isHover = colorClass.startsWith('hover:')
|
|
272
|
+
const classWithoutHover = isHover ? colorClass.slice(6) : colorClass
|
|
273
|
+
|
|
274
|
+
// Try matching standard color classes (default colors, custom theme colors, and gradients)
|
|
275
|
+
const standardMatch = classWithoutHover.match(/^(bg|text|border|from|via|to)-([a-z]+)(?:-(\d+))?$/)
|
|
276
|
+
if (standardMatch) {
|
|
277
|
+
return {
|
|
278
|
+
prefix: isHover ? `hover:${standardMatch[1]}` : standardMatch[1]!,
|
|
279
|
+
colorName: standardMatch[2]!,
|
|
280
|
+
shade: standardMatch[3],
|
|
281
|
+
isHover,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Try matching arbitrary value classes like bg-[#41b883] or from-[#41b883]
|
|
286
|
+
const arbitraryMatch = classWithoutHover.match(/^(bg|text|border|from|via|to)-(\[.+\])$/)
|
|
287
|
+
if (arbitraryMatch) {
|
|
288
|
+
return {
|
|
289
|
+
prefix: isHover ? `hover:${arbitraryMatch[1]}` : arbitraryMatch[1]!,
|
|
290
|
+
colorName: arbitraryMatch[2]!,
|
|
291
|
+
shade: undefined,
|
|
292
|
+
isHover,
|
|
293
|
+
isArbitrary: true,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return undefined
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Build a color class from components.
|
|
302
|
+
*/
|
|
303
|
+
export function buildColorClass(
|
|
304
|
+
prefix: string,
|
|
305
|
+
colorName: string,
|
|
306
|
+
shade?: string,
|
|
307
|
+
): string {
|
|
308
|
+
if (shade) {
|
|
309
|
+
return `${prefix}-${colorName}-${shade}`
|
|
310
|
+
}
|
|
311
|
+
return `${prefix}-${colorName}`
|
|
312
|
+
}
|
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'purple',
|
|
26
|
-
'fuchsia',
|
|
27
|
-
'pink',
|
|
28
|
-
'rose',
|
|
29
|
-
] as const
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Standard Tailwind color shades.
|
|
33
|
-
*/
|
|
34
|
-
export const STANDARD_SHADES = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] as const
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Special color values that don't have shades.
|
|
38
|
-
*/
|
|
39
|
-
export const SPECIAL_COLORS = ['transparent', 'current', 'inherit', 'white', 'black'] as const
|
|
1
|
+
import {
|
|
2
|
+
buildColorClass,
|
|
3
|
+
COLOR_CLASS_PATTERNS,
|
|
4
|
+
DEFAULT_TAILWIND_COLORS,
|
|
5
|
+
extractColorClasses,
|
|
6
|
+
isColorClass,
|
|
7
|
+
parseColorClass,
|
|
8
|
+
SPECIAL_COLORS,
|
|
9
|
+
STANDARD_SHADES,
|
|
10
|
+
} from '../color-patterns'
|
|
11
|
+
import type { Attribute, AvailableColors } from './types'
|
|
12
|
+
|
|
13
|
+
// Re-export shared detection logic for consumers
|
|
14
|
+
export {
|
|
15
|
+
buildColorClass,
|
|
16
|
+
COLOR_CLASS_PATTERNS,
|
|
17
|
+
DEFAULT_TAILWIND_COLORS,
|
|
18
|
+
extractColorClasses,
|
|
19
|
+
getColorType,
|
|
20
|
+
isColorClass,
|
|
21
|
+
parseColorClass,
|
|
22
|
+
SPECIAL_COLORS,
|
|
23
|
+
STANDARD_SHADES,
|
|
24
|
+
} from '../color-patterns'
|
|
40
25
|
|
|
41
26
|
/**
|
|
42
27
|
* Map of Tailwind color names to their CSS color values for preview.
|
|
@@ -97,140 +82,12 @@ export const SHADE_LIGHTNESS: Record<string, number> = {
|
|
|
97
82
|
'950': 0.05,
|
|
98
83
|
}
|
|
99
84
|
|
|
100
|
-
/**
|
|
101
|
-
* Non-color text-* utility classes that should NOT be treated as color classes.
|
|
102
|
-
* These are Tailwind utilities like text alignment, sizing, wrapping, etc.
|
|
103
|
-
*/
|
|
104
|
-
const NON_COLOR_TEXT_CLASSES = new Set([
|
|
105
|
-
// Text alignment
|
|
106
|
-
'text-left',
|
|
107
|
-
'text-center',
|
|
108
|
-
'text-right',
|
|
109
|
-
'text-justify',
|
|
110
|
-
'text-start',
|
|
111
|
-
'text-end',
|
|
112
|
-
// Text wrapping
|
|
113
|
-
'text-wrap',
|
|
114
|
-
'text-nowrap',
|
|
115
|
-
'text-balance',
|
|
116
|
-
'text-pretty',
|
|
117
|
-
// Text overflow
|
|
118
|
-
'text-ellipsis',
|
|
119
|
-
'text-clip',
|
|
120
|
-
// Text transform (these don't start with text- but just in case)
|
|
121
|
-
])
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Regex patterns to match Tailwind color classes.
|
|
125
|
-
*/
|
|
126
|
-
const COLOR_CLASS_PATTERNS = {
|
|
127
|
-
bg: /^bg-([a-z]+)(?:-(\d+))?$/,
|
|
128
|
-
text: /^text-([a-z]+)(?:-(\d+))?$/,
|
|
129
|
-
border: /^border-([a-z]+)(?:-(\d+))?$/,
|
|
130
|
-
hoverBg: /^hover:bg-([a-z]+)(?:-(\d+))?$/,
|
|
131
|
-
hoverText: /^hover:text-([a-z]+)(?:-(\d+))?$/,
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Check if a class is a text color class (not a non-color text utility).
|
|
136
|
-
*/
|
|
137
|
-
function isTextColorClass(className: string): boolean {
|
|
138
|
-
if (NON_COLOR_TEXT_CLASSES.has(className)) {
|
|
139
|
-
return false
|
|
140
|
-
}
|
|
141
|
-
return COLOR_CLASS_PATTERNS.text.test(className)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Parse a color class into its components.
|
|
146
|
-
*/
|
|
147
|
-
export function parseColorClass(colorClass: string): {
|
|
148
|
-
prefix: string
|
|
149
|
-
colorName: string
|
|
150
|
-
shade?: string
|
|
151
|
-
isHover: boolean
|
|
152
|
-
} | undefined {
|
|
153
|
-
// Exclude non-color text utility classes
|
|
154
|
-
if (NON_COLOR_TEXT_CLASSES.has(colorClass)) {
|
|
155
|
-
return undefined
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const isHover = colorClass.startsWith('hover:')
|
|
159
|
-
const classWithoutHover = isHover ? colorClass.slice(6) : colorClass
|
|
160
|
-
|
|
161
|
-
// Also check hover variants of non-color text classes
|
|
162
|
-
if (isHover && NON_COLOR_TEXT_CLASSES.has(`text-${classWithoutHover.slice(5)}`)) {
|
|
163
|
-
return undefined
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const match = classWithoutHover.match(/^(bg|text|border)-([a-z]+)(?:-(\d+))?$/)
|
|
167
|
-
|
|
168
|
-
if (!match) return undefined
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
prefix: isHover ? `hover:${match[1]}` : match[1]!,
|
|
172
|
-
colorName: match[2]!,
|
|
173
|
-
shade: match[3],
|
|
174
|
-
isHover,
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Build a color class from components.
|
|
180
|
-
*/
|
|
181
|
-
export function buildColorClass(
|
|
182
|
-
prefix: string,
|
|
183
|
-
colorName: string,
|
|
184
|
-
shade?: string,
|
|
185
|
-
): string {
|
|
186
|
-
if (shade) {
|
|
187
|
-
return `${prefix}-${colorName}-${shade}`
|
|
188
|
-
}
|
|
189
|
-
return `${prefix}-${colorName}`
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get the color type from a color class.
|
|
194
|
-
*/
|
|
195
|
-
export function getColorType(colorClass: string): keyof typeof COLOR_CLASS_PATTERNS | undefined {
|
|
196
|
-
for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
|
|
197
|
-
// For text type, use the helper to exclude non-color text utilities
|
|
198
|
-
if (key === 'text' || key === 'hoverText') {
|
|
199
|
-
if (key === 'text' && isTextColorClass(colorClass)) {
|
|
200
|
-
return key as keyof typeof COLOR_CLASS_PATTERNS
|
|
201
|
-
}
|
|
202
|
-
if (key === 'hoverText' && colorClass.startsWith('hover:') && isTextColorClass(colorClass.slice(6))) {
|
|
203
|
-
return key as keyof typeof COLOR_CLASS_PATTERNS
|
|
204
|
-
}
|
|
205
|
-
} else if (pattern.test(colorClass)) {
|
|
206
|
-
return key as keyof typeof COLOR_CLASS_PATTERNS
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return undefined
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Check if a class is a color class.
|
|
214
|
-
*/
|
|
215
|
-
export function isColorClass(className: string): boolean {
|
|
216
|
-
// Check text color separately to exclude non-color text utilities
|
|
217
|
-
if (COLOR_CLASS_PATTERNS.text.test(className)) {
|
|
218
|
-
return isTextColorClass(className)
|
|
219
|
-
}
|
|
220
|
-
if (COLOR_CLASS_PATTERNS.hoverText.test(className)) {
|
|
221
|
-
return isTextColorClass(className.slice(6)) // Remove 'hover:' prefix
|
|
222
|
-
}
|
|
223
|
-
return Object.entries(COLOR_CLASS_PATTERNS)
|
|
224
|
-
.filter(([key]) => key !== 'text' && key !== 'hoverText')
|
|
225
|
-
.some(([, pattern]) => pattern.test(className))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
85
|
/**
|
|
229
86
|
* Get a preview CSS color for a Tailwind color.
|
|
230
87
|
*/
|
|
231
88
|
export function getColorPreview(colorName: string, shade?: string): string {
|
|
232
89
|
// Special colors without shades
|
|
233
|
-
if (SPECIAL_COLORS.includes(colorName
|
|
90
|
+
if ((SPECIAL_COLORS as readonly string[]).includes(colorName)) {
|
|
234
91
|
return COLOR_PREVIEW_MAP[colorName] || colorName
|
|
235
92
|
}
|
|
236
93
|
|
|
@@ -293,27 +150,19 @@ export function replaceColorClass(
|
|
|
293
150
|
const classes = element.className.split(/\s+/).filter(Boolean)
|
|
294
151
|
const pattern = COLOR_CLASS_PATTERNS[colorType]
|
|
295
152
|
|
|
153
|
+
const prefix = colorType === 'hoverBg'
|
|
154
|
+
? 'hover:bg'
|
|
155
|
+
: colorType === 'hoverText'
|
|
156
|
+
? 'hover:text'
|
|
157
|
+
: colorType
|
|
158
|
+
const newClass = buildColorClass(prefix, newColorName, newShade)
|
|
159
|
+
|
|
296
160
|
let oldClass: string | undefined
|
|
297
161
|
const newClasses: string[] = []
|
|
298
162
|
|
|
299
163
|
for (const cls of classes) {
|
|
300
|
-
|
|
301
|
-
let isColorMatch: boolean
|
|
302
|
-
if (colorType === 'text') {
|
|
303
|
-
isColorMatch = isTextColorClass(cls)
|
|
304
|
-
} else if (colorType === 'hoverText') {
|
|
305
|
-
isColorMatch = cls.startsWith('hover:') && isTextColorClass(cls.slice(6))
|
|
306
|
-
} else {
|
|
307
|
-
isColorMatch = pattern.test(cls)
|
|
308
|
-
}
|
|
309
|
-
if (isColorMatch) {
|
|
164
|
+
if (pattern.test(cls)) {
|
|
310
165
|
oldClass = cls
|
|
311
|
-
// Build the new class with the same prefix
|
|
312
|
-
const isHover = colorType.startsWith('hover')
|
|
313
|
-
const prefix = isHover
|
|
314
|
-
? colorType.replace('hover', 'hover:').toLowerCase().replace('hover:bg', 'hover:bg').replace('hover:text', 'hover:text')
|
|
315
|
-
: colorType
|
|
316
|
-
const newClass = buildColorClass(prefix, newColorName, newShade)
|
|
317
166
|
newClasses.push(newClass)
|
|
318
167
|
} else {
|
|
319
168
|
newClasses.push(cls)
|
|
@@ -324,12 +173,6 @@ export function replaceColorClass(
|
|
|
324
173
|
return undefined
|
|
325
174
|
}
|
|
326
175
|
|
|
327
|
-
const newClass = buildColorClass(
|
|
328
|
-
colorType.startsWith('hover') ? `hover:${colorType.slice(5).toLowerCase()}` : colorType,
|
|
329
|
-
newColorName,
|
|
330
|
-
newShade,
|
|
331
|
-
)
|
|
332
|
-
|
|
333
176
|
element.className = newClasses.join(' ')
|
|
334
177
|
|
|
335
178
|
return { oldClass, newClass }
|
|
@@ -339,30 +182,7 @@ export function replaceColorClass(
|
|
|
339
182
|
* Get the current color classes from an element as Record<string, Attribute>.
|
|
340
183
|
*/
|
|
341
184
|
export function getElementColorClasses(element: HTMLElement): Record<string, Attribute> {
|
|
342
|
-
|
|
343
|
-
const colorClasses: Record<string, Attribute> = {}
|
|
344
|
-
|
|
345
|
-
for (const cls of classes) {
|
|
346
|
-
for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
|
|
347
|
-
// For text types, use the helper to exclude non-color text utilities
|
|
348
|
-
let isColorMatch: boolean
|
|
349
|
-
if (key === 'text') {
|
|
350
|
-
isColorMatch = isTextColorClass(cls)
|
|
351
|
-
} else if (key === 'hoverText') {
|
|
352
|
-
isColorMatch = cls.startsWith('hover:') && isTextColorClass(cls.slice(6))
|
|
353
|
-
} else {
|
|
354
|
-
isColorMatch = pattern.test(cls)
|
|
355
|
-
}
|
|
356
|
-
if (isColorMatch) {
|
|
357
|
-
if (!(key in colorClasses)) {
|
|
358
|
-
colorClasses[key] = { value: cls }
|
|
359
|
-
}
|
|
360
|
-
break
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return colorClasses
|
|
185
|
+
return extractColorClasses(element.className) ?? {}
|
|
366
186
|
}
|
|
367
187
|
|
|
368
188
|
/**
|
|
@@ -401,30 +221,20 @@ export function applyColorChange(
|
|
|
401
221
|
const classes = element.className.split(/\s+/).filter(Boolean)
|
|
402
222
|
const pattern = COLOR_CLASS_PATTERNS[colorType]
|
|
403
223
|
|
|
404
|
-
let oldClass: string | undefined
|
|
405
|
-
const newClasses: string[] = []
|
|
406
|
-
|
|
407
224
|
// Determine the new class prefix
|
|
408
225
|
const prefix = colorType === 'hoverBg'
|
|
409
226
|
? 'hover:bg'
|
|
410
227
|
: colorType === 'hoverText'
|
|
411
|
-
|
|
412
|
-
|
|
228
|
+
? 'hover:text'
|
|
229
|
+
: colorType
|
|
413
230
|
const newClass = buildColorClass(prefix, newColorName, newShade)
|
|
414
231
|
|
|
232
|
+
let oldClass: string | undefined
|
|
233
|
+
const newClasses: string[] = []
|
|
234
|
+
|
|
415
235
|
for (const cls of classes) {
|
|
416
|
-
|
|
417
|
-
let isColorMatch: boolean
|
|
418
|
-
if (colorType === 'text') {
|
|
419
|
-
isColorMatch = isTextColorClass(cls)
|
|
420
|
-
} else if (colorType === 'hoverText') {
|
|
421
|
-
isColorMatch = cls.startsWith('hover:') && isTextColorClass(cls.slice(6))
|
|
422
|
-
} else {
|
|
423
|
-
isColorMatch = pattern.test(cls)
|
|
424
|
-
}
|
|
425
|
-
if (isColorMatch) {
|
|
236
|
+
if (pattern.test(cls)) {
|
|
426
237
|
oldClass = cls
|
|
427
|
-
// Replace with new class
|
|
428
238
|
newClasses.push(newClass)
|
|
429
239
|
} else {
|
|
430
240
|
newClasses.push(cls)
|
|
@@ -456,10 +266,10 @@ export function applyColorChange(
|
|
|
456
266
|
const styleProperty = colorType === 'bg'
|
|
457
267
|
? 'backgroundColor'
|
|
458
268
|
: colorType === 'text'
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
269
|
+
? 'color'
|
|
270
|
+
: colorType === 'border'
|
|
271
|
+
? 'borderColor'
|
|
272
|
+
: 'color'
|
|
463
273
|
element.style[styleProperty] = cssValue
|
|
464
274
|
}
|
|
465
275
|
}
|
|
@@ -540,7 +350,7 @@ export function getAllColorsWithShades(availableColors: AvailableColors | undefi
|
|
|
540
350
|
|
|
541
351
|
return availableColors.colors.map(color => {
|
|
542
352
|
const shades = Object.keys(color.values)
|
|
543
|
-
const isSpecial = SPECIAL_COLORS.includes(color.name
|
|
353
|
+
const isSpecial = (SPECIAL_COLORS as readonly string[]).includes(color.name)
|
|
544
354
|
|
|
545
355
|
return {
|
|
546
356
|
name: color.name,
|
package/src/editor/dom.ts
CHANGED
|
@@ -231,6 +231,15 @@ export function getEditableHtmlFromElement(el: HTMLElement): string {
|
|
|
231
231
|
child.removeAttribute('contenteditable')
|
|
232
232
|
})
|
|
233
233
|
|
|
234
|
+
// Clean up styled spans — strip editor-only attributes and inline preview styles
|
|
235
|
+
// (Tailwind classes are the source of truth for styling)
|
|
236
|
+
clone.querySelectorAll('[data-cms-styled]').forEach(span => {
|
|
237
|
+
span.removeAttribute('style')
|
|
238
|
+
span.removeAttribute('data-cms-styled')
|
|
239
|
+
span.removeAttribute('data-cms-hover-bg')
|
|
240
|
+
span.removeAttribute('data-cms-hover-text')
|
|
241
|
+
})
|
|
242
|
+
|
|
234
243
|
return clone.innerHTML
|
|
235
244
|
}
|
|
236
245
|
|