@ngcorex/css 0.1.6 → 0.2.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.
Files changed (37) hide show
  1. package/dist/constraints/constraint-error.d.ts.map +1 -1
  2. package/dist/constraints/constraint-error.js +2 -2
  3. package/dist/engine/build-css.js +2 -2
  4. package/dist/errors/ngcorex-error.d.ts +68 -1
  5. package/dist/errors/ngcorex-error.d.ts.map +1 -1
  6. package/dist/errors/ngcorex-error.js +135 -4
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/tokens/normalize-colors.js +2 -2
  11. package/dist/tokens/normalize.d.ts.map +1 -1
  12. package/dist/tokens/normalize.js +2 -2
  13. package/dist/validation/color-format.d.ts +22 -0
  14. package/dist/validation/color-format.d.ts.map +1 -0
  15. package/dist/validation/color-format.js +249 -0
  16. package/dist/validation/index.d.ts +42 -0
  17. package/dist/validation/index.d.ts.map +1 -0
  18. package/dist/validation/index.js +206 -0
  19. package/dist/validation/scale-consistency.d.ts +14 -0
  20. package/dist/validation/scale-consistency.d.ts.map +1 -0
  21. package/dist/validation/scale-consistency.js +215 -0
  22. package/dist/validation/shadow-format.d.ts +14 -0
  23. package/dist/validation/shadow-format.d.ts.map +1 -0
  24. package/dist/validation/shadow-format.js +217 -0
  25. package/dist/validation/spacing-format.d.ts +14 -0
  26. package/dist/validation/spacing-format.d.ts.map +1 -0
  27. package/dist/validation/spacing-format.js +173 -0
  28. package/dist/validation/token-duplicates.d.ts +24 -0
  29. package/dist/validation/token-duplicates.d.ts.map +1 -0
  30. package/dist/validation/token-duplicates.js +149 -0
  31. package/dist/validation/types.d.ts +160 -0
  32. package/dist/validation/types.d.ts.map +1 -0
  33. package/dist/validation/types.js +4 -0
  34. package/dist/validation/z-index-logic.d.ts +14 -0
  35. package/dist/validation/z-index-logic.d.ts.map +1 -0
  36. package/dist/validation/z-index-logic.js +230 -0
  37. package/package.json +6 -6
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Main validation entry point
3
+ * Coordinates all non-blocking token validations
4
+ */
5
+ import { validateDuplicateTokens } from './token-duplicates.js';
6
+ import { validateScaleConsistency } from './scale-consistency.js';
7
+ import { validateColorFormat } from './color-format.js';
8
+ import { validateSpacingFormat } from './spacing-format.js';
9
+ import { validateShadowFormat } from './shadow-format.js';
10
+ import { validateZIndexLogic } from './z-index-logic.js';
11
+ /**
12
+ * Default validation configuration
13
+ */
14
+ export const DEFAULT_VALIDATION_CONFIG = {
15
+ enabled: {
16
+ duplicateTokens: true,
17
+ scaleConsistency: true,
18
+ colorFormat: true,
19
+ spacingFormat: true,
20
+ shadowFormat: true,
21
+ zIndexLogic: true
22
+ },
23
+ severity: 'warning'
24
+ };
25
+ /**
26
+ * Run all enabled validations on tokens
27
+ */
28
+ export function runValidations(tokens, config = DEFAULT_VALIDATION_CONFIG) {
29
+ const results = [];
30
+ // Run duplicate token validation
31
+ if (config.enabled.duplicateTokens) {
32
+ const duplicateResults = validateDuplicateTokens(tokens, 'tokens.json', config.severity);
33
+ results.push(...duplicateResults);
34
+ }
35
+ // Run scale consistency validation
36
+ if (config.enabled.scaleConsistency) {
37
+ const scaleResults = validateScaleConsistency(tokens, config.severity);
38
+ results.push(...scaleResults);
39
+ }
40
+ // Run color format validation
41
+ if (config.enabled.colorFormat) {
42
+ const colorResults = validateColorFormat(tokens, config.severity);
43
+ results.push(...colorResults);
44
+ }
45
+ // Run spacing format validation
46
+ if (config.enabled.spacingFormat) {
47
+ const spacingResults = validateSpacingFormat(tokens, config.severity);
48
+ results.push(...spacingResults);
49
+ }
50
+ // Run shadow format validation
51
+ if (config.enabled.shadowFormat) {
52
+ const shadowResults = validateShadowFormat(tokens, config.severity);
53
+ results.push(...shadowResults);
54
+ }
55
+ // Run z-index logic validation
56
+ if (config.enabled.zIndexLogic) {
57
+ const zIndexResults = validateZIndexLogic(tokens, config.severity);
58
+ results.push(...zIndexResults);
59
+ }
60
+ // Run custom validators
61
+ if (config.customValidators) {
62
+ for (const validator of config.customValidators) {
63
+ const customResults = validator.validate(tokens);
64
+ results.push(...customResults);
65
+ }
66
+ }
67
+ // Count results by severity
68
+ const summary = {
69
+ info: results.filter(r => r.severity === 'info').length,
70
+ warning: results.filter(r => r.severity === 'warning').length,
71
+ error: results.filter(r => r.severity === 'error').length
72
+ };
73
+ // Report is valid if there are no errors (warnings and info are non-blocking)
74
+ const valid = summary.error === 0;
75
+ return {
76
+ valid,
77
+ results,
78
+ summary
79
+ };
80
+ }
81
+ /**
82
+ * Run validations and return only warnings and errors (filter out info)
83
+ */
84
+ export function runValidationsWithoutInfo(tokens, config = DEFAULT_VALIDATION_CONFIG) {
85
+ const report = runValidations(tokens, config);
86
+ const filteredResults = report.results.filter(r => r.severity !== 'info');
87
+ return {
88
+ valid: report.valid,
89
+ results: filteredResults,
90
+ summary: {
91
+ info: 0,
92
+ warning: filteredResults.filter(r => r.severity === 'warning').length,
93
+ error: filteredResults.filter(r => r.severity === 'error').length
94
+ }
95
+ };
96
+ }
97
+ /**
98
+ * Get a human-readable summary of the validation report
99
+ */
100
+ export function getValidationSummary(report) {
101
+ const lines = [];
102
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
103
+ lines.push(' 🔍 Token Validation Report');
104
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
105
+ lines.push('');
106
+ if (report.valid) {
107
+ lines.push(' ✅ No blocking errors found');
108
+ }
109
+ else {
110
+ lines.push(` ❌ ${report.summary.error} error${report.summary.error > 1 ? 's' : ''} found`);
111
+ }
112
+ if (report.summary.warning > 0) {
113
+ lines.push(` ⚠️ ${report.summary.warning} warning${report.summary.warning > 1 ? 's' : ''}`);
114
+ }
115
+ if (report.summary.info > 0) {
116
+ lines.push(` ℹ️ ${report.summary.info} info message${report.summary.info > 1 ? 's' : ''}`);
117
+ }
118
+ lines.push('');
119
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
120
+ lines.push('');
121
+ return lines.join('\n');
122
+ }
123
+ /**
124
+ * Print validation results grouped by severity
125
+ */
126
+ export function printValidationResults(report) {
127
+ if (report.results.length === 0) {
128
+ console.log('✅ No validation issues found');
129
+ return;
130
+ }
131
+ // Print errors first
132
+ const errors = report.results.filter(r => r.severity === 'error');
133
+ if (errors.length > 0) {
134
+ console.log('');
135
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
136
+ console.log(` ❌ ${errors.length} Error${errors.length > 1 ? 's' : ''}`);
137
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
138
+ console.log('');
139
+ for (const result of errors) {
140
+ console.log(` ❌ ${result.message}`);
141
+ console.log(` Path: ${result.path}`);
142
+ if (result.suggestion) {
143
+ console.log(` 💡 ${result.suggestion}`);
144
+ }
145
+ console.log('');
146
+ }
147
+ }
148
+ // Print warnings
149
+ const warnings = report.results.filter(r => r.severity === 'warning');
150
+ if (warnings.length > 0) {
151
+ console.log('');
152
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
153
+ console.log(` ⚠️ ${warnings.length} Warning${warnings.length > 1 ? 's' : ''}`);
154
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
155
+ console.log('');
156
+ for (const result of warnings) {
157
+ console.log(` ⚠️ ${result.message}`);
158
+ console.log(` Path: ${result.path}`);
159
+ if (result.suggestion) {
160
+ console.log(` 💡 ${result.suggestion}`);
161
+ }
162
+ console.log('');
163
+ }
164
+ }
165
+ // Print info messages
166
+ const info = report.results.filter(r => r.severity === 'info');
167
+ if (info.length > 0) {
168
+ console.log('');
169
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
170
+ console.log(` ℹ️ ${info.length} Info Message${info.length > 1 ? 's' : ''}`);
171
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
172
+ console.log('');
173
+ for (const result of info) {
174
+ console.log(` ℹ️ ${result.message}`);
175
+ console.log(` Path: ${result.path}`);
176
+ if (result.suggestion) {
177
+ console.log(` 💡 ${result.suggestion}`);
178
+ }
179
+ console.log('');
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * Check if validation report has any results
185
+ */
186
+ export function hasValidationResults(report) {
187
+ return report.results.length > 0;
188
+ }
189
+ /**
190
+ * Check if validation report has errors
191
+ */
192
+ export function hasValidationErrors(report) {
193
+ return report.summary.error > 0;
194
+ }
195
+ /**
196
+ * Check if validation report has warnings
197
+ */
198
+ export function hasValidationWarnings(report) {
199
+ return report.summary.warning > 0;
200
+ }
201
+ /**
202
+ * Check if validation report has info messages
203
+ */
204
+ export function hasValidationInfo(report) {
205
+ return report.summary.info > 0;
206
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Scale consistency checks
3
+ * Checks for logical progressions in token scales (e.g., spacing, radius, typography)
4
+ */
5
+ import type { ValidationResult, TokensForValidation, ScaleConsistencyIssue } from './types.js';
6
+ /**
7
+ * Validate all scales for consistency
8
+ */
9
+ export declare function validateScaleConsistency(tokens: TokensForValidation, severity?: 'info' | 'warning' | 'error'): ValidationResult[];
10
+ /**
11
+ * Get a summary of scale consistency issues
12
+ */
13
+ export declare function getScaleConsistencySummary(issues: ScaleConsistencyIssue[]): string;
14
+ //# sourceMappingURL=scale-consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scale-consistency.d.ts","sourceRoot":"","sources":["../../src/validation/scale-consistency.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAoMpB;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,GAAE,MAAM,GAAG,SAAS,GAAG,OAAmB,GACjD,gBAAgB,EAAE,CAgCpB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,CAelF"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Scale consistency checks
3
+ * Checks for logical progressions in token scales (e.g., spacing, radius, typography)
4
+ */
5
+ /**
6
+ * Validation code for scale consistency
7
+ */
8
+ const SCALE_CONSISTENCY_CODE = 'SCALE_CONSISTENCY';
9
+ /**
10
+ * Common scale orders for different categories
11
+ */
12
+ const COMMON_SCALE_ORDERS = {
13
+ spacing: {
14
+ category: 'spacing',
15
+ expectedOrder: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'],
16
+ isNumeric: true
17
+ },
18
+ radius: {
19
+ category: 'radius',
20
+ expectedOrder: ['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', 'full'],
21
+ isNumeric: true
22
+ },
23
+ fontSize: {
24
+ category: 'fontSize',
25
+ expectedOrder: ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl'],
26
+ isNumeric: true
27
+ },
28
+ fontWeight: {
29
+ category: 'fontWeight',
30
+ expectedOrder: ['thin', 'light', 'normal', 'medium', 'semibold', 'bold', 'extrabold', 'black'],
31
+ isNumeric: true
32
+ },
33
+ lineHeight: {
34
+ category: 'lineHeight',
35
+ expectedOrder: ['none', 'tight', 'snug', 'normal', 'relaxed', 'loose'],
36
+ isNumeric: true
37
+ },
38
+ zIndex: {
39
+ category: 'zIndex',
40
+ expectedOrder: ['base', 'dropdown', 'sticky', 'fixed', 'modal', 'popover', 'tooltip'],
41
+ isNumeric: true
42
+ }
43
+ };
44
+ /**
45
+ * Parse a CSS value to extract numeric value and unit
46
+ */
47
+ function parseCssValue(value) {
48
+ const match = value.match(/^(-?\d*\.?\d+)(px|rem|em|%|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|s|ms)?$/);
49
+ if (!match)
50
+ return null;
51
+ return {
52
+ value: parseFloat(match[1]),
53
+ unit: match[2] || ''
54
+ };
55
+ }
56
+ /**
57
+ * Check if values are in a monotonic progression
58
+ */
59
+ function checkMonotonicProgression(values, expectedIncreasing = true) {
60
+ const issues = [];
61
+ for (let i = 1; i < values.length; i++) {
62
+ const diff = values[i] - values[i - 1];
63
+ if (expectedIncreasing && diff <= 0) {
64
+ issues.push(i);
65
+ }
66
+ else if (!expectedIncreasing && diff >= 0) {
67
+ issues.push(i);
68
+ }
69
+ }
70
+ return {
71
+ isMonotonic: issues.length === 0,
72
+ issues
73
+ };
74
+ }
75
+ /**
76
+ * Check for gaps in a numeric progression
77
+ */
78
+ function checkForGaps(values, keys, expectedOrder) {
79
+ const gapIndices = [];
80
+ // Check if any expected keys are missing
81
+ for (let i = 0; i < expectedOrder.length; i++) {
82
+ if (!keys.includes(expectedOrder[i])) {
83
+ // Find the position where this key should be
84
+ const position = expectedOrder.indexOf(expectedOrder[i]);
85
+ if (position < keys.length) {
86
+ gapIndices.push(i);
87
+ }
88
+ }
89
+ }
90
+ return {
91
+ hasGaps: gapIndices.length > 0,
92
+ gapIndices
93
+ };
94
+ }
95
+ /**
96
+ * Validate scale consistency for a token category
97
+ */
98
+ function validateScale(category, tokens, severity = 'warning') {
99
+ const results = [];
100
+ const scaleOrder = COMMON_SCALE_ORDERS[category];
101
+ if (!scaleOrder) {
102
+ // No predefined order for this category, skip
103
+ return results;
104
+ }
105
+ // Get keys in expected order (if they exist)
106
+ const keys = [];
107
+ const values = [];
108
+ const keyToValueMap = new Map();
109
+ for (const key of scaleOrder.expectedOrder) {
110
+ if (key in tokens) {
111
+ keys.push(key);
112
+ const rawValue = tokens[key];
113
+ let numericValue;
114
+ if (typeof rawValue === 'number') {
115
+ numericValue = rawValue;
116
+ }
117
+ else {
118
+ const parsed = parseCssValue(rawValue);
119
+ if (parsed) {
120
+ numericValue = parsed.value;
121
+ }
122
+ else {
123
+ // Can't parse as numeric, skip this key
124
+ continue;
125
+ }
126
+ }
127
+ values.push(numericValue);
128
+ keyToValueMap.set(key, { value: numericValue, raw: rawValue });
129
+ }
130
+ }
131
+ if (values.length < 2) {
132
+ // Not enough values to check consistency
133
+ return results;
134
+ }
135
+ // Check for monotonic progression
136
+ const { isMonotonic, issues } = checkMonotonicProgression(values, scaleOrder.isNumeric);
137
+ if (!isMonotonic) {
138
+ for (const issueIndex of issues) {
139
+ const key = keys[issueIndex];
140
+ const prevKey = keys[issueIndex - 1];
141
+ const currentValue = keyToValueMap.get(key).raw;
142
+ const prevValue = keyToValueMap.get(prevKey).raw;
143
+ results.push({
144
+ severity,
145
+ code: SCALE_CONSISTENCY_CODE,
146
+ message: `Non-monotonic progression in ${category} scale: "${key}" (${currentValue}) is not greater than "${prevKey}" (${prevValue})`,
147
+ path: `${category}.${key}`,
148
+ suggestion: `Consider adjusting the value of "${key}" to be greater than "${prevKey}" for a consistent scale.`,
149
+ example: `// Example:\n// ${category}.${prevKey}: ${prevValue}\n// ${category}.${key}: ${currentValue} // Should be > ${prevValue}`
150
+ });
151
+ }
152
+ }
153
+ // Check for gaps in the scale
154
+ const { hasGaps, gapIndices } = checkForGaps(values, keys, scaleOrder.expectedOrder);
155
+ // Skip "missing keys" info for zIndex - z-index-logic.ts provides more semantic "missing layers" message
156
+ if (hasGaps && category !== 'zIndex') {
157
+ const missingKeys = gapIndices.map(i => scaleOrder.expectedOrder[i]);
158
+ results.push({
159
+ severity: 'info', // Gaps are less severe than non-monotonic values
160
+ code: SCALE_CONSISTENCY_CODE,
161
+ message: `Potential gaps in ${category} scale: missing keys ${missingKeys.map(k => `"${k}"`).join(', ')}`,
162
+ path: category,
163
+ suggestion: `Consider adding the missing scale keys for a more complete scale.`,
164
+ example: `// Example:\n// ${category}.${missingKeys[0]}: "value"`
165
+ });
166
+ }
167
+ return results;
168
+ }
169
+ /**
170
+ * Validate all scales for consistency
171
+ */
172
+ export function validateScaleConsistency(tokens, severity = 'warning') {
173
+ const results = [];
174
+ // Validate spacing scale
175
+ if (tokens.spacing) {
176
+ results.push(...validateScale('spacing', tokens.spacing, severity));
177
+ }
178
+ // Validate radius scale
179
+ if (tokens.radius) {
180
+ results.push(...validateScale('radius', tokens.radius, severity));
181
+ }
182
+ // Validate typography scales
183
+ if (tokens.typography) {
184
+ if (tokens.typography.fontSize) {
185
+ results.push(...validateScale('fontSize', tokens.typography.fontSize, severity));
186
+ }
187
+ if (tokens.typography.fontWeight) {
188
+ results.push(...validateScale('fontWeight', tokens.typography.fontWeight, severity));
189
+ }
190
+ if (tokens.typography.lineHeight) {
191
+ results.push(...validateScale('lineHeight', tokens.typography.lineHeight, severity));
192
+ }
193
+ }
194
+ // Validate z-index scale
195
+ if (tokens.zIndex) {
196
+ results.push(...validateScale('zIndex', tokens.zIndex, severity));
197
+ }
198
+ return results;
199
+ }
200
+ /**
201
+ * Get a summary of scale consistency issues
202
+ */
203
+ export function getScaleConsistencySummary(issues) {
204
+ if (issues.length === 0) {
205
+ return 'No scale consistency issues found.';
206
+ }
207
+ const summary = [];
208
+ summary.push(`Found ${issues.length} scale consistency issue${issues.length > 1 ? 's' : ''}:`);
209
+ for (const issue of issues) {
210
+ summary.push(` • ${issue.category}: ${issue.issue}`);
211
+ summary.push(` ${issue.details}`);
212
+ summary.push(` 💡 ${issue.suggestion}`);
213
+ }
214
+ return summary.join('\n');
215
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shadow format validation
3
+ * Checks for valid CSS box-shadow syntax
4
+ */
5
+ import type { ValidationResult, TokensForValidation, ShadowFormatIssue } from './types.js';
6
+ /**
7
+ * Validate all shadow formats
8
+ */
9
+ export declare function validateShadowFormat(tokens: TokensForValidation, severity?: 'info' | 'warning' | 'error'): ValidationResult[];
10
+ /**
11
+ * Get a summary of shadow format issues
12
+ */
13
+ export declare function getShadowFormatSummary(issues: ShadowFormatIssue[]): string;
14
+ //# sourceMappingURL=shadow-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-format.d.ts","sourceRoot":"","sources":["../../src/validation/shadow-format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AA6NpB;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,GAAE,MAAM,GAAG,SAAS,GAAG,OAAmB,GACjD,gBAAgB,EAAE,CAqBpB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAe1E"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Shadow format validation
3
+ * Checks for valid CSS box-shadow syntax
4
+ */
5
+ import { isValidColor, getColorFormat } from './color-format.js';
6
+ /**
7
+ * Validation code for shadow format
8
+ */
9
+ const SHADOW_FORMAT_CODE = 'SHADOW_FORMAT';
10
+ /**
11
+ * Parse a box-shadow value
12
+ * Format: offset-x | offset-y | blur-radius | spread-radius | color | inset
13
+ */
14
+ function parseBoxShadow(value) {
15
+ const trimmed = value.trim();
16
+ const parts = trimmed.split(/\s+/);
17
+ const result = {
18
+ valid: false,
19
+ offsetX: undefined,
20
+ offsetY: undefined,
21
+ blur: undefined,
22
+ spread: undefined,
23
+ color: undefined,
24
+ inset: false,
25
+ error: undefined
26
+ };
27
+ // Check for inset keyword
28
+ let startIndex = 0;
29
+ if (parts[0] === 'inset') {
30
+ result.inset = true;
31
+ startIndex = 1;
32
+ }
33
+ // Need at least 2 values (offset-x and offset-y)
34
+ if (parts.length - startIndex < 2) {
35
+ result.error = 'Box-shadow requires at least offset-x and offset-y values';
36
+ return result;
37
+ }
38
+ // Parse offset-x
39
+ if (!isValidLength(parts[startIndex])) {
40
+ result.error = `Invalid offset-x value: "${parts[startIndex]}"`;
41
+ return result;
42
+ }
43
+ result.offsetX = parts[startIndex];
44
+ // Parse offset-y
45
+ if (!isValidLength(parts[startIndex + 1])) {
46
+ result.error = `Invalid offset-y value: "${parts[startIndex + 1]}"`;
47
+ return result;
48
+ }
49
+ result.offsetY = parts[startIndex + 1];
50
+ // Parse blur-radius (optional)
51
+ if (parts.length > startIndex + 2) {
52
+ if (isValidLength(parts[startIndex + 2])) {
53
+ result.blur = parts[startIndex + 2];
54
+ startIndex += 3;
55
+ }
56
+ else if (isValidColor(parts[startIndex + 2])) {
57
+ result.color = parts[startIndex + 2];
58
+ startIndex += 2;
59
+ }
60
+ else {
61
+ result.error = `Invalid blur-radius or color value: "${parts[startIndex + 2]}"`;
62
+ return result;
63
+ }
64
+ }
65
+ else {
66
+ startIndex += 2;
67
+ }
68
+ // Parse spread-radius (optional)
69
+ if (parts.length > startIndex && isValidLength(parts[startIndex])) {
70
+ result.spread = parts[startIndex];
71
+ startIndex += 1;
72
+ }
73
+ // Parse color (optional)
74
+ if (parts.length > startIndex && isValidColor(parts[startIndex])) {
75
+ result.color = parts[startIndex];
76
+ }
77
+ result.valid = true;
78
+ return result;
79
+ }
80
+ /**
81
+ * Check if a value is a valid CSS length
82
+ */
83
+ function isValidLength(value) {
84
+ const match = value.match(/^(-?\d*\.?\d+)(px|rem|em|%|vh|vw|vmin|vmax|ch|ex)?$/);
85
+ if (!match)
86
+ return false;
87
+ const numericValue = parseFloat(match[1]);
88
+ return !isNaN(numericValue);
89
+ }
90
+ /**
91
+ * Check if a length value is negative
92
+ */
93
+ function isNegativeLength(value) {
94
+ return value.startsWith('-');
95
+ }
96
+ /**
97
+ * Validate a single shadow value
98
+ */
99
+ function validateShadowValue(path, value, severity = 'warning') {
100
+ const parsed = parseBoxShadow(value);
101
+ if (!parsed.valid) {
102
+ return {
103
+ severity: 'error',
104
+ code: SHADOW_FORMAT_CODE,
105
+ message: `Invalid shadow format: "${value}"`,
106
+ path,
107
+ suggestion: parsed.error || 'Use valid box-shadow syntax: offset-x offset-y [blur-radius] [spread-radius] [color] [inset]',
108
+ example: `// Examples:\n// "${path}": "0 1px 2px 0 rgba(0,0,0,0.05)"\n// "${path}": "0 4px 6px -1px rgba(0,0,0,0.1)"`
109
+ };
110
+ }
111
+ const issues = [];
112
+ // Check for negative blur-radius
113
+ if (parsed.blur && isNegativeLength(parsed.blur)) {
114
+ issues.push('Blur-radius should not be negative');
115
+ }
116
+ // Check for color format
117
+ if (parsed.color) {
118
+ const format = getColorFormat(parsed.color);
119
+ if (format === 'hex3' || format === 'hex4') {
120
+ issues.push('Consider using full hex color format (hex6) for better precision');
121
+ }
122
+ }
123
+ if (issues.length > 0) {
124
+ return {
125
+ severity,
126
+ code: SHADOW_FORMAT_CODE,
127
+ message: `Shadow format issues: ${issues.join(', ')}`,
128
+ path,
129
+ suggestion: 'Review the shadow value for best practices.',
130
+ example: `// Example:\n// "${path}": "0 1px 2px 0 rgba(0,0,0,0.05)"`
131
+ };
132
+ }
133
+ return null;
134
+ }
135
+ /**
136
+ * Check for shadow scale consistency
137
+ */
138
+ function checkShadowScaleConsistency(shadows, severity = 'warning') {
139
+ const results = [];
140
+ const keys = Object.keys(shadows);
141
+ if (keys.length < 2)
142
+ return results;
143
+ // Expected shadow scale order
144
+ const expectedOrder = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
145
+ const orderedKeys = keys.filter(k => expectedOrder.includes(k)).sort((a, b) => {
146
+ return expectedOrder.indexOf(a) - expectedOrder.indexOf(b);
147
+ });
148
+ if (orderedKeys.length < 2)
149
+ return results;
150
+ // Parse shadows to compare blur values
151
+ const blurValues = [];
152
+ for (const key of orderedKeys) {
153
+ const parsed = parseBoxShadow(shadows[key]);
154
+ let blur = null;
155
+ if (parsed.blur) {
156
+ const match = parsed.blur.match(/^(-?\d*\.?\d+)(px|rem|em)?$/);
157
+ if (match) {
158
+ blur = parseFloat(match[1]);
159
+ }
160
+ }
161
+ blurValues.push({ key, blur });
162
+ }
163
+ // Check for monotonic progression
164
+ for (let i = 1; i < blurValues.length; i++) {
165
+ const prev = blurValues[i - 1];
166
+ const curr = blurValues[i];
167
+ if (prev.blur !== null && curr.blur !== null) {
168
+ if (curr.blur <= prev.blur) {
169
+ results.push({
170
+ severity,
171
+ code: SHADOW_FORMAT_CODE,
172
+ message: `Non-monotonic shadow scale: "${curr.key}" blur (${curr.blur}px) is not greater than "${prev.key}" (${prev.blur}px)`,
173
+ path: `shadows.${curr.key}`,
174
+ suggestion: 'Shadow blur values should increase with scale size.',
175
+ example: `// Example:\n// "shadows.${prev.key}": "${shadows[prev.key]}"\n// "shadows.${curr.key}": "0 ${curr.blur + 2}px ..."`
176
+ });
177
+ }
178
+ }
179
+ }
180
+ return results;
181
+ }
182
+ /**
183
+ * Validate all shadow formats
184
+ */
185
+ export function validateShadowFormat(tokens, severity = 'warning') {
186
+ const results = [];
187
+ if (!tokens.shadows) {
188
+ return results;
189
+ }
190
+ const shadows = tokens.shadows;
191
+ // Validate each shadow value
192
+ for (const [key, value] of Object.entries(shadows)) {
193
+ const result = validateShadowValue(`shadows.${key}`, value, severity);
194
+ if (result) {
195
+ results.push(result);
196
+ }
197
+ }
198
+ // Check for scale consistency
199
+ results.push(...checkShadowScaleConsistency(shadows, severity));
200
+ return results;
201
+ }
202
+ /**
203
+ * Get a summary of shadow format issues
204
+ */
205
+ export function getShadowFormatSummary(issues) {
206
+ if (issues.length === 0) {
207
+ return 'No shadow format issues found.';
208
+ }
209
+ const summary = [];
210
+ summary.push(`Found ${issues.length} shadow format issue${issues.length > 1 ? 's' : ''}:`);
211
+ for (const issue of issues) {
212
+ summary.push(` • ${issue.path}: ${issue.issue}`);
213
+ summary.push(` ${issue.details}`);
214
+ summary.push(` 💡 ${issue.suggestion}`);
215
+ }
216
+ return summary.join('\n');
217
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Spacing format validation
3
+ * Checks for valid CSS spacing units and consistency
4
+ */
5
+ import type { ValidationResult, TokensForValidation, SpacingFormatIssue } from './types.js';
6
+ /**
7
+ * Validate all spacing formats
8
+ */
9
+ export declare function validateSpacingFormat(tokens: TokensForValidation, severity?: 'info' | 'warning' | 'error'): ValidationResult[];
10
+ /**
11
+ * Get a summary of spacing format issues
12
+ */
13
+ export declare function getSpacingFormatSummary(issues: SpacingFormatIssue[]): string;
14
+ //# sourceMappingURL=spacing-format.d.ts.map