@bookklik/senangstart-css 0.2.8 → 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 +2510 -1927
- package/dist/senangstart-css.min.js +211 -208
- 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 +211 -208
- 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 +1 -1
- package/scripts/convert-tailwind.js +191 -68
- package/scripts/generate-llms-txt.js +28 -0
- package/src/cdn/senangstart-engine.js +37 -1927
- package/src/cdn/tw-conversion-engine.js +203 -74
- package/src/compiler/generators/css.js +300 -54
- package/src/compiler/parser.js +14 -4
- package/src/config/defaults.js +1 -1
- package/src/core/constants.js +5 -3
- package/src/definitions/index.js +3 -2
- package/src/definitions/layout.js +2 -2
- package/src/definitions/space.js +45 -19
- package/src/index.js +47 -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/docs/public/llms.txt
CHANGED
|
@@ -73,6 +73,34 @@ Example: `space="p:small tab:p:medium lap:p:big"`
|
|
|
73
73
|
- `active:`, `disabled:`, `group-hover:`
|
|
74
74
|
- `dark:`: `visual="bg:white dark:bg:slate-900"`
|
|
75
75
|
|
|
76
|
+
## State Capabilities & Interaction
|
|
77
|
+
|
|
78
|
+
React to parent ("Group") or sibling ("Peer") states across all attributes.
|
|
79
|
+
|
|
80
|
+
### 1. Group Capabilities
|
|
81
|
+
Add these to the `layout` attribute of a parent element:
|
|
82
|
+
- `hoverable`: Adds `:hover` support.
|
|
83
|
+
- `focusable`: Adds `:focus-within` support.
|
|
84
|
+
- `pressable`: Adds `:active` support.
|
|
85
|
+
- `expandable`: Reacts to `[aria-expanded="true"]`.
|
|
86
|
+
- `selectable`: Reacts to `[aria-selected="true"]`.
|
|
87
|
+
|
|
88
|
+
**Example:**
|
|
89
|
+
```html
|
|
90
|
+
<div layout="flex hoverable" visual="bg:white">
|
|
91
|
+
<p visual="text:grey hover:text:primary">Hover parent to change me</p>
|
|
92
|
+
</div>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. Peer Interactions
|
|
96
|
+
Use `interact="[id]"` on the trigger and `listens="[id]"` on the receiver.
|
|
97
|
+
|
|
98
|
+
**Example:**
|
|
99
|
+
```html
|
|
100
|
+
<button layout="hoverable" interact="my-menu">Trigger</button>
|
|
101
|
+
<div layout="hidden hover:block" listens="my-menu">Target</div>
|
|
102
|
+
```
|
|
103
|
+
|
|
76
104
|
## Configuration
|
|
77
105
|
Use `senangstart.config.js` to override defaults.
|
|
78
106
|
```js
|
package/docs/reference/colors.md
CHANGED
|
@@ -12,7 +12,7 @@ Complete reference for the color palette.
|
|
|
12
12
|
| `dark` | #3E4A5D | **Brand dark** |
|
|
13
13
|
| `light` | #DBEAFE | **Brand light** |
|
|
14
14
|
| `primary` | #2563EB | **Brand primary** |
|
|
15
|
-
| `secondary` | #
|
|
15
|
+
| `secondary` | #1E40AF | **Brand secondary** |
|
|
16
16
|
| `success` | #10B981 | Positive feedback |
|
|
17
17
|
| `warning` | #F59E0B | Cautions |
|
|
18
18
|
| `danger` | #EF4444 | Errors |
|
|
@@ -104,7 +104,7 @@ Add custom colors in `senangstart.config.js`:
|
|
|
104
104
|
export default {
|
|
105
105
|
theme: {
|
|
106
106
|
colors: {
|
|
107
|
-
'brand': '#
|
|
107
|
+
'brand': '#38BDF8',
|
|
108
108
|
'accent': '#EC4899',
|
|
109
109
|
'muted': '#9CA3AF',
|
|
110
110
|
'surface': '#F9FAFB'
|
|
@@ -17,16 +17,16 @@ space="h:[value]"
|
|
|
17
17
|
|
|
18
18
|
## Scale Values
|
|
19
19
|
|
|
20
|
-
`none`, `thin`, `regular`, `thick`, `tiny`, `tiny-2x`, `small`, `small-2x`, `small-3x`, `small-4x`, `medium`, `medium-2x`, `medium-3x`, `medium-4x`, `large`, `large-2x`, `large-3x`, `large-4x`, `big`, `big-2x`, `big-3x`, `big-4x`, `giant`, `giant-2x`, `giant-3x`, `giant-4x`, `vast`, `vast-2x`, `vast-3x`, `vast-4x`, `vast-5x`, `vast-6x`, `vast-7x`, `vast-8x`, `vast-9x`, `vast-10x`, `min`, `max`, `fit`
|
|
20
|
+
`none`, `thin`, `regular`, `thick`, `tiny`, `tiny-2x`, `small`, `small-2x`, `small-3x`, `small-4x`, `medium`, `medium-2x`, `medium-3x`, `medium-4x`, `large`, `large-2x`, `large-3x`, `large-4x`, `big`, `big-2x`, `big-3x`, `big-4x`, `giant`, `giant-2x`, `giant-3x`, `giant-4x`, `vast`, `vast-2x`, `vast-3x`, `vast-4x`, `vast-5x`, `vast-6x`, `vast-7x`, `vast-8x`, `vast-9x`, `vast-10x`, `min`, `max`, `fit`, `full`, `half`, `third`, `third-2x`, `quarter`, `quarter-2x`, `quarter-3x`, `1/1`, `1/2`, `1/3`, `2/3`, `1/4`, `2/4`, `3/4`
|
|
21
21
|
|
|
22
22
|
## Examples
|
|
23
23
|
|
|
24
24
|
```html
|
|
25
|
+
<div space="h:full">Full height</div>
|
|
26
|
+
<div space="h:half">Half height</div>
|
|
25
27
|
<div space="h:[100vh]">Full viewport height</div>
|
|
26
28
|
<div space="min-h:[400px]">Min height</div>
|
|
27
29
|
<div space="h:max">Content height</div>
|
|
28
|
-
<div space="max-h:max">Max content height</div>
|
|
29
|
-
<div space="min-h:min">Min content height</div>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Preview
|
|
@@ -36,11 +36,11 @@ space="h:[value]"
|
|
|
36
36
|
### Height Control
|
|
37
37
|
|
|
38
38
|
<div layout="flex col" space="g:medium">
|
|
39
|
-
<p space="m:none" visual="text:neutral-600 dark:text:neutral-400 text-sm"><code>space="h:
|
|
39
|
+
<p space="m:none" visual="text:neutral-600 dark:text:neutral-400 text-sm"><code>space="h:full"</code> - Set fixed heights</p>
|
|
40
40
|
<div layout="flex" space="g:small p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium" style="height: 120px;">
|
|
41
|
-
<div space="h:
|
|
42
|
-
<div space="h:
|
|
43
|
-
<div space="h:
|
|
41
|
+
<div space="h:full p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:full</div>
|
|
42
|
+
<div space="h:third-2x p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:third-2x</div>
|
|
43
|
+
<div space="h:half p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:half</div>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
@@ -49,9 +49,9 @@ space="h:[value]"
|
|
|
49
49
|
|
|
50
50
|
```html
|
|
51
51
|
<div layout="flex" space="g:small p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium" style="height: 120px;">
|
|
52
|
-
<div space="h:
|
|
53
|
-
<div space="h:
|
|
54
|
-
<div space="h:
|
|
52
|
+
<div space="h:full p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:full</div>
|
|
53
|
+
<div space="h:third-2x p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:third-2x</div>
|
|
54
|
+
<div space="h:half p:small" visual="bg:primary text:white rounded:small" layout="flex center">h:half</div>
|
|
55
55
|
</div>
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -17,17 +17,17 @@ space="w:[value]"
|
|
|
17
17
|
|
|
18
18
|
## Scale Values
|
|
19
19
|
|
|
20
|
-
`none`, `thin`, `regular`, `thick`, `tiny`, `tiny-2x`, `small`, `small-2x`, `small-3x`, `small-4x`, `medium`, `medium-2x`, `medium-3x`, `medium-4x`, `large`, `large-2x`, `large-3x`, `large-4x`, `big`, `big-2x`, `big-3x`, `big-4x`, `giant`, `giant-2x`, `giant-3x`, `giant-4x`, `vast`, `vast-2x`, `vast-3x`, `vast-4x`, `vast-5x`, `vast-6x`, `vast-7x`, `vast-8x`, `vast-9x`, `vast-10x`, `min`, `max`, `fit`
|
|
20
|
+
`none`, `thin`, `regular`, `thick`, `tiny`, `tiny-2x`, `small`, `small-2x`, `small-3x`, `small-4x`, `medium`, `medium-2x`, `medium-3x`, `medium-4x`, `large`, `large-2x`, `large-3x`, `large-4x`, `big`, `big-2x`, `big-3x`, `big-4x`, `giant`, `giant-2x`, `giant-3x`, `giant-4x`, `vast`, `vast-2x`, `vast-3x`, `vast-4x`, `vast-5x`, `vast-6x`, `vast-7x`, `vast-8x`, `vast-9x`, `vast-10x`, `min`, `max`, `fit`, `full`, `half`, `third`, `third-2x`, `quarter`, `quarter-2x`, `quarter-3x`, `1/1`, `1/2`, `1/3`, `2/3`, `1/4`, `2/4`, `3/4`
|
|
21
21
|
|
|
22
22
|
## Examples
|
|
23
23
|
|
|
24
24
|
```html
|
|
25
|
-
<div space="w:
|
|
25
|
+
<div space="w:full">Full width</div>
|
|
26
|
+
<div space="w:half">Half width</div>
|
|
27
|
+
<div space="w:third">Third width</div>
|
|
28
|
+
<div space="w:quarter-3x">Three quarters</div>
|
|
26
29
|
<div space="max-w:[1200px]">Max width container</div>
|
|
27
|
-
<div space="min-w:[300px]">Min width</div>
|
|
28
30
|
<div space="w:max">Content width</div>
|
|
29
|
-
<div space="max-w:max">Max content width</div>
|
|
30
|
-
<div space="min-w:min">Min content width</div>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Preview
|
|
@@ -37,11 +37,11 @@ space="w:[value]"
|
|
|
37
37
|
### Width Control
|
|
38
38
|
|
|
39
39
|
<div layout="flex col" space="g:medium">
|
|
40
|
-
<p space="m:none" visual="text:neutral-600 dark:text:neutral-400 text-sm"><code>space="w:
|
|
40
|
+
<p space="m:none" visual="text:neutral-600 dark:text:neutral-400 text-sm"><code>space="w:full"</code> - Set fixed or percentage widths</p>
|
|
41
41
|
<div layout="flex col" space="g:small p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
|
|
42
|
-
<div space="w:
|
|
43
|
-
<div space="w:
|
|
44
|
-
<div space="w:
|
|
42
|
+
<div space="w:full p:small" visual="bg:primary text:white rounded:small">w:full</div>
|
|
43
|
+
<div space="w:quarter-3x p:small" visual="bg:primary text:white rounded:small">w:quarter-3x</div>
|
|
44
|
+
<div space="w:half p:small" visual="bg:primary text:white rounded:small">w:half</div>
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
47
|
|
|
@@ -50,9 +50,9 @@ space="w:[value]"
|
|
|
50
50
|
|
|
51
51
|
```html
|
|
52
52
|
<div layout="flex col" space="g:small p:medium" visual="bg:neutral-100 dark:bg:neutral-900 rounded:medium">
|
|
53
|
-
<div space="w:
|
|
54
|
-
<div space="w:
|
|
55
|
-
<div space="w:
|
|
53
|
+
<div space="w:full p:small" visual="bg:primary text:white rounded:small">w:full</div>
|
|
54
|
+
<div space="w:quarter-3x p:small" visual="bg:primary text:white rounded:small">w:quarter-3x</div>
|
|
55
|
+
<div space="w:half p:small" visual="bg:primary text:white rounded:small">w:half</div>
|
|
56
56
|
</div>
|
|
57
57
|
```
|
|
58
58
|
|
package/package.json
CHANGED
package/public/senangstart.css
CHANGED
|
@@ -13,54 +13,96 @@ import { readFileSync, writeFileSync } from 'fs';
|
|
|
13
13
|
import { argv } from 'process';
|
|
14
14
|
import { resolve, join, sep } from 'path';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Basic path validation to ensure we're within the allowed directory
|
|
18
|
+
* @param {string} filePath - Path to validate
|
|
19
|
+
*/
|
|
20
|
+
function isValidFilePath(filePath) {
|
|
21
|
+
try {
|
|
22
|
+
const resolvedPath = resolve(filePath);
|
|
23
|
+
const rootPath = process.cwd();
|
|
24
|
+
// Case-insensitive comparison for Windows
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
return resolvedPath.toLowerCase().startsWith(rootPath.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
return resolvedPath.startsWith(rootPath);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
// ======================
|
|
17
35
|
// SPACING SCALE MAPPING
|
|
18
36
|
// ======================
|
|
19
37
|
// Maps Tailwind spacing numbers to SenangStart semantic scale
|
|
20
38
|
const spacingScale = {
|
|
21
39
|
'0': 'none',
|
|
22
|
-
'px': '
|
|
23
|
-
'0.5': '
|
|
40
|
+
'px': 'thin',
|
|
41
|
+
'0.5': 'regular',
|
|
24
42
|
'1': 'tiny',
|
|
25
|
-
'1.5': 'tiny',
|
|
26
|
-
'2': '
|
|
27
|
-
'2.5': 'small',
|
|
28
|
-
'3': 'small',
|
|
29
|
-
'3.5': 'small',
|
|
30
|
-
'4': '
|
|
31
|
-
'5': 'medium',
|
|
32
|
-
'6': 'medium',
|
|
33
|
-
'7': 'medium',
|
|
34
|
-
'8': '
|
|
35
|
-
'9': '
|
|
36
|
-
'10': '
|
|
37
|
-
'11': '
|
|
38
|
-
'12': '
|
|
39
|
-
'14': '
|
|
40
|
-
'16': '
|
|
41
|
-
'20': '
|
|
42
|
-
'24': '
|
|
43
|
-
'28': '
|
|
44
|
-
'32': '
|
|
45
|
-
'36': '
|
|
43
|
+
'1.5': 'tiny-2x',
|
|
44
|
+
'2': 'small',
|
|
45
|
+
'2.5': 'small-2x',
|
|
46
|
+
'3': 'small-3x',
|
|
47
|
+
'3.5': 'small-4x',
|
|
48
|
+
'4': 'medium',
|
|
49
|
+
'5': 'medium-2x',
|
|
50
|
+
'6': 'medium-3x',
|
|
51
|
+
'7': 'medium-4x',
|
|
52
|
+
'8': 'large',
|
|
53
|
+
'9': 'large-2x',
|
|
54
|
+
'10': 'large-3x',
|
|
55
|
+
'11': 'large-4x',
|
|
56
|
+
'12': 'big',
|
|
57
|
+
'14': 'big-2x',
|
|
58
|
+
'16': 'big-3x',
|
|
59
|
+
'20': 'big-4x',
|
|
60
|
+
'24': 'giant',
|
|
61
|
+
'28': 'giant-2x',
|
|
62
|
+
'32': 'giant-3x',
|
|
63
|
+
'36': 'giant-4x',
|
|
46
64
|
'40': 'vast',
|
|
47
|
-
'44': 'vast',
|
|
48
|
-
'48': 'vast',
|
|
49
|
-
'52': 'vast',
|
|
50
|
-
'56': 'vast',
|
|
51
|
-
'60': 'vast',
|
|
52
|
-
'64': 'vast',
|
|
53
|
-
'72': 'vast',
|
|
54
|
-
'80': 'vast',
|
|
55
|
-
'96': 'vast',
|
|
56
|
-
'full': '
|
|
65
|
+
'44': 'vast-2x',
|
|
66
|
+
'48': 'vast-3x',
|
|
67
|
+
'52': 'vast-4x',
|
|
68
|
+
'56': 'vast-5x',
|
|
69
|
+
'60': 'vast-6x',
|
|
70
|
+
'64': 'vast-7x',
|
|
71
|
+
'72': 'vast-8x',
|
|
72
|
+
'80': 'vast-9x',
|
|
73
|
+
'96': 'vast-10x',
|
|
74
|
+
'full': 'full',
|
|
57
75
|
'screen': '[100vw]',
|
|
58
|
-
'min': '
|
|
59
|
-
'max': '
|
|
60
|
-
'fit': '
|
|
76
|
+
'min': 'min',
|
|
77
|
+
'max': 'max',
|
|
78
|
+
'fit': 'fit',
|
|
61
79
|
'auto': 'auto'
|
|
62
80
|
};
|
|
63
81
|
|
|
82
|
+
// ======================
|
|
83
|
+
// PERCENTAGE ADJECTIVES
|
|
84
|
+
// ======================
|
|
85
|
+
// Maps Tailwind fractional values to SenangStart percentage adjectives
|
|
86
|
+
const percentageAdjectives = {
|
|
87
|
+
'1/2': 'half',
|
|
88
|
+
'1/3': 'third',
|
|
89
|
+
'2/3': 'third-2x',
|
|
90
|
+
'1/4': 'quarter',
|
|
91
|
+
'2/4': 'quarter-2x',
|
|
92
|
+
'3/4': 'quarter-3x',
|
|
93
|
+
// Less common fractions - keep as arbitrary for precision
|
|
94
|
+
'1/5': '[20%]',
|
|
95
|
+
'2/5': '[40%]',
|
|
96
|
+
'3/5': '[60%]',
|
|
97
|
+
'4/5': '[80%]',
|
|
98
|
+
'1/6': '[16.666667%]',
|
|
99
|
+
'5/6': '[83.333333%]',
|
|
100
|
+
'1/12': '[8.333333%]',
|
|
101
|
+
'5/12': '[41.666667%]',
|
|
102
|
+
'7/12': '[58.333333%]',
|
|
103
|
+
'11/12': '[91.666667%]'
|
|
104
|
+
};
|
|
105
|
+
|
|
64
106
|
// ======================
|
|
65
107
|
// BORDER RADIUS MAPPING
|
|
66
108
|
// ======================
|
|
@@ -352,24 +394,32 @@ function getSpacingScale(value, options = {}) {
|
|
|
352
394
|
if (value.startsWith('[') && value.endsWith(']')) {
|
|
353
395
|
return value;
|
|
354
396
|
}
|
|
355
|
-
// Special keywords that
|
|
397
|
+
// Special keywords that use semantic values
|
|
356
398
|
const specialKeywords = ['full', 'screen', 'min', 'max', 'fit', 'auto'];
|
|
357
399
|
if (specialKeywords.includes(value)) {
|
|
358
400
|
const specialMap = {
|
|
359
|
-
'full': '
|
|
401
|
+
'full': 'full',
|
|
360
402
|
'screen': '[100vw]',
|
|
361
|
-
'min': '
|
|
362
|
-
'max': '
|
|
363
|
-
'fit': '
|
|
403
|
+
'min': 'min',
|
|
404
|
+
'max': 'max',
|
|
405
|
+
'fit': 'fit',
|
|
364
406
|
'auto': 'auto'
|
|
365
407
|
};
|
|
366
408
|
return specialMap[value];
|
|
367
409
|
}
|
|
410
|
+
// Check for percentage adjectives
|
|
411
|
+
if (percentageAdjectives[value]) {
|
|
412
|
+
return percentageAdjectives[value];
|
|
413
|
+
}
|
|
368
414
|
// Output tw- prefix for numeric scale
|
|
369
415
|
return `tw-${value}`;
|
|
370
416
|
}
|
|
371
417
|
|
|
372
418
|
// Semantic mode (default)
|
|
419
|
+
// Check for percentage adjectives first
|
|
420
|
+
if (percentageAdjectives[value]) {
|
|
421
|
+
return percentageAdjectives[value];
|
|
422
|
+
}
|
|
373
423
|
if (spacingScale[value]) {
|
|
374
424
|
return spacingScale[value];
|
|
375
425
|
}
|
|
@@ -386,11 +436,12 @@ function getSpacingScale(value, options = {}) {
|
|
|
386
436
|
// ======================
|
|
387
437
|
const borderWidthScale = {
|
|
388
438
|
'0': 'none',
|
|
389
|
-
'
|
|
439
|
+
'px': 'thin',
|
|
440
|
+
'1': 'tiny',
|
|
390
441
|
'2': 'regular',
|
|
391
442
|
'3': 'thick',
|
|
392
|
-
'4': '
|
|
393
|
-
'8': '
|
|
443
|
+
'4': 'medium',
|
|
444
|
+
'8': 'large'
|
|
394
445
|
};
|
|
395
446
|
|
|
396
447
|
function getBorderWidth(value, options = {}) {
|
|
@@ -480,6 +531,19 @@ function convertClass(twClass, options = {}) {
|
|
|
480
531
|
value: prefix + "border:" + borderColorMatch[1],
|
|
481
532
|
};
|
|
482
533
|
|
|
534
|
+
// Directional border colors (border-t-*, border-r-*, border-b-*, border-l-*)
|
|
535
|
+
const borderSideColorMatch = baseClass.match(
|
|
536
|
+
/^border-([trbl])-((?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|white|black|transparent|current|inherit)(?:-\d+)?)$/
|
|
537
|
+
);
|
|
538
|
+
if (borderSideColorMatch) {
|
|
539
|
+
const side = borderSideColorMatch[1]; // t, r, b, or l
|
|
540
|
+
const colorVal = borderSideColorMatch[2];
|
|
541
|
+
return {
|
|
542
|
+
category: 'visual',
|
|
543
|
+
value: prefix + `border-${side}:${colorVal}`,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
483
547
|
// Padding
|
|
484
548
|
const paddingMatch = baseClass.match(/^p([trblxy])?-(.+)$/);
|
|
485
549
|
if (paddingMatch) {
|
|
@@ -528,7 +592,11 @@ function convertClass(twClass, options = {}) {
|
|
|
528
592
|
const prop = widthMatch[1];
|
|
529
593
|
const rawVal = widthMatch[2];
|
|
530
594
|
// Special width values
|
|
531
|
-
const specialWidthVals = { 'max': '
|
|
595
|
+
const specialWidthVals = { 'max': 'max', 'min': 'min', 'fit': 'fit', 'prose': '[65ch]' };
|
|
596
|
+
// Check for percentage adjective first (e.g., 1/2 → half)
|
|
597
|
+
if (percentageAdjectives[rawVal]) {
|
|
598
|
+
return { category: 'space', value: prefix + prop + ":" + percentageAdjectives[rawVal] };
|
|
599
|
+
}
|
|
532
600
|
const val = specialWidthVals[rawVal] || getSpacingScale(rawVal, options);
|
|
533
601
|
return { category: 'space', value: prefix + prop + ":" + val };
|
|
534
602
|
}
|
|
@@ -536,7 +604,11 @@ function convertClass(twClass, options = {}) {
|
|
|
536
604
|
if (heightMatch) {
|
|
537
605
|
const prop = heightMatch[1];
|
|
538
606
|
const rawVal = heightMatch[2];
|
|
539
|
-
const specialHeightVals = { 'screen': '[100vh]', 'svh': '[100svh]', 'lvh': '[100lvh]', 'dvh': '[100dvh]', 'max': '
|
|
607
|
+
const specialHeightVals = { 'screen': '[100vh]', 'svh': '[100svh]', 'lvh': '[100lvh]', 'dvh': '[100dvh]', 'max': 'max', 'min': 'min', 'fit': 'fit' };
|
|
608
|
+
// Check for percentage adjective first (e.g., 1/2 → half)
|
|
609
|
+
if (percentageAdjectives[rawVal]) {
|
|
610
|
+
return { category: 'space', value: prefix + prop + ":" + percentageAdjectives[rawVal] };
|
|
611
|
+
}
|
|
540
612
|
const val = specialHeightVals[rawVal] || getSpacingScale(rawVal, options);
|
|
541
613
|
return { category: 'space', value: prefix + prop + ":" + val };
|
|
542
614
|
}
|
|
@@ -587,7 +659,7 @@ function convertClass(twClass, options = {}) {
|
|
|
587
659
|
const width = borderWidthMatch[2] || "1";
|
|
588
660
|
return {
|
|
589
661
|
category: 'visual',
|
|
590
|
-
value: prefix + "border" + side + ":
|
|
662
|
+
value: prefix + "border" + side + ":" + getBorderWidth(width, options),
|
|
591
663
|
};
|
|
592
664
|
}
|
|
593
665
|
|
|
@@ -621,6 +693,26 @@ function convertClass(twClass, options = {}) {
|
|
|
621
693
|
return { category: 'layout', value: prefix + "row-span:" + rowSpanMatch[1] };
|
|
622
694
|
}
|
|
623
695
|
|
|
696
|
+
// Positional properties (top-0, right-0, bottom-0, left-0, inset-0, etc.)
|
|
697
|
+
// Includes fraction support: left-1/2, top-1/3, etc.
|
|
698
|
+
const positionMatch = baseClass.match(/^(top|right|bottom|left|inset|inset-x|inset-y)-(\d+|px|auto|full|1\/2|1\/3|2\/3|1\/4|2\/4|3\/4|\[.+\])$/);
|
|
699
|
+
if (positionMatch) {
|
|
700
|
+
const prop = positionMatch[1];
|
|
701
|
+
let val = positionMatch[2];
|
|
702
|
+
// Handle 0 specially
|
|
703
|
+
if (val === '0') {
|
|
704
|
+
val = 'none';
|
|
705
|
+
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
706
|
+
// Keep arbitrary values as-is
|
|
707
|
+
} else if (percentageAdjectives[val]) {
|
|
708
|
+
// Map fractions to semantic names (1/2 → half, etc.)
|
|
709
|
+
val = percentageAdjectives[val];
|
|
710
|
+
} else {
|
|
711
|
+
val = getSpacingScale(val, options);
|
|
712
|
+
}
|
|
713
|
+
return { category: "layout", value: prefix + prop + ":" + val };
|
|
714
|
+
}
|
|
715
|
+
|
|
624
716
|
// Opacity
|
|
625
717
|
const opacityMatch = baseClass.match(/^opacity-(\d+)$/);
|
|
626
718
|
if (opacityMatch) {
|
|
@@ -655,19 +747,19 @@ function convertClass(twClass, options = {}) {
|
|
|
655
747
|
// 3D TRANSFORMS
|
|
656
748
|
// ======================
|
|
657
749
|
|
|
658
|
-
// Perspective
|
|
659
|
-
const perspectiveMatch = baseClass.match(/^perspective(?:-(.+))?$/);
|
|
660
|
-
if (perspectiveMatch) {
|
|
661
|
-
const val = perspectiveMatch[1] || 'normal';
|
|
662
|
-
return { category: 'visual', value: prefix + "perspective:" + val };
|
|
663
|
-
}
|
|
664
|
-
|
|
665
750
|
// Perspective origin
|
|
666
751
|
const perspectiveOriginMatch = baseClass.match(/^perspective-origin(?:-(.+))?$/);
|
|
667
752
|
if (perspectiveOriginMatch) {
|
|
668
753
|
const val = perspectiveOriginMatch[1] || 'center';
|
|
669
754
|
return { category: 'visual', value: prefix + "perspective-origin:" + val };
|
|
670
755
|
}
|
|
756
|
+
|
|
757
|
+
// Perspective
|
|
758
|
+
const perspectiveMatch = baseClass.match(/^perspective(?:-(.+))?$/);
|
|
759
|
+
if (perspectiveMatch) {
|
|
760
|
+
const val = perspectiveMatch[1] || 'normal';
|
|
761
|
+
return { category: 'visual', value: prefix + "perspective:" + val };
|
|
762
|
+
}
|
|
671
763
|
|
|
672
764
|
// Rotate X/Y/Z (3D rotation)
|
|
673
765
|
const rotateAxisMatch = baseClass.match(/^rotate-([xyz])(?:-(.+))?$/);
|
|
@@ -707,11 +799,32 @@ function convertClass(twClass, options = {}) {
|
|
|
707
799
|
return { category: 'visual', value: prefix + `skew-${axis}:${val}` };
|
|
708
800
|
}
|
|
709
801
|
|
|
710
|
-
// Translate X/Y/Z
|
|
711
|
-
|
|
802
|
+
// Translate X/Y/Z - includes negative values and fractions
|
|
803
|
+
// Positive: translate-x-1/2 → translate-x:half
|
|
804
|
+
// Negative: -translate-x-1/2 → translate-x:-half
|
|
805
|
+
const translateAxisMatch = baseClass.match(/^(-?)translate-([xyz])(?:-(.+))?$/);
|
|
712
806
|
if (translateAxisMatch) {
|
|
713
|
-
const
|
|
714
|
-
const
|
|
807
|
+
const isNeg = translateAxisMatch[1] === '-';
|
|
808
|
+
const axis = translateAxisMatch[2].toLowerCase();
|
|
809
|
+
let val = translateAxisMatch[3] || '0';
|
|
810
|
+
|
|
811
|
+
// Map fractions and values
|
|
812
|
+
if (val.startsWith('[') && val.endsWith(']')) {
|
|
813
|
+
// Keep arbitrary values as-is, but handle negative
|
|
814
|
+
if (isNeg) {
|
|
815
|
+
const inner = val.slice(1, -1);
|
|
816
|
+
val = `[-${inner}]`;
|
|
817
|
+
}
|
|
818
|
+
} else if (percentageAdjectives[val]) {
|
|
819
|
+
val = percentageAdjectives[val];
|
|
820
|
+
if (isNeg) val = `-${val}`;
|
|
821
|
+
} else if (val === '0') {
|
|
822
|
+
val = '0';
|
|
823
|
+
} else {
|
|
824
|
+
val = getSpacingScale(val, options);
|
|
825
|
+
if (isNeg) val = `-${val}`;
|
|
826
|
+
}
|
|
827
|
+
|
|
715
828
|
return { category: 'visual', value: prefix + `translate-${axis}:${val}` };
|
|
716
829
|
}
|
|
717
830
|
|
|
@@ -737,11 +850,7 @@ function convertClass(twClass, options = {}) {
|
|
|
737
850
|
}
|
|
738
851
|
|
|
739
852
|
// Mask
|
|
740
|
-
|
|
741
|
-
if (maskMatch) {
|
|
742
|
-
const val = maskMatch[1] || 'none';
|
|
743
|
-
return { category: 'visual', value: prefix + "mask:" + val };
|
|
744
|
-
}
|
|
853
|
+
|
|
745
854
|
|
|
746
855
|
// Mask image
|
|
747
856
|
const maskImageMatch = baseClass.match(/^mask-image(?:-(.+))?$/);
|
|
@@ -792,6 +901,13 @@ function convertClass(twClass, options = {}) {
|
|
|
792
901
|
return { category: 'visual', value: prefix + "mask-type:" + val };
|
|
793
902
|
}
|
|
794
903
|
|
|
904
|
+
// Mask (generic) - Check this LAST
|
|
905
|
+
const maskMatch = baseClass.match(/^mask(?:-(.+))?$/);
|
|
906
|
+
if (maskMatch) {
|
|
907
|
+
const val = maskMatch[1] || 'none';
|
|
908
|
+
return { category: 'visual', value: prefix + "mask:" + val };
|
|
909
|
+
}
|
|
910
|
+
|
|
795
911
|
// Divide utilities - check these BEFORE other visual checks to avoid conflicts
|
|
796
912
|
const divideXMatch = baseClass.match(/^divide-x-((?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|white|black|transparent|current|inherit)(?:-\d+)?)$/);
|
|
797
913
|
if (divideXMatch) {
|
|
@@ -871,7 +987,7 @@ function convertClass(twClass, options = {}) {
|
|
|
871
987
|
/**
|
|
872
988
|
* Convert HTML string with Tailwind classes to SenangStart syntax
|
|
873
989
|
*/
|
|
874
|
-
|
|
990
|
+
function convertHTML(html, options = {}) {
|
|
875
991
|
// Match elements with class attribute
|
|
876
992
|
const classRegex = /class\s*=\s*["']([^"']+)["']/gi;
|
|
877
993
|
|
|
@@ -948,9 +1064,9 @@ export function convertClasses(classString, options = {}) {
|
|
|
948
1064
|
|
|
949
1065
|
/**
|
|
950
1066
|
* CLI entry point
|
|
1067
|
+
* @param {string[]} args - Command line arguments (defaults to process.argv.slice(2))
|
|
951
1068
|
*/
|
|
952
|
-
function main() {
|
|
953
|
-
const args = argv.slice(2);
|
|
1069
|
+
function main(args = argv.slice(2)) {
|
|
954
1070
|
|
|
955
1071
|
// Parse --exact flag
|
|
956
1072
|
const exactMode = args.includes('--exact') || args.includes('-e');
|
|
@@ -994,7 +1110,14 @@ Examples:
|
|
|
994
1110
|
}
|
|
995
1111
|
|
|
996
1112
|
// File mode
|
|
997
|
-
const inputFile = args.find(
|
|
1113
|
+
const inputFile = args.find((arg, index) => {
|
|
1114
|
+
if (arg.startsWith('-')) return false;
|
|
1115
|
+
if (index > 0) {
|
|
1116
|
+
const prevArg = args[index - 1];
|
|
1117
|
+
if (prevArg === '-o' || prevArg === '--output' || prevArg === '--string') return false;
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
});
|
|
998
1121
|
|
|
999
1122
|
if (!inputFile) {
|
|
1000
1123
|
console.error('Error: Input file required');
|
|
@@ -1038,4 +1161,4 @@ if (import.meta.url === `file://${process.argv[1].replace(/\\/g, '/')}` ||
|
|
|
1038
1161
|
}
|
|
1039
1162
|
|
|
1040
1163
|
// Export for testing
|
|
1041
|
-
export { convertClass, spacingScale, layoutMappings, visualKeywordMappings };
|
|
1164
|
+
export { convertClass, spacingScale, percentageAdjectives, layoutMappings, visualKeywordMappings, main, convertHTML, isValidFilePath };
|
|
@@ -173,6 +173,34 @@ Example: \`space="p:small tab:p:medium lap:p:big"\`
|
|
|
173
173
|
- \`active:\`, \`disabled:\`, \`group-hover:\`
|
|
174
174
|
- \`dark:\`: \`visual="bg:white dark:bg:slate-900"\`
|
|
175
175
|
|
|
176
|
+
## State Capabilities & Interaction
|
|
177
|
+
|
|
178
|
+
React to parent ("Group") or sibling ("Peer") states across all attributes.
|
|
179
|
+
|
|
180
|
+
### 1. Group Capabilities
|
|
181
|
+
Add these to the \`layout\` attribute of a parent element:
|
|
182
|
+
- \`hoverable\`: Adds \`:hover\` support.
|
|
183
|
+
- \`focusable\`: Adds \`:focus-within\` support.
|
|
184
|
+
- \`pressable\`: Adds \`:active\` support.
|
|
185
|
+
- \`expandable\`: Reacts to \`[aria-expanded="true"]\`.
|
|
186
|
+
- \`selectable\`: Reacts to \`[aria-selected="true"]\`.
|
|
187
|
+
|
|
188
|
+
**Example:**
|
|
189
|
+
\`\`\`html
|
|
190
|
+
<div layout="flex hoverable" visual="bg:white">
|
|
191
|
+
<p visual="text:grey hover:text:primary">Hover parent to change me</p>
|
|
192
|
+
</div>
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
### 2. Peer Interactions
|
|
196
|
+
Use \`interact="[id]"\` on the trigger and \`listens="[id]"\` on the receiver.
|
|
197
|
+
|
|
198
|
+
**Example:**
|
|
199
|
+
\`\`\`html
|
|
200
|
+
<button layout="hoverable" interact="my-menu">Trigger</button>
|
|
201
|
+
<div layout="hidden hover:block" listens="my-menu">Target</div>
|
|
202
|
+
\`\`\`
|
|
203
|
+
|
|
176
204
|
## Configuration
|
|
177
205
|
Use \`senangstart.config.js\` to override defaults.
|
|
178
206
|
\`\`\`js
|