@bookklik/senangstart-css 0.2.9 → 0.2.10

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 (42) hide show
  1. package/dist/senangstart-css.js +254 -38
  2. package/dist/senangstart-css.min.js +191 -153
  3. package/dist/senangstart-tw.js +271 -5
  4. package/dist/senangstart-tw.min.js +1 -1
  5. package/docs/SYNTAX-REFERENCE.md +1731 -1590
  6. package/docs/guide/preflight.md +20 -1
  7. package/docs/ms/guide/preflight.md +19 -0
  8. package/docs/ms/reference/breakpoints.md +14 -0
  9. package/docs/ms/reference/visual/border-radius.md +50 -10
  10. package/docs/ms/reference/visual/contain.md +57 -0
  11. package/docs/ms/reference/visual/content-visibility.md +53 -0
  12. package/docs/ms/reference/visual/placeholder-color.md +92 -0
  13. package/docs/ms/reference/visual/writing-mode.md +53 -0
  14. package/docs/ms/reference/visual.md +6 -0
  15. package/docs/public/assets/senangstart-css.min.js +191 -153
  16. package/docs/public/llms.txt +35 -2
  17. package/docs/reference/breakpoints.md +14 -0
  18. package/docs/reference/visual/border-radius.md +50 -10
  19. package/docs/reference/visual/contain.md +57 -0
  20. package/docs/reference/visual/content-visibility.md +53 -0
  21. package/docs/reference/visual/placeholder-color.md +92 -0
  22. package/docs/reference/visual/writing-mode.md +53 -0
  23. package/docs/reference/visual.md +7 -0
  24. package/docs/syntax-reference.json +2185 -2009
  25. package/package.json +1 -1
  26. package/scripts/convert-tailwind.js +300 -26
  27. package/scripts/generate-docs.js +403 -403
  28. package/src/cdn/senangstart-engine.js +5 -5
  29. package/src/cdn/tw-conversion-engine.js +302 -5
  30. package/src/cli/commands/build.js +10 -0
  31. package/src/compiler/generators/css.js +102 -15
  32. package/src/compiler/generators/preflight.js +26 -13
  33. package/src/compiler/generators/typescript.js +3 -1
  34. package/src/compiler/index.js +27 -3
  35. package/src/compiler/parser.js +13 -6
  36. package/src/config/defaults.js +3 -0
  37. package/src/definitions/index.js +4 -1
  38. package/src/definitions/visual-performance.js +126 -0
  39. package/src/definitions/visual.js +25 -9
  40. package/src/utils/common.js +17 -5
  41. package/tests/unit/compiler/generators/css.test.js +102 -5
  42. package/tests/unit/convert-tailwind.test.js +15 -4
@@ -19,7 +19,9 @@ export function generateTypeScript(config) {
19
19
 
20
20
  // Generate radius unions
21
21
  const radiusKeys = Object.keys(theme.radius);
22
- const roundedUnions = radiusKeys.map(k => `'rounded:${k}'`).join(' | ');
22
+ const directions = ['t', 'b', 'l', 'r', 'tl', 'tr', 'bl', 'br'];
23
+ const roundedUnions = radiusKeys.map(k => `'rounded:${k}'`).join(' | ') +
24
+ ' | ' + directions.flatMap(d => radiusKeys.map(k => `'rounded-${d}:${k}'`)).join(' | ');
23
25
 
24
26
  // Generate shadow unions
25
27
  const shadowKeys = Object.keys(theme.shadow);
@@ -18,11 +18,24 @@ import { generateTypeScript } from './generators/typescript.js';
18
18
  export function compileSource(content, config) {
19
19
  const parsed = parseSource(content);
20
20
  const tokens = tokenizeAll(parsed);
21
+
22
+ // Check for invalid tokens and log warnings
23
+ const invalidTokens = tokens.filter(token => token.error);
24
+ if (invalidTokens.length > 0) {
25
+ if (typeof console !== 'undefined') {
26
+ console.warn(`\n${invalidTokens.length} error(s) found in source:`);
27
+ for (const token of invalidTokens) {
28
+ console.warn(` • ${token.raw} (${token.attrType}): ${token.error}`);
29
+ }
30
+ }
31
+ }
32
+
21
33
  const css = generateCSS(tokens, config);
22
34
 
23
35
  return {
24
36
  tokens,
25
37
  css,
38
+ errors: invalidTokens.length > 0 ? invalidTokens : null,
26
39
  minifiedCSS: config.output?.minify ? minifyCSS(css) : null
27
40
  };
28
41
  }
@@ -36,14 +49,25 @@ export function compileSource(content, config) {
36
49
  export function compileMultiple(files, config) {
37
50
  const parsed = parseMultipleSources(files);
38
51
  const tokens = tokenizeAll(parsed);
52
+
53
+ // Check for invalid tokens and log warnings
54
+ const invalidTokens = tokens.filter(token => token.error);
55
+ if (invalidTokens.length > 0) {
56
+ if (typeof console !== 'undefined') {
57
+ console.warn(`\n${invalidTokens.length} error(s) found in source:`);
58
+ for (const token of invalidTokens) {
59
+ console.warn(` • ${token.raw} (${token.attrType}): ${token.error}`);
60
+ }
61
+ }
62
+ }
63
+
39
64
  const css = generateCSS(tokens, config);
40
65
 
41
66
  return {
42
67
  tokens,
43
68
  css,
44
- minifiedCSS: config.output?.minify ? minifyCSS(css) : null,
45
- aiContext: generateAIContext(config),
46
- typescript: generateTypeScript(config)
69
+ errors: invalidTokens.length > 0 ? invalidTokens : null,
70
+ minifiedCSS: config.output?.minify ? minifyCSS(css) : null
47
71
  };
48
72
  }
49
73
 
@@ -18,11 +18,11 @@ const ATTRIBUTE_PATTERNS = {
18
18
  */
19
19
  function createAttributePatterns() {
20
20
  return {
21
- layout: /layout\s*=\s*["']([^"']*)["']/g,
22
- space: /space\s*=\s*["']([^"']*)["']/g,
23
- visual: /visual\s*=\s*["']([^"']*)["']/g,
24
- interact: /interact\s*=\s*["']([^"']*)["']/g,
25
- listens: /listens\s*=\s*["']([^"']*)["']/g
21
+ layout: /layout\s*=\s*("[^"]*"|'[^']*')/g,
22
+ space: /space\s*=\s*("[^"]*"|'[^']*')/g,
23
+ visual: /visual\s*=\s*("[^"]*"|'[^']*')/g,
24
+ interact: /interact\s*=\s*("[^"]*"|'[^']*')/g,
25
+ listens: /listens\s*=\s*("[^"]*"|'[^']*')/g
26
26
  };
27
27
  }
28
28
 
@@ -45,7 +45,14 @@ export function parseSource(content) {
45
45
  for (const [attr, pattern] of Object.entries(patterns)) {
46
46
  let match;
47
47
  while ((match = pattern.exec(content)) !== null) {
48
- const value = match[1].trim();
48
+ let value = match[1].trim();
49
+ if (value.length >= 2) {
50
+ const firstChar = value[0];
51
+ const lastChar = value[value.length - 1];
52
+ if ((firstChar === '"' && lastChar === '"') || (firstChar === "'" && lastChar === "'")) {
53
+ value = value.slice(1, -1);
54
+ }
55
+ }
49
56
  if (value.length > 10000) {
50
57
  continue;
51
58
  }
@@ -138,6 +138,7 @@ export const defaultConfig = {
138
138
  'tab': '768px', // Tablet
139
139
  'lap': '1024px', // Laptop
140
140
  'desk': '1280px', // Desktop
141
+ 'print': 'print', // Print media query
141
142
 
142
143
  // Tailwind Compatibility
143
144
  'tw-sm': '640px',
@@ -148,6 +149,8 @@ export const defaultConfig = {
148
149
  },
149
150
 
150
151
  // 7. COLORS: Palette Scales
152
+ // Placeholder color for form inputs
153
+ placeholder: '#9ca3af',
151
154
  colors: {
152
155
  // Base colors
153
156
  'white': '#FFFFFF',
@@ -16,6 +16,7 @@ import transformDefinitions from './visual-transforms.js';
16
16
  import borderDefinitions from './visual-borders.js';
17
17
  import divideDefinitions from './visual-divide.js';
18
18
  import svgDefinitions from './visual-svg.js';
19
+ import performanceDefinitions from './visual-performance.js';
19
20
 
20
21
  // Import split layout definitions
21
22
  import flexDefinitions from './layout-flex.js';
@@ -37,7 +38,8 @@ const allVisualDefinitions = {
37
38
  ...transformDefinitions,
38
39
  ...borderDefinitions,
39
40
  ...divideDefinitions,
40
- ...svgDefinitions
41
+ ...svgDefinitions,
42
+ ...performanceDefinitions
41
43
  };
42
44
 
43
45
  // Re-export all definitions
@@ -54,6 +56,7 @@ export { transformDefinitions } from './visual-transforms.js';
54
56
  export { borderDefinitions } from './visual-borders.js';
55
57
  export { divideDefinitions } from './visual-divide.js';
56
58
  export { svgDefinitions } from './visual-svg.js';
59
+ export { performanceDefinitions } from './visual-performance.js';
57
60
 
58
61
  // Re-export split layout definitions
59
62
  export { flexDefinitions } from './layout-flex.js';
@@ -0,0 +1,126 @@
1
+ /**
2
+ * SenangStart CSS - Performance Utility Definitions
3
+ * Content visibility, contain, and other performance optimizations
4
+ */
5
+
6
+ // ======================
7
+ // CONTENT VISIBILITY
8
+ // ======================
9
+
10
+ export const contentVisibility = {
11
+ name: 'content-visibility',
12
+ property: 'visual',
13
+ syntax: 'visual="content-visibility:[value]"',
14
+ description: 'Optimize rendering by skipping off-screen content',
15
+ descriptionMs: 'Optimumkan rendering dengan melangkau kandungan luar skrin',
16
+ category: 'visual',
17
+ values: [
18
+ { value: 'visible', css: 'content-visibility: visible;', description: 'Render all content', descriptionMs: 'Render semua kandungan' },
19
+ { value: 'auto', css: 'content-visibility: auto;', description: 'Skip when off-screen', descriptionMs: 'Langkau bila luar skrin' },
20
+ { value: 'hidden', css: 'content-visibility: hidden;', description: 'Never render off-screen', descriptionMs: 'Jangan render luar skrin' }
21
+ ],
22
+ examples: [
23
+ { code: '<section visual="content-visibility:auto">Large list</section>', description: 'Auto-optimize large content' },
24
+ { code: '<div visual="content-visibility:hidden">Hidden until needed</div>', description: 'Hide until revealed' }
25
+ ],
26
+ preview: [
27
+ {
28
+ title: 'Content Visibility',
29
+ titleMs: 'Ketampakan Kandungan',
30
+ description: 'Performance optimization for off-screen content',
31
+ descriptionMs: 'Pengoptimuman prestasi untuk kandungan luar skrin',
32
+ html: `<div layout="flex" space="g:medium p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
33
+ <div space="p:small" visual="bg:primary text:white rounded:small">visible</div>
34
+ <div space="p:small" visual="bg:primary text:white rounded:small">auto</div>
35
+ <div space="p:small" visual="bg:primary text:white rounded:small">hidden</div>
36
+ </div>`,
37
+ highlightValue: 'content-visibility:auto'
38
+ }
39
+ ]
40
+ };
41
+
42
+ // ======================
43
+ // CONTAIN
44
+ // ======================
45
+
46
+ export const contain = {
47
+ name: 'contain',
48
+ property: 'visual',
49
+ syntax: 'visual="contain:[value]"',
50
+ description: 'Isolate element rendering for performance',
51
+ descriptionMs: 'Pencil rendering elemen untuk prestasi',
52
+ category: 'visual',
53
+ values: [
54
+ { value: 'none', css: 'contain: none;', description: 'No containment', descriptionMs: 'Tiada pengandungan' },
55
+ { value: 'strict', css: 'contain: strict;', description: 'Full containment', descriptionMs: 'Pengandungan penuh' },
56
+ { value: 'content', css: 'contain: content;', description: 'Content containment', descriptionMs: 'Pengandungan kandungan' },
57
+ { value: 'size', css: 'contain: size;', description: 'Size containment', descriptionMs: 'Pengandungan saiz' },
58
+ { value: 'layout', css: 'contain: layout;', description: 'Layout containment', descriptionMs: 'Pengandungan susun atur' },
59
+ { value: 'style', css: 'contain: style;', description: 'Style containment', descriptionMs: 'Pengandungan gaya' },
60
+ { value: 'paint', css: 'contain: paint;', description: 'Paint containment', descriptionMs: 'Pengandungan lukis' }
61
+ ],
62
+ examples: [
63
+ { code: '<div visual="contain:strict">Isolated rendering</div>', description: 'Full containment' },
64
+ { code: '<div visual="contain:content">Content isolation</div>', description: 'Content only' }
65
+ ],
66
+ preview: [
67
+ {
68
+ title: 'Contain',
69
+ titleMs: 'Mengandung',
70
+ description: 'Isolate element from rest of page for performance',
71
+ descriptionMs: 'Pencil elemen dari halaman lain untuk prestasi',
72
+ html: `<div layout="flex" space="g:medium p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
73
+ <div space="p:small" visual="bg:primary text:white rounded:small">none</div>
74
+ <div space="p:small" visual="bg:primary text:white rounded:small">content</div>
75
+ <div space="p:small" visual="bg:primary text:white rounded:small">strict</div>
76
+ </div>`,
77
+ highlightValue: 'contain:strict'
78
+ }
79
+ ]
80
+ };
81
+
82
+ // ======================
83
+ // WRITING MODE
84
+ // ======================
85
+
86
+ export const writingMode = {
87
+ name: 'writing-mode',
88
+ property: 'visual',
89
+ syntax: 'visual="writing-mode:[value]"',
90
+ description: 'Set writing direction for RTL/vertical text',
91
+ descriptionMs: 'Tetapkan arah penulisan untuk teks RTL/menegak',
92
+ category: 'visual',
93
+ values: [
94
+ { value: 'horizontal-tb', css: 'writing-mode: horizontal-tb;', description: 'Left to right', descriptionMs: 'Kiri ke kanan' },
95
+ { value: 'vertical-rl', css: 'writing-mode: vertical-rl;', description: 'Top to bottom RTL', descriptionMs: 'Atas ke bawah RTL' },
96
+ { value: 'vertical-lr', css: 'writing-mode: vertical-lr;', description: 'Top to bottom LTR', descriptionMs: 'Atas ke bawah LTR' },
97
+ { value: 'sideways-rl', css: 'writing-mode: sideways-rl;', description: 'Sideways RTL', descriptionMs: 'Menyerong RTL' },
98
+ { value: 'sideways-lr', css: 'writing-mode: sideways-lr;', description: 'Sideways LTR', descriptionMs: 'Menyerong LTR' }
99
+ ],
100
+ examples: [
101
+ { code: '<div visual="writing-mode:vertical-rl">Vertical text</div>', description: 'Vertical text RTL' },
102
+ { code: '<div visual="writing-mode:horizontal-tb">Horizontal text</div>', description: 'Horizontal text LTR' }
103
+ ],
104
+ preview: [
105
+ {
106
+ title: 'Writing Mode',
107
+ titleMs: 'Mod Penulisan',
108
+ description: 'Control text direction and orientation',
109
+ descriptionMs: 'Kawal arah dan orientasi teks',
110
+ html: `<div layout="flex" space="g:medium p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
111
+ <div space="p:small" visual="bg:primary text:white rounded:small">horizontal-tb</div>
112
+ <div space="p:small" visual="bg:primary text:white rounded:small">vertical-rl</div>
113
+ </div>`,
114
+ highlightValue: 'writing-mode:vertical-rl'
115
+ }
116
+ ]
117
+ };
118
+
119
+ // Export all performance definitions
120
+ export const performanceDefinitions = {
121
+ contentVisibility,
122
+ contain,
123
+ writingMode
124
+ };
125
+
126
+ export default performanceDefinitions;
@@ -414,11 +414,12 @@ export const lineHeight = {
414
414
  export const borderRadius = {
415
415
  name: 'border-radius',
416
416
  property: 'visual',
417
- syntax: 'visual="rounded:[value]"',
418
- description: 'Set border radius',
419
- descriptionMs: 'Tetapkan jejari sempadan',
417
+ syntax: 'visual="rounded:[value]" | visual="rounded-{t|b|l|r|tl|tr|bl|br}:[value]"',
418
+ description: 'Set border radius for all corners or specific corners',
419
+ descriptionMs: 'Tetapkan jejari sempadan untuk semua bucu atau bucu tertentu',
420
420
  category: 'visual',
421
421
  usesScale: 'radius',
422
+ supportsArbitrary: true,
422
423
  values: [
423
424
  { value: 'none', css: 'border-radius: var(--r-none);', description: 'No rounding', descriptionMs: 'Tiada pembulatan' },
424
425
  { value: 'small', css: 'border-radius: var(--r-small);', description: 'Small radius', descriptionMs: 'Jejari kecil' },
@@ -427,8 +428,10 @@ export const borderRadius = {
427
428
  { value: 'round', css: 'border-radius: var(--r-round);', description: 'Fully round', descriptionMs: 'Sepenuhnya bulat' }
428
429
  ],
429
430
  examples: [
430
- { code: '<div visual="rounded:medium">Rounded corners</div>', description: 'Medium radius' },
431
- { code: '<div visual="rounded:round">Pill shape</div>', description: 'Pill' }
431
+ { code: '<div visual="rounded:medium">Rounded corners</div>', description: 'All corners rounded' },
432
+ { code: '<div visual="rounded:round">Pill shape</div>', description: 'Fully round' },
433
+ { code: '<div visual="rounded-t:medium">Top rounded</div>', description: 'Top corners only' },
434
+ { code: '<div visual="rounded-tl:big rounded-br:big">Opposite corners</div>', description: 'Specific corners' }
432
435
  ],
433
436
  footnotes: [
434
437
  {
@@ -446,12 +449,25 @@ export const borderRadius = {
446
449
  description: 'Round element corners from subtle to pill-shaped',
447
450
  descriptionMs: 'Bulatkan sudut elemen dari halus hingga berbentuk pil',
448
451
  html: `<div layout="flex" space="g:medium p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
449
- <div space="p:small" visual="bg:primary text:white rounded:none">none</div>
450
- <div space="p:small" visual="bg:primary text:white rounded:small">small</div>
451
- <div space="p:small" visual="bg:primary text:white rounded:medium">medium</div>
452
- <div space="p:small" visual="bg:primary text:white rounded:round">round</div>
452
+ <div space="p:small" visual="bg:primary text:white rounded:none">none</div>
453
+ <div space="p:small" visual="bg:primary text:white rounded:small">small</div>
454
+ <div space="p:small" visual="bg:primary text:white rounded:medium">medium</div>
455
+ <div space="p:small" visual="bg:primary text:white rounded:round">round</div>
453
456
  </div>`,
454
457
  highlightValue: 'rounded:medium'
458
+ },
459
+ {
460
+ title: 'Directional Border Radius',
461
+ titleMs: 'Jejari Sempadan Arah',
462
+ description: 'Round specific corners for unique shapes',
463
+ descriptionMs: 'Bulatkan bucu tertentu untuk bentuk unik',
464
+ html: `<div layout="flex" space="g:medium p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
465
+ <div space="p:small" visual="bg:primary text:white rounded-t:medium">top</div>
466
+ <div space="p:small" visual="bg:primary text:white rounded-b:medium">bottom</div>
467
+ <div space="p:small" visual="bg:primary text:white rounded-l:medium">left</div>
468
+ <div space="p:small" visual="bg:primary text:white rounded-r:medium">right</div>
469
+ </div>`,
470
+ highlightValue: 'rounded-t:medium'
455
471
  }
456
472
  ]
457
473
  };
@@ -14,14 +14,26 @@ export function sanitizeValue(value) {
14
14
  return '';
15
15
  }
16
16
 
17
+ let sanitized = value;
18
+
17
19
  // 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
20
+ // Note: We don't filter {} because some tests expect them (e.g. content icons like content: "→")
19
21
  const dangerousChars = /[;]/g;
20
- if (dangerousChars.test(value)) {
21
- return value.replace(dangerousChars, '_');
22
- }
22
+ sanitized = sanitized.replace(dangerousChars, '_');
23
+
24
+ // Filter CSS injection attempts via at-rules
25
+ const atRules = /@import|@charset|@namespace|@supports|@keyframes/gi;
26
+ sanitized = sanitized.replace(atRules, '');
27
+
28
+ // Filter expression() (IE vulnerability)
29
+ const expression = /expression\s*\(/gi;
30
+ sanitized = sanitized.replace(expression, '');
31
+
32
+ // Filter javascript: and data: URLs in url() that could execute scripts
33
+ const dangerousUrls = /(url\s*\(\s*['"]?)(javascript:|data:)([^)]*\))/gi;
34
+ sanitized = sanitized.replace(dangerousUrls, '$1about:blank$3');
23
35
 
24
- return value;
36
+ return sanitized;
25
37
  }
26
38
 
27
39
  export default { sanitizeValue };
@@ -547,6 +547,96 @@ describe('CSS Generator', () => {
547
547
  assert.ok(css.includes('border-radius: var(--r-full)'));
548
548
  });
549
549
 
550
+ it('generates rounded-t', () => {
551
+ const token = { property: 'rounded-t', value: 'medium', attrType: 'visual', raw: 'rounded-t:medium' };
552
+ const config = createTestConfig();
553
+ const css = generateCSS([token], config);
554
+
555
+ assert.ok(css.includes('border-top-left-radius: var(--r-medium)'));
556
+ assert.ok(css.includes('border-top-right-radius: var(--r-medium)'));
557
+ });
558
+
559
+ it('generates rounded-b', () => {
560
+ const token = { property: 'rounded-b', value: 'big', attrType: 'visual', raw: 'rounded-b:big' };
561
+ const config = createTestConfig();
562
+ const css = generateCSS([token], config);
563
+
564
+ assert.ok(css.includes('border-bottom-left-radius: var(--r-big)'));
565
+ assert.ok(css.includes('border-bottom-right-radius: var(--r-big)'));
566
+ });
567
+
568
+ it('generates rounded-l', () => {
569
+ const token = { property: 'rounded-l', value: 'small', attrType: 'visual', raw: 'rounded-l:small' };
570
+ const config = createTestConfig();
571
+ const css = generateCSS([token], config);
572
+
573
+ assert.ok(css.includes('border-top-left-radius: var(--r-small)'));
574
+ assert.ok(css.includes('border-bottom-left-radius: var(--r-small)'));
575
+ });
576
+
577
+ it('generates rounded-r', () => {
578
+ const token = { property: 'rounded-r', value: 'round', attrType: 'visual', raw: 'rounded-r:round' };
579
+ const config = createTestConfig();
580
+ const css = generateCSS([token], config);
581
+
582
+ assert.ok(css.includes('border-top-right-radius: var(--r-round)'));
583
+ assert.ok(css.includes('border-bottom-right-radius: var(--r-round)'));
584
+ });
585
+
586
+ it('generates rounded-tl', () => {
587
+ const token = { property: 'rounded-tl', value: 'medium', attrType: 'visual', raw: 'rounded-tl:medium' };
588
+ const config = createTestConfig();
589
+ const css = generateCSS([token], config);
590
+
591
+ assert.ok(css.includes('border-top-left-radius: var(--r-medium)'));
592
+ assert.ok(!css.includes('border-top-right-radius'));
593
+ });
594
+
595
+ it('generates rounded-tr', () => {
596
+ const token = { property: 'rounded-tr', value: 'big', attrType: 'visual', raw: 'rounded-tr:big' };
597
+ const config = createTestConfig();
598
+ const css = generateCSS([token], config);
599
+
600
+ assert.ok(css.includes('border-top-right-radius: var(--r-big)'));
601
+ assert.ok(!css.includes('border-top-left-radius'));
602
+ });
603
+
604
+ it('generates rounded-bl', () => {
605
+ const token = { property: 'rounded-bl', value: 'small', attrType: 'visual', raw: 'rounded-bl:small' };
606
+ const config = createTestConfig();
607
+ const css = generateCSS([token], config);
608
+
609
+ assert.ok(css.includes('border-bottom-left-radius: var(--r-small)'));
610
+ assert.ok(!css.includes('border-bottom-right-radius'));
611
+ });
612
+
613
+ it('generates rounded-br', () => {
614
+ const token = { property: 'rounded-br', value: 'round', attrType: 'visual', raw: 'rounded-br:round' };
615
+ const config = createTestConfig();
616
+ const css = generateCSS([token], config);
617
+
618
+ assert.ok(css.includes('border-bottom-right-radius: var(--r-round)'));
619
+ assert.ok(!css.includes('border-bottom-left-radius'));
620
+ });
621
+
622
+ it('generates rounded-t with arbitrary value', () => {
623
+ const token = { property: 'rounded-t', value: '12px', attrType: 'visual', raw: 'rounded-t:[12px]', isArbitrary: true };
624
+ const config = createTestConfig();
625
+ const css = generateCSS([token], config);
626
+
627
+ assert.ok(css.includes('border-top-left-radius: 12px'));
628
+ assert.ok(css.includes('border-top-right-radius: 12px'));
629
+ });
630
+
631
+ it('generates rounded-tl with arbitrary value', () => {
632
+ const token = { property: 'rounded-tl', value: '8px', attrType: 'visual', raw: 'rounded-tl:[8px]', isArbitrary: true };
633
+ const config = createTestConfig();
634
+ const css = generateCSS([token], config);
635
+
636
+ assert.ok(css.includes('border-top-left-radius: 8px'));
637
+ assert.ok(!css.includes('border-top-right-radius'));
638
+ });
639
+
550
640
  });
551
641
 
552
642
  describe('Shadow', () => {
@@ -581,6 +671,8 @@ describe('CSS Generator', () => {
581
671
  assert.ok(css.includes('> :not([hidden]) ~ :not([hidden])'));
582
672
  assert.ok(css.includes('border-left-color: var(--c-gray-300)'));
583
673
  assert.ok(css.includes('border-right-color: var(--c-gray-300)'));
674
+ assert.ok(css.includes('border-left-style: solid'));
675
+ assert.ok(css.includes('border-right-style: solid'));
584
676
  });
585
677
 
586
678
  it('generates divide-y', () => {
@@ -591,6 +683,8 @@ describe('CSS Generator', () => {
591
683
  assert.ok(css.includes('> :not([hidden]) ~ :not([hidden])'));
592
684
  assert.ok(css.includes('border-top-color: var(--c-danger)'));
593
685
  assert.ok(css.includes('border-bottom-color: var(--c-danger)'));
686
+ assert.ok(css.includes('border-top-style: solid'));
687
+ assert.ok(css.includes('border-bottom-style: solid'));
594
688
  });
595
689
 
596
690
  it('generates divide width', () => {
@@ -599,7 +693,10 @@ describe('CSS Generator', () => {
599
693
  const css = generateCSS([token], config);
600
694
 
601
695
  assert.ok(css.includes('> :not([hidden]) ~ :not([hidden])'));
602
- assert.ok(css.includes('border-width: var(--s-thin)'));
696
+ assert.ok(css.includes('border-top-width: calc(var(--s-thin) * (1 - var(--ss-divide-y-reverse)))'));
697
+ assert.ok(css.includes('border-bottom-width: calc(var(--s-thin) * var(--ss-divide-y-reverse))'));
698
+ assert.ok(css.includes('border-left-width: calc(var(--s-thin) * (1 - var(--ss-divide-x-reverse)))'));
699
+ assert.ok(css.includes('border-right-width: calc(var(--s-thin) * var(--ss-divide-x-reverse))'));
603
700
  });
604
701
 
605
702
  it('generates divide-x-w', () => {
@@ -608,8 +705,8 @@ describe('CSS Generator', () => {
608
705
  const css = generateCSS([token], config);
609
706
 
610
707
  assert.ok(css.includes('> :not([hidden]) ~ :not([hidden])'));
611
- assert.ok(css.includes('border-left-width: var(--s-regular)'));
612
- assert.ok(css.includes('border-right-width: var(--s-regular)'));
708
+ assert.ok(css.includes('border-right-width: calc(var(--s-regular) * var(--ss-divide-x-reverse))'));
709
+ assert.ok(css.includes('border-left-width: calc(var(--s-regular) * (1 - var(--ss-divide-x-reverse)))'));
613
710
  });
614
711
 
615
712
  it('generates divide-y-w', () => {
@@ -618,8 +715,8 @@ describe('CSS Generator', () => {
618
715
  const css = generateCSS([token], config);
619
716
 
620
717
  assert.ok(css.includes('> :not([hidden]) ~ :not([hidden])'));
621
- assert.ok(css.includes('border-top-width: var(--s-thick)'));
622
- assert.ok(css.includes('border-bottom-width: var(--s-thick)'));
718
+ assert.ok(css.includes('border-bottom-width: calc(var(--s-thick) * var(--ss-divide-y-reverse))'));
719
+ assert.ok(css.includes('border-top-width: calc(var(--s-thick) * (1 - var(--ss-divide-y-reverse)))'));
623
720
  });
624
721
 
625
722
  it('generates divide style', () => {
@@ -42,12 +42,12 @@ describe('convertClass', () => {
42
42
  });
43
43
 
44
44
  it('should convert positional offsets (top, left, right, bottom)', () => {
45
- assert.deepStrictEqual(convertClass('top-0'), { category: 'layout', value: 'top:none' });
46
- assert.deepStrictEqual(convertClass('left-0'), { category: 'layout', value: 'left:none' });
45
+ assert.deepStrictEqual(convertClass('top-0'), { category: 'layout', value: 'top:0' });
46
+ assert.deepStrictEqual(convertClass('left-0'), { category: 'layout', value: 'left:0' });
47
47
  assert.deepStrictEqual(convertClass('bottom-full'), { category: 'layout', value: 'bottom:full' });
48
48
  assert.deepStrictEqual(convertClass('left-1/2'), { category: 'layout', value: 'left:half' });
49
49
  assert.deepStrictEqual(convertClass('top-1/3'), { category: 'layout', value: 'top:third' });
50
- assert.deepStrictEqual(convertClass('inset-0'), { category: 'layout', value: 'inset:none' });
50
+ assert.deepStrictEqual(convertClass('inset-0'), { category: 'layout', value: 'inset:0' });
51
51
  });
52
52
 
53
53
  it('should convert grid classes', () => {
@@ -127,7 +127,7 @@ describe('convertClass', () => {
127
127
  it('should convert text size classes', () => {
128
128
  assert.deepStrictEqual(convertClass('text-sm'), { category: 'visual', value: 'text-size:small' });
129
129
  assert.deepStrictEqual(convertClass('text-xl'), { category: 'visual', value: 'text-size:big' });
130
- assert.deepStrictEqual(convertClass('text-2xl'), { category: 'visual', value: 'text-size:giant' });
130
+ assert.deepStrictEqual(convertClass('text-2xl'), { category: 'visual', value: 'text-size:huge' });
131
131
  });
132
132
 
133
133
  it('should convert border radius classes', () => {
@@ -136,6 +136,17 @@ describe('convertClass', () => {
136
136
  assert.deepStrictEqual(convertClass('rounded-full'), { category: 'visual', value: 'rounded:round' });
137
137
  });
138
138
 
139
+ it('should convert directional border radius classes', () => {
140
+ assert.deepStrictEqual(convertClass('rounded-t-lg'), { category: 'visual', value: 'rounded-t:medium' });
141
+ assert.deepStrictEqual(convertClass('rounded-b-lg'), { category: 'visual', value: 'rounded-b:medium' });
142
+ assert.deepStrictEqual(convertClass('rounded-l-lg'), { category: 'visual', value: 'rounded-l:medium' });
143
+ assert.deepStrictEqual(convertClass('rounded-r-lg'), { category: 'visual', value: 'rounded-r:medium' });
144
+ assert.deepStrictEqual(convertClass('rounded-tl-3xl'), { category: 'visual', value: 'rounded-tl:big' });
145
+ assert.deepStrictEqual(convertClass('rounded-tr-3xl'), { category: 'visual', value: 'rounded-tr:big' });
146
+ assert.deepStrictEqual(convertClass('rounded-bl-full'), { category: 'visual', value: 'rounded-bl:round' });
147
+ assert.deepStrictEqual(convertClass('rounded-br-full'), { category: 'visual', value: 'rounded-br:round' });
148
+ });
149
+
139
150
  it('should convert shadow classes', () => {
140
151
  assert.deepStrictEqual(convertClass('shadow'), { category: 'visual', value: 'shadow:small' });
141
152
  assert.deepStrictEqual(convertClass('shadow-md'), { category: 'visual', value: 'shadow:medium' });