@bookklik/senangstart-css 0.2.8 → 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 (72) hide show
  1. package/dist/senangstart-css.js +2751 -1952
  2. package/dist/senangstart-css.min.js +266 -225
  3. package/dist/senangstart-tw.js +440 -77
  4. package/dist/senangstart-tw.min.js +1 -1
  5. package/docs/SYNTAX-REFERENCE.md +1731 -1590
  6. package/docs/guide/configuration.md +2 -2
  7. package/docs/guide/preflight.md +20 -1
  8. package/docs/guide/states.md +60 -0
  9. package/docs/ms/guide/configuration.md +2 -2
  10. package/docs/ms/guide/preflight.md +19 -0
  11. package/docs/ms/guide/states.md +60 -0
  12. package/docs/ms/reference/breakpoints.md +14 -0
  13. package/docs/ms/reference/colors.md +2 -2
  14. package/docs/ms/reference/space/height.md +10 -10
  15. package/docs/ms/reference/space/width.md +12 -12
  16. package/docs/ms/reference/visual/border-radius.md +50 -10
  17. package/docs/ms/reference/visual/contain.md +57 -0
  18. package/docs/ms/reference/visual/content-visibility.md +53 -0
  19. package/docs/ms/reference/visual/placeholder-color.md +92 -0
  20. package/docs/ms/reference/visual/writing-mode.md +53 -0
  21. package/docs/ms/reference/visual.md +6 -0
  22. package/docs/public/assets/senangstart-css.min.js +266 -225
  23. package/docs/public/llms.txt +63 -2
  24. package/docs/reference/breakpoints.md +14 -0
  25. package/docs/reference/colors.md +2 -2
  26. package/docs/reference/space/height.md +10 -10
  27. package/docs/reference/space/width.md +12 -12
  28. package/docs/reference/visual/border-radius.md +50 -10
  29. package/docs/reference/visual/contain.md +57 -0
  30. package/docs/reference/visual/content-visibility.md +53 -0
  31. package/docs/reference/visual/placeholder-color.md +92 -0
  32. package/docs/reference/visual/writing-mode.md +53 -0
  33. package/docs/reference/visual.md +7 -0
  34. package/docs/syntax-reference.json +2185 -2009
  35. package/package.json +1 -1
  36. package/public/senangstart.css +1 -1
  37. package/scripts/convert-tailwind.js +486 -89
  38. package/scripts/generate-docs.js +403 -403
  39. package/scripts/generate-llms-txt.js +28 -0
  40. package/src/cdn/senangstart-engine.js +37 -1927
  41. package/src/cdn/tw-conversion-engine.js +504 -78
  42. package/src/cli/commands/build.js +10 -0
  43. package/src/compiler/generators/css.js +400 -67
  44. package/src/compiler/generators/preflight.js +26 -13
  45. package/src/compiler/generators/typescript.js +3 -1
  46. package/src/compiler/index.js +27 -3
  47. package/src/compiler/parser.js +24 -7
  48. package/src/config/defaults.js +4 -1
  49. package/src/core/constants.js +5 -3
  50. package/src/definitions/index.js +7 -3
  51. package/src/definitions/layout.js +2 -2
  52. package/src/definitions/space.js +45 -19
  53. package/src/definitions/visual-performance.js +126 -0
  54. package/src/definitions/visual.js +25 -9
  55. package/src/index.js +47 -0
  56. package/src/utils/common.js +17 -5
  57. package/templates/senangstart.config.js +1 -1
  58. package/tests/helpers/test-utils.js +1 -1
  59. package/tests/integration/compiler.test.js +12 -1
  60. package/tests/unit/compiler/generators/css.coverage.test.js +833 -0
  61. package/tests/unit/compiler/generators/css.test.js +1520 -6
  62. package/tests/unit/compiler/generators/preflight.test.js +31 -0
  63. package/tests/unit/compiler/parser.test.js +26 -0
  64. package/tests/unit/config/defaults.test.js +2 -2
  65. package/tests/unit/convert-tailwind.cli.test.js +95 -0
  66. package/tests/unit/convert-tailwind.coverage.test.js +225 -0
  67. package/tests/unit/convert-tailwind.test.js +61 -21
  68. package/tests/unit/core/tokenizer-core.test.js +102 -0
  69. package/tests/unit/definitions/index.test.js +108 -0
  70. package/tests/unit/definitions/layout_definitions.test.js +40 -0
  71. package/tests/unit/utils/common.test.js +26 -0
  72. package/scripts/bundle-jit.js +0 -45
@@ -0,0 +1,102 @@
1
+ /**
2
+ * SenangStart CSS - Tokenizer Core Unit Tests
3
+ */
4
+
5
+ import { describe, it } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import { tokenize, isValidToken } from '../../../src/core/tokenizer-core.js';
8
+
9
+ describe('Tokenizer Core', () => {
10
+
11
+ describe('isValidToken', () => {
12
+
13
+ it('returns false for missing property', () => {
14
+ assert.strictEqual(isValidToken({ value: 'val' }), false);
15
+ });
16
+
17
+ it('returns false for non-string property', () => {
18
+ assert.strictEqual(isValidToken({ property: 123, value: 'val' }), false);
19
+ });
20
+
21
+ it('returns false for too long property', () => {
22
+ assert.strictEqual(isValidToken({ property: 'a'.repeat(101), value: 'val' }), false);
23
+ });
24
+
25
+ it('returns false for non-string value', () => {
26
+ assert.strictEqual(isValidToken({ property: 'prop', value: 123 }), false);
27
+ });
28
+
29
+ it('returns false for too long value', () => {
30
+ assert.strictEqual(isValidToken({ property: 'prop', value: 'a'.repeat(501) }), false);
31
+ });
32
+
33
+ it('returns false for invalid breakpoint', () => {
34
+ assert.strictEqual(isValidToken({ property: 'prop', value: 'val', breakpoint: 'unknown' }), false);
35
+ });
36
+
37
+ it('returns false for invalid state', () => {
38
+ assert.strictEqual(isValidToken({ property: 'prop', value: 'val', state: 'unknown' }), false);
39
+ });
40
+
41
+ it('returns true for valid minimal token', () => {
42
+ assert.strictEqual(isValidToken({ property: 'prop', value: 'val' }), true);
43
+ });
44
+
45
+ it('returns true for valid full token', () => {
46
+ assert.strictEqual(isValidToken({
47
+ property: 'prop',
48
+ value: 'val',
49
+ breakpoint: 'tab',
50
+ state: 'hover'
51
+ }), true);
52
+ });
53
+
54
+ it('returns true for null value', () => {
55
+ assert.strictEqual(isValidToken({ property: 'prop', value: null }), true);
56
+ });
57
+ });
58
+
59
+ describe('tokenize - Validation Edge Cases', () => {
60
+
61
+ it('returns error for non-string raw input', () => {
62
+ const token = tokenize(123, 'space');
63
+ assert.strictEqual(token.error, 'Invalid token format');
64
+ });
65
+
66
+ it('returns error for empty raw input', () => {
67
+ const token = tokenize('', 'space');
68
+ assert.strictEqual(token.error, 'Invalid token format');
69
+ });
70
+
71
+ it('returns error for too long raw input', () => {
72
+ const token = tokenize('a'.repeat(201), 'space');
73
+ assert.strictEqual(token.error, 'Invalid token format');
74
+ });
75
+
76
+ it('returns error for invalid token structure', () => {
77
+ // unknown:bg:white -> unknown is not a breakpoint, so it becomes property 'unknown'
78
+ // but if we force an invalid state or something
79
+ const token = tokenize('unknown:state:prop:val', 'space');
80
+ // wait, tokenize doesn't throw on unknown prefixes unless validated by isValidToken
81
+ // if parts[0] is not a breakpoint, it's not shifted.
82
+
83
+ const invalidToken = tokenize('prop:val:too:many:parts', 'space');
84
+ // This will still be a valid token structure but might not be what's expected.
85
+
86
+ // Force isValidToken to fail by using a reserved word that isn't a breakpoint or state in a prefix position
87
+ // but since tokenize logic only checks BREAKPOINTS.includes(parts[0]), it defaults to property.
88
+
89
+ // Let's test a case where it results in an invalid token structure
90
+ const longProp = 'a'.repeat(101);
91
+ const tokenLong = tokenize(`${longProp}:val`, 'space');
92
+ assert.strictEqual(tokenLong.error, 'Invalid token structure');
93
+ });
94
+
95
+ it('handles single part tokens for space/visual attributes', () => {
96
+ const token = tokenize('single', 'space');
97
+ assert.strictEqual(token.property, 'single');
98
+ assert.strictEqual(token.value, 'single');
99
+ });
100
+ });
101
+
102
+ });
@@ -0,0 +1,108 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import {
4
+ getAllDefinitions,
5
+ getDefinitionsByCategory,
6
+ getDefinition,
7
+ validateDefinitions,
8
+ buildAllMaps
9
+ } from '../../../src/definitions/index.js';
10
+
11
+ describe('Utility Definitions Index', () => {
12
+ describe('getAllDefinitions', () => {
13
+ it('returns a flat array of all definitions', () => {
14
+ const allDefs = getAllDefinitions();
15
+ assert.ok(Array.isArray(allDefs));
16
+ assert.ok(allDefs.length > 0);
17
+ // Verify some known definitions are present
18
+ const names = allDefs.map(d => d.name);
19
+ assert.ok(names.includes('display'));
20
+ assert.ok(names.includes('padding'));
21
+ assert.ok(names.includes('text-color'));
22
+ assert.ok(names.includes('background-color'));
23
+ });
24
+ });
25
+
26
+ describe('getDefinitionsByCategory', () => {
27
+ it('returns definitions grouped by category', () => {
28
+ const byCategory = getDefinitionsByCategory();
29
+ assert.ok(byCategory.layout);
30
+ assert.ok(byCategory.space);
31
+ assert.ok(byCategory.visual);
32
+ assert.ok(Array.isArray(byCategory.layout));
33
+ assert.ok(Array.isArray(byCategory.space));
34
+ assert.ok(Array.isArray(byCategory.visual));
35
+ });
36
+ });
37
+
38
+ describe('getDefinition', () => {
39
+ it('returns a specific definition by name', () => {
40
+ const def = getDefinition('display');
41
+ assert.strictEqual(def.name, 'display');
42
+ assert.strictEqual(def.category, 'layout');
43
+ });
44
+
45
+ it('returns null for unknown definitions', () => {
46
+ const def = getDefinition('nonexistent-definition');
47
+ assert.strictEqual(def, null);
48
+ });
49
+ });
50
+
51
+ describe('validateDefinitions', () => {
52
+ it('validates that all definitions are complete', () => {
53
+ const result = validateDefinitions();
54
+ assert.strictEqual(result.valid, true);
55
+ assert.strictEqual(result.errors.length, 0);
56
+ });
57
+
58
+ it('reports missing fields', () => {
59
+ const brokenDefs = [
60
+ { name: 'broken' } // Missing property, description, descriptionMs, category
61
+ ];
62
+ const result = validateDefinitions(brokenDefs);
63
+ assert.strictEqual(result.valid, false);
64
+ assert.ok(result.errors.some(e => e.includes("Missing 'property'")));
65
+ assert.ok(result.errors.some(e => e.includes("Missing 'description'")));
66
+ assert.ok(result.errors.some(e => e.includes("Missing 'descriptionMs'")));
67
+ assert.ok(result.errors.some(e => e.includes("Missing 'category'")));
68
+ });
69
+
70
+ it('reports unknown name for missing fields if name is missing', () => {
71
+ const brokenDefs = [
72
+ { property: 'test' } // Missing name
73
+ ];
74
+ const result = validateDefinitions(brokenDefs);
75
+ assert.ok(result.errors.some(e => e.includes("in definition 'unknown'")));
76
+ });
77
+
78
+ it('reports missing examples', () => {
79
+ const brokenDefs = [
80
+ {
81
+ name: 'no-examples',
82
+ property: 'test',
83
+ description: 'test',
84
+ descriptionMs: 'test',
85
+ category: 'test',
86
+ examples: []
87
+ }
88
+ ];
89
+ const result = validateDefinitions(brokenDefs);
90
+ assert.strictEqual(result.valid, false);
91
+ assert.ok(result.errors.some(e => e.includes("Missing examples in definition 'no-examples'")));
92
+ });
93
+ });
94
+
95
+ describe('buildAllMaps', () => {
96
+ it('builds all CSS generator maps', () => {
97
+ const maps = buildAllMaps();
98
+ assert.ok(maps.layoutMap);
99
+ assert.ok(maps.spacePropertyMap);
100
+ assert.ok(maps.typographyKeywords);
101
+
102
+ // Check for some expected values
103
+ assert.strictEqual(maps.layoutMap['flex'], 'display: flex;');
104
+ assert.strictEqual(maps.spacePropertyMap['p'], 'padding: var(--s-{value});');
105
+ assert.ok(maps.typographyKeywords['italic'] !== undefined);
106
+ });
107
+ });
108
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { buildLayoutMap } from '../../../src/definitions/layout.js';
4
+
5
+ describe('Layout Definitions', () => {
6
+ describe('buildLayoutMap', () => {
7
+ it('builds a map of layout values to CSS', () => {
8
+ const map = buildLayoutMap();
9
+ assert.ok(typeof map === 'object');
10
+ // Check for some common layout values
11
+ assert.strictEqual(map['flex'], 'display: flex;');
12
+ assert.strictEqual(map['grid'], 'display: grid;');
13
+ assert.strictEqual(map['hidden'], 'display: none;');
14
+ });
15
+
16
+ it('skips range values like "1-12"', () => {
17
+ const map = buildLayoutMap();
18
+ // Values like '1-12' (used in grid-cols etc) should be skipped by buildLayoutMap
19
+ // as they are handled by dynamic generators or special syntax
20
+ assert.strictEqual(map['1-12'], undefined);
21
+ });
22
+
23
+ it('skips mocked range values to hit coverage', () => {
24
+ const mockDefinitions = {
25
+ test: {
26
+ name: 'test',
27
+ syntax: 'layout="["',
28
+ dynamic: false,
29
+ values: [
30
+ { value: '1-5', css: 'test: {n};' },
31
+ { value: 'normal', css: 'test: normal;' }
32
+ ]
33
+ }
34
+ };
35
+ const map = buildLayoutMap(mockDefinitions);
36
+ assert.strictEqual(map['1-5'], undefined);
37
+ assert.strictEqual(map['normal'], 'test: normal;');
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { sanitizeValue } from '../../../src/utils/common.js';
4
+
5
+ describe('Common Utilities', () => {
6
+ describe('sanitizeValue', () => {
7
+ it('returns empty string for non-string input', () => {
8
+ // @ts-ignore
9
+ assert.strictEqual(sanitizeValue(null), '');
10
+ // @ts-ignore
11
+ assert.strictEqual(sanitizeValue(undefined), '');
12
+ // @ts-ignore
13
+ assert.strictEqual(sanitizeValue(123), '');
14
+ // @ts-ignore
15
+ assert.strictEqual(sanitizeValue({}), '');
16
+ });
17
+
18
+ it('sanitizes dangerous characters', () => {
19
+ assert.strictEqual(sanitizeValue('color: red;'), 'color: red_');
20
+ });
21
+
22
+ it('returns the same string if no dangerous characters are present', () => {
23
+ assert.strictEqual(sanitizeValue('red-500'), 'red-500');
24
+ });
25
+ });
26
+ });
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Bundle JIT Runtime with esbuild
5
- * Bundles src/cdn/senangstart-engine.js into a self-contained IIFE for browser use
6
- */
7
-
8
- import * as esbuild from 'esbuild';
9
- import { dirname, join } from 'path';
10
- import { fileURLToPath } from 'url';
11
-
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const root = join(__dirname, '..');
14
-
15
- console.log('📦 Bundling JIT runtime...');
16
-
17
- // Build unminified version
18
- await esbuild.build({
19
- entryPoints: [join(root, 'src', 'cdn', 'senangstart-engine.js')],
20
- bundle: true,
21
- format: 'iife',
22
- outfile: join(root, 'dist', 'senangstart-css.js'),
23
- minify: false,
24
- banner: {
25
- js: '/* SenangStart CSS - JIT Runtime v0.2.0 | MIT License */'
26
- }
27
- });
28
-
29
- console.log('✓ Created dist/senangstart-css.js');
30
-
31
- // Build minified version
32
- await esbuild.build({
33
- entryPoints: [join(root, 'src', 'cdn', 'senangstart-engine.js')],
34
- bundle: true,
35
- format: 'iife',
36
- outfile: join(root, 'dist', 'senangstart-css.min.js'),
37
- minify: true,
38
- banner: {
39
- js: '/* SenangStart CSS v0.2.0 | MIT */'
40
- }
41
- });
42
-
43
- console.log('✓ Created dist/senangstart-css.min.js');
44
- console.log('');
45
- console.log('📦 Bundle complete!');