@bookklik/senangstart-css 0.2.7 → 0.2.9
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/senangstart-css.js +9052 -2142
- package/dist/senangstart-css.min.js +1207 -119
- package/dist/senangstart-tw.js +170 -73
- package/dist/senangstart-tw.min.js +1 -1
- package/docs/guide/configuration.md +2 -2
- package/docs/guide/states.md +60 -0
- package/docs/ms/guide/configuration.md +2 -2
- package/docs/ms/guide/states.md +60 -0
- package/docs/ms/reference/colors.md +2 -2
- package/docs/ms/reference/space/height.md +10 -10
- package/docs/ms/reference/space/width.md +12 -12
- package/docs/public/assets/senangstart-css.min.js +1207 -119
- package/docs/public/llms.txt +28 -0
- package/docs/reference/colors.md +2 -2
- package/docs/reference/space/height.md +10 -10
- package/docs/reference/space/width.md +12 -12
- package/package.json +1 -1
- package/public/senangstart.css +1196 -0
- package/scripts/convert-tailwind.js +191 -68
- package/scripts/generate-llms-txt.js +28 -0
- package/src/cdn/senangstart-engine.js +36 -2268
- package/src/cdn/tw-conversion-engine.js +203 -74
- package/src/compiler/generators/css.js +309 -249
- package/src/compiler/parser.js +14 -4
- package/src/compiler/tokenizer.js +0 -1
- package/src/config/defaults.js +1 -1
- package/src/core/constants.js +5 -3
- package/src/core/tokenizer-core.js +3 -58
- package/src/definitions/index.js +3 -2
- package/src/definitions/layout.js +6 -2
- package/src/definitions/space.js +45 -19
- package/src/index.js +47 -0
- package/src/utils/common.js +27 -0
- package/templates/senangstart.config.js +1 -1
- package/tests/helpers/test-utils.js +1 -1
- package/tests/integration/compiler.test.js +12 -1
- package/tests/unit/compiler/generators/css.coverage.test.js +833 -0
- package/tests/unit/compiler/generators/css.test.js +1418 -1
- package/tests/unit/compiler/generators/preflight.test.js +31 -0
- package/tests/unit/compiler/parser.test.js +26 -0
- package/tests/unit/config/defaults.test.js +2 -2
- package/tests/unit/convert-tailwind.cli.test.js +95 -0
- package/tests/unit/convert-tailwind.coverage.test.js +225 -0
- package/tests/unit/convert-tailwind.test.js +49 -20
- package/tests/unit/core/tokenizer-core.test.js +102 -0
- package/tests/unit/definitions/index.test.js +108 -0
- package/tests/unit/definitions/layout_definitions.test.js +40 -0
- package/tests/unit/utils/common.test.js +26 -0
- package/scripts/bundle-jit.js +0 -45
package/src/compiler/parser.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
const ATTRIBUTE_PATTERNS = {
|
|
8
8
|
layout: /layout\s*=\s*["']([^"']*)["']/g,
|
|
9
9
|
space: /space\s*=\s*["']([^"']*)["']/g,
|
|
10
|
-
visual: /visual\s*=\s*["']([^"']*)["']/g
|
|
10
|
+
visual: /visual\s*=\s*["']([^"']*)["']/g,
|
|
11
|
+
interact: /interact\s*=\s*["']([^"']*)["']/g,
|
|
12
|
+
listens: /listens\s*=\s*["']([^"']*)["']/g
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -18,7 +20,9 @@ function createAttributePatterns() {
|
|
|
18
20
|
return {
|
|
19
21
|
layout: /layout\s*=\s*["']([^"']*)["']/g,
|
|
20
22
|
space: /space\s*=\s*["']([^"']*)["']/g,
|
|
21
|
-
visual: /visual\s*=\s*["']([^"']*)["']/g
|
|
23
|
+
visual: /visual\s*=\s*["']([^"']*)["']/g,
|
|
24
|
+
interact: /interact\s*=\s*["']([^"']*)["']/g,
|
|
25
|
+
listens: /listens\s*=\s*["']([^"']*)["']/g
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -31,7 +35,9 @@ export function parseSource(content) {
|
|
|
31
35
|
const results = {
|
|
32
36
|
layout: new Set(),
|
|
33
37
|
space: new Set(),
|
|
34
|
-
visual: new Set()
|
|
38
|
+
visual: new Set(),
|
|
39
|
+
interact: new Set(),
|
|
40
|
+
listens: new Set()
|
|
35
41
|
};
|
|
36
42
|
|
|
37
43
|
const patterns = createAttributePatterns();
|
|
@@ -63,7 +69,9 @@ export function parseMultipleSources(files) {
|
|
|
63
69
|
const combined = {
|
|
64
70
|
layout: new Set(),
|
|
65
71
|
space: new Set(),
|
|
66
|
-
visual: new Set()
|
|
72
|
+
visual: new Set(),
|
|
73
|
+
interact: new Set(),
|
|
74
|
+
listens: new Set()
|
|
67
75
|
};
|
|
68
76
|
|
|
69
77
|
for (const file of files) {
|
|
@@ -72,6 +80,8 @@ export function parseMultipleSources(files) {
|
|
|
72
80
|
parsed.layout.forEach(token => combined.layout.add(token));
|
|
73
81
|
parsed.space.forEach(token => combined.space.add(token));
|
|
74
82
|
parsed.visual.forEach(token => combined.visual.add(token));
|
|
83
|
+
parsed.interact.forEach(token => combined.interact.add(token));
|
|
84
|
+
parsed.listens.forEach(token => combined.listens.add(token));
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
return combined;
|
package/src/config/defaults.js
CHANGED
|
@@ -158,7 +158,7 @@ export const defaultConfig = {
|
|
|
158
158
|
'dark': '#3E4A5D', // Brand dark
|
|
159
159
|
'light': '#DBEAFE', // Brand light/secondary
|
|
160
160
|
'primary': '#2563EB', // Brand primary
|
|
161
|
-
'secondary': '#
|
|
161
|
+
'secondary': '#1E40AF', // Brand secondary
|
|
162
162
|
'success': '#10B981',
|
|
163
163
|
'warning': '#F59E0B',
|
|
164
164
|
'danger': '#EF4444',
|
package/src/core/constants.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export const BREAKPOINTS = ['mob', 'tab', 'lap', 'desk', 'tw-sm', 'tw-md', 'tw-lg', 'tw-xl', 'tw-2xl'];
|
|
8
8
|
|
|
9
9
|
// State prefixes
|
|
10
|
-
export const STATES = ['hover', 'focus', 'focus-visible', 'active', 'disabled', 'dark'];
|
|
10
|
+
export const STATES = ['hover', 'focus', 'focus-visible', 'active', 'disabled', 'dark', 'expanded', 'selected'];
|
|
11
11
|
|
|
12
12
|
// Layout keywords (no colon syntax)
|
|
13
13
|
export const LAYOUT_KEYWORDS = [
|
|
@@ -15,7 +15,9 @@ export const LAYOUT_KEYWORDS = [
|
|
|
15
15
|
'row', 'col', 'row-reverse', 'col-reverse',
|
|
16
16
|
'center', 'start', 'end', 'between', 'around', 'evenly',
|
|
17
17
|
'wrap', 'nowrap',
|
|
18
|
-
'absolute', 'relative', 'fixed', 'sticky'
|
|
18
|
+
'absolute', 'relative', 'fixed', 'sticky',
|
|
19
|
+
// State Capabilities
|
|
20
|
+
'hoverable', 'focusable', 'pressable', 'expandable', 'selectable', 'disabled'
|
|
19
21
|
];
|
|
20
22
|
|
|
21
23
|
// Layout CSS mappings
|
|
@@ -290,7 +292,7 @@ export const DEFAULT_THEME = {
|
|
|
290
292
|
'dark': '#3E4A5D',
|
|
291
293
|
'light': '#DBEAFE',
|
|
292
294
|
'primary': '#2563EB',
|
|
293
|
-
'secondary': '#
|
|
295
|
+
'secondary': '#1E40AF',
|
|
294
296
|
'success': '#10B981',
|
|
295
297
|
'warning': '#F59E0B',
|
|
296
298
|
'danger': '#EF4444',
|
|
@@ -4,20 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { BREAKPOINTS, STATES, LAYOUT_KEYWORDS } from './constants.js';
|
|
7
|
+
import { sanitizeValue } from '../utils/common.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Sanitize token value to prevent CSS injection
|
|
10
11
|
* @param {string} value - Value to sanitize
|
|
11
12
|
* @returns {string} - Sanitized value
|
|
12
13
|
*/
|
|
13
|
-
export
|
|
14
|
-
if (typeof value !== 'string') {
|
|
15
|
-
return '';
|
|
16
|
-
}
|
|
17
|
-
// Only remove semicolons which can terminate CSS rules
|
|
18
|
-
// Allow braces for legitimate use cases like content:["{icon}"]
|
|
19
|
-
return value.replace(/;/g, '_');
|
|
20
|
-
}
|
|
14
|
+
export { sanitizeValue };
|
|
21
15
|
|
|
22
16
|
/**
|
|
23
17
|
* Validate token structure
|
|
@@ -46,56 +40,7 @@ export function isValidToken(token) {
|
|
|
46
40
|
return true;
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
/**
|
|
50
|
-
* Lightweight token parser (no validation, used for internal processing)
|
|
51
|
-
* @param {string} raw - Raw token string
|
|
52
|
-
* @returns {Object} - Parsed token object
|
|
53
|
-
*/
|
|
54
|
-
export function parseToken(raw) {
|
|
55
|
-
const token = {
|
|
56
|
-
raw,
|
|
57
|
-
breakpoint: null,
|
|
58
|
-
state: null,
|
|
59
|
-
property: null,
|
|
60
|
-
value: null,
|
|
61
|
-
isArbitrary: false
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const parts = raw.split(':');
|
|
65
|
-
let idx = 0;
|
|
66
43
|
|
|
67
|
-
// Check for breakpoint
|
|
68
|
-
if (BREAKPOINTS.includes(parts[0])) {
|
|
69
|
-
token.breakpoint = parts[0];
|
|
70
|
-
idx++;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Check for state
|
|
74
|
-
if (STATES.includes(parts[idx])) {
|
|
75
|
-
token.state = parts[idx];
|
|
76
|
-
idx++;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Property
|
|
80
|
-
if (idx < parts.length) {
|
|
81
|
-
token.property = parts[idx];
|
|
82
|
-
idx++;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Value
|
|
86
|
-
if (idx < parts.length) {
|
|
87
|
-
let value = parts.slice(idx).join(':');
|
|
88
|
-
const arbitraryMatch = value.match(/^\[(.+)\]$/);
|
|
89
|
-
if (arbitraryMatch) {
|
|
90
|
-
token.value = arbitraryMatch[1].replace(/_/g, ' ');
|
|
91
|
-
token.isArbitrary = true;
|
|
92
|
-
} else {
|
|
93
|
-
token.value = value;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return token;
|
|
98
|
-
}
|
|
99
44
|
|
|
100
45
|
/**
|
|
101
46
|
* Tokenize a single attribute value string
|
|
@@ -230,4 +175,4 @@ export function tokenizeAll(parsed) {
|
|
|
230
175
|
return tokens;
|
|
231
176
|
}
|
|
232
177
|
|
|
233
|
-
export default { tokenize, tokenizeAll,
|
|
178
|
+
export default { tokenize, tokenizeAll, sanitizeValue, isValidToken };
|
package/src/definitions/index.js
CHANGED
|
@@ -108,12 +108,13 @@ export function getDefinition(name) {
|
|
|
108
108
|
/**
|
|
109
109
|
* Validate that all definitions have required fields
|
|
110
110
|
* Used by tests to ensure definitions are complete
|
|
111
|
+
* @param {Array} definitions - Optional array of definitions to validate (defaults to all)
|
|
111
112
|
*/
|
|
112
|
-
export function validateDefinitions() {
|
|
113
|
+
export function validateDefinitions(definitions = getAllDefinitions()) {
|
|
113
114
|
const requiredFields = ['name', 'property', 'description', 'descriptionMs', 'category'];
|
|
114
115
|
const errors = [];
|
|
115
116
|
|
|
116
|
-
for (const def of
|
|
117
|
+
for (const def of definitions) {
|
|
117
118
|
for (const field of requiredFields) {
|
|
118
119
|
if (!def[field]) {
|
|
119
120
|
errors.push(`Missing '${field}' in definition '${def.name || 'unknown'}'`);
|
|
@@ -174,13 +174,17 @@ export const layoutDefinitions = {
|
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
// Build flat value map for CSS generator
|
|
177
|
-
export function buildLayoutMap() {
|
|
177
|
+
export function buildLayoutMap(definitions = layoutDefinitions) {
|
|
178
178
|
const map = {};
|
|
179
179
|
|
|
180
180
|
// Add all simple keyword values from definitions
|
|
181
|
-
for (const def of Object.values(
|
|
181
|
+
for (const def of Object.values(definitions)) {
|
|
182
182
|
if (def.dynamic) continue; // Skip dynamic properties that need special handling
|
|
183
183
|
|
|
184
|
+
// Only include definitions that act as global keywords (no prefix in syntax)
|
|
185
|
+
// format: layout="[value]"
|
|
186
|
+
if (!def.syntax || !def.syntax.includes('layout="[')) continue;
|
|
187
|
+
|
|
184
188
|
for (const v of def.values) {
|
|
185
189
|
// Skip range values like '1-12' that need special handling
|
|
186
190
|
if (v.value.match(/^\d+-\d+$/)) continue;
|
package/src/definitions/space.js
CHANGED
|
@@ -244,16 +244,29 @@ export const width = {
|
|
|
244
244
|
'big', 'big-2x', 'big-3x', 'big-4x',
|
|
245
245
|
'giant', 'giant-2x', 'giant-3x', 'giant-4x',
|
|
246
246
|
'vast', 'vast-2x', 'vast-3x', 'vast-4x', 'vast-5x', 'vast-6x', 'vast-7x', 'vast-8x', 'vast-9x', 'vast-10x',
|
|
247
|
-
'min', 'max', 'fit'
|
|
247
|
+
'min', 'max', 'fit',
|
|
248
|
+
// Percentage adjectives
|
|
249
|
+
'full', 'half', 'third', 'third-2x', 'quarter', 'quarter-2x', 'quarter-3x',
|
|
250
|
+
// Fractional values (backwards compatibility)
|
|
251
|
+
'1/1', '1/2', '1/3', '2/3', '1/4', '2/4', '3/4'
|
|
252
|
+
],
|
|
253
|
+
percentageAdjectives: [
|
|
254
|
+
{ name: 'full', value: '100%', description: 'Full width (100%)', descriptionMs: 'Lebar penuh (100%)' },
|
|
255
|
+
{ name: 'half', value: '50%', description: 'Half width (50%)', descriptionMs: 'Separuh lebar (50%)' },
|
|
256
|
+
{ name: 'third', value: '33.333333%', description: 'One third width (33%)', descriptionMs: 'Satu pertiga lebar (33%)' },
|
|
257
|
+
{ name: 'third-2x', value: '66.666667%', description: 'Two thirds width (66%)', descriptionMs: 'Dua pertiga lebar (66%)' },
|
|
258
|
+
{ name: 'quarter', value: '25%', description: 'One quarter width (25%)', descriptionMs: 'Satu perempat lebar (25%)' },
|
|
259
|
+
{ name: 'quarter-2x', value: '50%', description: 'Two quarters width (50%)', descriptionMs: 'Dua perempat lebar (50%)' },
|
|
260
|
+
{ name: 'quarter-3x', value: '75%', description: 'Three quarters width (75%)', descriptionMs: 'Tiga perempat lebar (75%)' }
|
|
248
261
|
],
|
|
249
262
|
supportsArbitrary: true,
|
|
250
263
|
examples: [
|
|
251
|
-
{ code: '<div space="w:
|
|
264
|
+
{ code: '<div space="w:full">Full width</div>', description: 'Full width' },
|
|
265
|
+
{ code: '<div space="w:half">Half width</div>', description: 'Half width (50%)' },
|
|
266
|
+
{ code: '<div space="w:third">Third width</div>', description: 'One third width (33%)' },
|
|
267
|
+
{ code: '<div space="w:quarter-3x">Three quarters</div>', description: 'Three quarters width (75%)' },
|
|
252
268
|
{ code: '<div space="max-w:[1200px]">Max width container</div>', description: 'Max width' },
|
|
253
|
-
{ code: '<div space="
|
|
254
|
-
{ code: '<div space="w:max">Content width</div>', description: 'Width based on content (max-content)' },
|
|
255
|
-
{ code: '<div space="max-w:max">Max content width</div>', description: 'Maximum content width' },
|
|
256
|
-
{ code: '<div space="min-w:min">Min content width</div>', description: 'Minimum content width' }
|
|
269
|
+
{ code: '<div space="w:max">Content width</div>', description: 'Width based on content (max-content)' }
|
|
257
270
|
],
|
|
258
271
|
preview: [
|
|
259
272
|
{
|
|
@@ -262,11 +275,11 @@ export const width = {
|
|
|
262
275
|
description: 'Set fixed or percentage widths',
|
|
263
276
|
descriptionMs: 'Tetapkan lebar tetap atau peratusan',
|
|
264
277
|
html: `<div layout="flex col" space="g:small p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
|
|
265
|
-
<div space="w:
|
|
266
|
-
<div space="w:
|
|
267
|
-
<div space="w:
|
|
278
|
+
<div space="w:full p:small" visual="bg:primary text:white rounded:small">w:full</div>
|
|
279
|
+
<div space="w:quarter-3x p:small" visual="bg:primary text:white rounded:small">w:quarter-3x</div>
|
|
280
|
+
<div space="w:half p:small" visual="bg:primary text:white rounded:small">w:half</div>
|
|
268
281
|
</div>`,
|
|
269
|
-
highlightValue: 'w:
|
|
282
|
+
highlightValue: 'w:full'
|
|
270
283
|
},
|
|
271
284
|
{
|
|
272
285
|
title: 'Content-Based Sizing',
|
|
@@ -329,15 +342,28 @@ export const height = {
|
|
|
329
342
|
'big', 'big-2x', 'big-3x', 'big-4x',
|
|
330
343
|
'giant', 'giant-2x', 'giant-3x', 'giant-4x',
|
|
331
344
|
'vast', 'vast-2x', 'vast-3x', 'vast-4x', 'vast-5x', 'vast-6x', 'vast-7x', 'vast-8x', 'vast-9x', 'vast-10x',
|
|
332
|
-
'min', 'max', 'fit'
|
|
345
|
+
'min', 'max', 'fit',
|
|
346
|
+
// Percentage adjectives
|
|
347
|
+
'full', 'half', 'third', 'third-2x', 'quarter', 'quarter-2x', 'quarter-3x',
|
|
348
|
+
// Fractional values (backwards compatibility)
|
|
349
|
+
'1/1', '1/2', '1/3', '2/3', '1/4', '2/4', '3/4'
|
|
350
|
+
],
|
|
351
|
+
percentageAdjectives: [
|
|
352
|
+
{ name: 'full', value: '100%', description: 'Full height (100%)', descriptionMs: 'Tinggi penuh (100%)' },
|
|
353
|
+
{ name: 'half', value: '50%', description: 'Half height (50%)', descriptionMs: 'Separuh tinggi (50%)' },
|
|
354
|
+
{ name: 'third', value: '33.333333%', description: 'One third height (33%)', descriptionMs: 'Satu pertiga tinggi (33%)' },
|
|
355
|
+
{ name: 'third-2x', value: '66.666667%', description: 'Two thirds height (66%)', descriptionMs: 'Dua pertiga tinggi (66%)' },
|
|
356
|
+
{ name: 'quarter', value: '25%', description: 'One quarter height (25%)', descriptionMs: 'Satu perempat tinggi (25%)' },
|
|
357
|
+
{ name: 'quarter-2x', value: '50%', description: 'Two quarters height (50%)', descriptionMs: 'Dua perempat tinggi (50%)' },
|
|
358
|
+
{ name: 'quarter-3x', value: '75%', description: 'Three quarters height (75%)', descriptionMs: 'Tiga perempat tinggi (75%)' }
|
|
333
359
|
],
|
|
334
360
|
supportsArbitrary: true,
|
|
335
361
|
examples: [
|
|
336
|
-
{ code: '<div space="h:
|
|
362
|
+
{ code: '<div space="h:full">Full height</div>', description: 'Full height' },
|
|
363
|
+
{ code: '<div space="h:half">Half height</div>', description: 'Half height (50%)' },
|
|
364
|
+
{ code: '<div space="h:[100vh]">Full viewport height</div>', description: 'Full viewport height' },
|
|
337
365
|
{ code: '<div space="min-h:[400px]">Min height</div>', description: 'Minimum height' },
|
|
338
|
-
{ code: '<div space="h:max">Content height</div>', description: 'Height based on content (max-content)' }
|
|
339
|
-
{ code: '<div space="max-h:max">Max content height</div>', description: 'Maximum content height' },
|
|
340
|
-
{ code: '<div space="min-h:min">Min content height</div>', description: 'Minimum content height' }
|
|
366
|
+
{ code: '<div space="h:max">Content height</div>', description: 'Height based on content (max-content)' }
|
|
341
367
|
],
|
|
342
368
|
preview: [
|
|
343
369
|
{
|
|
@@ -346,11 +372,11 @@ export const height = {
|
|
|
346
372
|
description: 'Set fixed heights',
|
|
347
373
|
descriptionMs: 'Tetapkan tinggi tetap',
|
|
348
374
|
html: `<div layout="flex" space="g:small p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium" style="height: 120px;">
|
|
349
|
-
<div space="h:
|
|
350
|
-
<div space="h:
|
|
351
|
-
<div space="h:
|
|
375
|
+
<div space="h:full p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:full</div>
|
|
376
|
+
<div space="h:third-2x p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:third-2x</div>
|
|
377
|
+
<div space="h:half p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:half</div>
|
|
352
378
|
</div>`,
|
|
353
|
-
highlightValue: 'h:
|
|
379
|
+
highlightValue: 'h:full'
|
|
354
380
|
},
|
|
355
381
|
{
|
|
356
382
|
title: 'Content-Based Height',
|
package/src/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SenangStart CSS - Core API
|
|
3
|
+
* Exporting the compiler engine for programmatic usage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Core Tokenizer
|
|
7
|
+
import { tokenize, tokenizeAll } from './core/tokenizer-core.js';
|
|
8
|
+
|
|
9
|
+
// Parsers
|
|
10
|
+
import { parseSource, parseMultipleSources } from './compiler/parser.js';
|
|
11
|
+
|
|
12
|
+
// Generators
|
|
13
|
+
import { generateCSS, generateCSSVariables } from './compiler/generators/css.js';
|
|
14
|
+
import { generatePreflight } from './compiler/generators/preflight.js';
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
import { defaultConfig, mergeConfig } from './config/defaults.js';
|
|
18
|
+
|
|
19
|
+
// Constants
|
|
20
|
+
import * as constants from './core/constants.js';
|
|
21
|
+
|
|
22
|
+
// High-level Compiler
|
|
23
|
+
import { compileSource, compileMultiple } from './compiler/index.js';
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
// Core
|
|
27
|
+
tokenize,
|
|
28
|
+
tokenizeAll,
|
|
29
|
+
parseSource,
|
|
30
|
+
parseMultipleSources,
|
|
31
|
+
|
|
32
|
+
// Generators
|
|
33
|
+
generateCSS,
|
|
34
|
+
generateCSSVariables,
|
|
35
|
+
generatePreflight,
|
|
36
|
+
|
|
37
|
+
// High-level
|
|
38
|
+
compileSource,
|
|
39
|
+
compileMultiple,
|
|
40
|
+
|
|
41
|
+
// Configuration
|
|
42
|
+
defaultConfig,
|
|
43
|
+
mergeConfig,
|
|
44
|
+
|
|
45
|
+
// Constants
|
|
46
|
+
constants
|
|
47
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SenangStart CSS - Common Utilities
|
|
3
|
+
* Shared helper functions for compiler and runtime
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sanitize token value to prevent CSS injection
|
|
8
|
+
* Removes potentially dangerous characters/sequences
|
|
9
|
+
* @param {string} value - Value to sanitize
|
|
10
|
+
* @returns {string} - Sanitized value
|
|
11
|
+
*/
|
|
12
|
+
export function sanitizeValue(value) {
|
|
13
|
+
if (typeof value !== 'string') {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Remove potentially dangerous characters that could break CSS syntax
|
|
18
|
+
// Note: We used to filter {} but some tests expect them (e.g. content icons), so we only filter ; for now
|
|
19
|
+
const dangerousChars = /[;]/g;
|
|
20
|
+
if (dangerousChars.test(value)) {
|
|
21
|
+
return value.replace(dangerousChars, '_');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default { sanitizeValue };
|
|
@@ -170,10 +170,21 @@ describe('Compiler Integration', () => {
|
|
|
170
170
|
|
|
171
171
|
const result = compileMultiple(files, config);
|
|
172
172
|
|
|
173
|
-
assert.ok(result.css);
|
|
174
173
|
assert.strictEqual(result.tokens.length, 0);
|
|
175
174
|
});
|
|
176
175
|
|
|
176
|
+
it('minifies output when configured', () => {
|
|
177
|
+
const files = [
|
|
178
|
+
{ path: 'test.html', content: '<div layout="flex">Test</div>' }
|
|
179
|
+
];
|
|
180
|
+
const config = createTestConfig({ output: { minify: true } });
|
|
181
|
+
|
|
182
|
+
const result = compileMultiple(files, config);
|
|
183
|
+
|
|
184
|
+
assert.ok(result.minifiedCSS);
|
|
185
|
+
assert.ok(result.minifiedCSS.length <= result.css.length);
|
|
186
|
+
});
|
|
187
|
+
|
|
177
188
|
});
|
|
178
189
|
|
|
179
190
|
describe('Real-world Scenarios', () => {
|