@debeshghorui/chaitailwind 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.js +241 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Debesh Ghorui
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# ChaiTailwind
|
|
2
|
+
|
|
3
|
+
ChaiTailwind is a lightweight utility-first CSS engine that scans chai-* class names and applies inline styles dynamically.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @debeshghorui/chaitailwind
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Local Development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install
|
|
15
|
+
npm test
|
|
16
|
+
npm run build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { initChai } from "@debeshghorui/chaitailwind";
|
|
23
|
+
|
|
24
|
+
initChai();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For local demo usage in this repository:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script type="module">
|
|
31
|
+
import { initChai } from "../src/index.js";
|
|
32
|
+
initChai();
|
|
33
|
+
</script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Supported Utilities (v0.1)
|
|
37
|
+
|
|
38
|
+
- Spacing: chai-p-*, chai-m-*
|
|
39
|
+
- Colors: chai-bg-*, chai-text-*
|
|
40
|
+
- Typography: chai-fs-*, chai-center
|
|
41
|
+
- Borders: chai-border-*, chai-rounded-*
|
|
42
|
+
- Layout: chai-flex, chai-justify-center, chai-items-center
|
|
43
|
+
|
|
44
|
+
## Value Rules
|
|
45
|
+
|
|
46
|
+
- Numeric values auto-convert to px: chai-p-10 -> padding: 10px
|
|
47
|
+
- Explicit units are supported: px, rem, em, %, vh, vw, vmin, vmax, pt
|
|
48
|
+
- Percent shortcut is supported with pct suffix: chai-m-50pct -> margin: 50%
|
|
49
|
+
- Colors support common tokens (red, blue, gray-100, etc.) and hex formats (#fff, #ffffff, hex-ffffff)
|
|
50
|
+
|
|
51
|
+
## Scripts
|
|
52
|
+
|
|
53
|
+
- npm test: runs unit and integration tests with Vitest + jsdom
|
|
54
|
+
- npm run build: bundles src/index.js to dist/index.js using esbuild
|
|
55
|
+
- npm run prepublishOnly: runs test + build before publish
|
|
56
|
+
|
|
57
|
+
## Publish Checklist
|
|
58
|
+
|
|
59
|
+
1. Create and verify your npm account at npmjs.com.
|
|
60
|
+
2. Run npm login.
|
|
61
|
+
3. Run npm test && npm run build.
|
|
62
|
+
4. Publish with npm publish --access public.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/utils.js
|
|
2
|
+
var COLOR_MAP = {
|
|
3
|
+
black: "#000000",
|
|
4
|
+
white: "#ffffff",
|
|
5
|
+
gray: "#6b7280",
|
|
6
|
+
"gray-100": "#f3f4f6",
|
|
7
|
+
"gray-500": "#6b7280",
|
|
8
|
+
"gray-900": "#111827",
|
|
9
|
+
red: "#ef4444",
|
|
10
|
+
"red-500": "#ef4444",
|
|
11
|
+
blue: "#3b82f6",
|
|
12
|
+
"blue-500": "#3b82f6",
|
|
13
|
+
green: "#22c55e",
|
|
14
|
+
"green-500": "#22c55e",
|
|
15
|
+
yellow: "#f59e0b"
|
|
16
|
+
};
|
|
17
|
+
var LENGTH_UNITS = ["px", "rem", "em", "%", "vh", "vw", "vmin", "vmax", "pt"];
|
|
18
|
+
function isNumeric(value) {
|
|
19
|
+
return /^-?\d+(\.\d+)?$/.test(value);
|
|
20
|
+
}
|
|
21
|
+
function normalizeLength(value) {
|
|
22
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const trimmed = value.trim().toLowerCase();
|
|
26
|
+
if (isNumeric(trimmed)) {
|
|
27
|
+
return `${trimmed}px`;
|
|
28
|
+
}
|
|
29
|
+
if (/^-?\d+(\.\d+)?pct$/.test(trimmed)) {
|
|
30
|
+
return `${trimmed.replace("pct", "")}%`;
|
|
31
|
+
}
|
|
32
|
+
if (LENGTH_UNITS.some((unit) => trimmed.endsWith(unit))) {
|
|
33
|
+
return trimmed;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function resolveColor(value) {
|
|
38
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const token = value.trim().toLowerCase();
|
|
42
|
+
if (COLOR_MAP[token]) {
|
|
43
|
+
return COLOR_MAP[token];
|
|
44
|
+
}
|
|
45
|
+
if (/^#[0-9a-f]{3}$|^#[0-9a-f]{6}$/i.test(token)) {
|
|
46
|
+
return token;
|
|
47
|
+
}
|
|
48
|
+
if (/^hex-[0-9a-f]{3}$|^hex-[0-9a-f]{6}$/i.test(token)) {
|
|
49
|
+
return `#${token.slice(4)}`;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function toKebabCase(property) {
|
|
54
|
+
return property.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/applyStyles.js
|
|
58
|
+
function applyStyles(element, styleMap) {
|
|
59
|
+
if (!element || !styleMap) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
Object.entries(styleMap).forEach(([property, value]) => {
|
|
63
|
+
if (value == null) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
element.style.setProperty(toKebabCase(property), String(value));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/mapper.js
|
|
71
|
+
function mapUtilityToStyle(parsed) {
|
|
72
|
+
if (!parsed) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const { utility, value } = parsed;
|
|
76
|
+
switch (utility) {
|
|
77
|
+
case "p": {
|
|
78
|
+
const normalized = normalizeLength(value);
|
|
79
|
+
return normalized ? { padding: normalized } : null;
|
|
80
|
+
}
|
|
81
|
+
case "m": {
|
|
82
|
+
const normalized = normalizeLength(value);
|
|
83
|
+
return normalized ? { margin: normalized } : null;
|
|
84
|
+
}
|
|
85
|
+
case "fs": {
|
|
86
|
+
const normalized = normalizeLength(value);
|
|
87
|
+
return normalized ? { fontSize: normalized } : null;
|
|
88
|
+
}
|
|
89
|
+
case "rounded": {
|
|
90
|
+
const normalized = normalizeLength(value);
|
|
91
|
+
return normalized ? { borderRadius: normalized } : null;
|
|
92
|
+
}
|
|
93
|
+
case "bg": {
|
|
94
|
+
const color = resolveColor(value);
|
|
95
|
+
return color ? { backgroundColor: color } : null;
|
|
96
|
+
}
|
|
97
|
+
case "text": {
|
|
98
|
+
const color = resolveColor(value);
|
|
99
|
+
return color ? { color } : null;
|
|
100
|
+
}
|
|
101
|
+
case "border": {
|
|
102
|
+
const borderWidth = normalizeLength(value);
|
|
103
|
+
if (borderWidth) {
|
|
104
|
+
return { border: `${borderWidth} solid #000000` };
|
|
105
|
+
}
|
|
106
|
+
const borderColor = resolveColor(value);
|
|
107
|
+
if (borderColor) {
|
|
108
|
+
return { border: `1px solid ${borderColor}` };
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
case "center":
|
|
113
|
+
return { textAlign: "center" };
|
|
114
|
+
case "flex":
|
|
115
|
+
return { display: "flex" };
|
|
116
|
+
case "justify-center":
|
|
117
|
+
return { justifyContent: "center" };
|
|
118
|
+
case "items-center":
|
|
119
|
+
return { alignItems: "center" };
|
|
120
|
+
default:
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/parser.js
|
|
126
|
+
var STATIC_UTILITIES = /* @__PURE__ */ new Set([
|
|
127
|
+
"center",
|
|
128
|
+
"flex",
|
|
129
|
+
"justify-center",
|
|
130
|
+
"items-center"
|
|
131
|
+
]);
|
|
132
|
+
var VALUE_UTILITIES = /* @__PURE__ */ new Set([
|
|
133
|
+
"p",
|
|
134
|
+
"m",
|
|
135
|
+
"bg",
|
|
136
|
+
"text",
|
|
137
|
+
"fs",
|
|
138
|
+
"border",
|
|
139
|
+
"rounded"
|
|
140
|
+
]);
|
|
141
|
+
function parseChaiClass(className) {
|
|
142
|
+
if (!className || !className.startsWith("chai-")) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const payload = className.slice(5);
|
|
146
|
+
if (!payload) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
if (STATIC_UTILITIES.has(payload)) {
|
|
150
|
+
return {
|
|
151
|
+
raw: className,
|
|
152
|
+
utility: payload,
|
|
153
|
+
value: null
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const [utility, ...rest] = payload.split("-");
|
|
157
|
+
if (!VALUE_UTILITIES.has(utility) || rest.length === 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const value = rest.join("-");
|
|
161
|
+
if (!value) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
raw: className,
|
|
166
|
+
utility,
|
|
167
|
+
value
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function extractChaiClasses(classList) {
|
|
171
|
+
return Array.from(classList).filter((name) => name.startsWith("chai-"));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/scanner.js
|
|
175
|
+
function applyClassStyles(element) {
|
|
176
|
+
const chaiClasses = extractChaiClasses(element.classList);
|
|
177
|
+
chaiClasses.forEach((className) => {
|
|
178
|
+
const parsed = parseChaiClass(className);
|
|
179
|
+
if (!parsed) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const styleMap = mapUtilityToStyle(parsed);
|
|
183
|
+
if (!styleMap) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
applyStyles(element, styleMap);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function scanDOM(root = document) {
|
|
190
|
+
if (!root || !root.querySelectorAll) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const nodes = root.querySelectorAll("[class]");
|
|
194
|
+
nodes.forEach((node) => applyClassStyles(node));
|
|
195
|
+
}
|
|
196
|
+
function scanElement(element) {
|
|
197
|
+
if (!element || !element.classList) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
applyClassStyles(element);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/index.js
|
|
204
|
+
var observer = null;
|
|
205
|
+
function initChai(options = {}) {
|
|
206
|
+
const { root = document, observe = false } = options;
|
|
207
|
+
scanDOM(root);
|
|
208
|
+
if (observe && root && root.body) {
|
|
209
|
+
observer = new MutationObserver((mutations) => {
|
|
210
|
+
mutations.forEach((mutation) => {
|
|
211
|
+
if (mutation.type !== "childList") {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
mutation.addedNodes.forEach((node) => {
|
|
215
|
+
if (!(node instanceof Element)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
scanElement(node);
|
|
219
|
+
if (node.querySelectorAll) {
|
|
220
|
+
scanDOM(node);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
observer.observe(root.body, {
|
|
226
|
+
childList: true,
|
|
227
|
+
subtree: true
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function stopChaiObserver() {
|
|
232
|
+
if (!observer) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
observer.disconnect();
|
|
236
|
+
observer = null;
|
|
237
|
+
}
|
|
238
|
+
export {
|
|
239
|
+
initChai,
|
|
240
|
+
stopChaiObserver
|
|
241
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@debeshghorui/chaitailwind",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight utility-first CSS engine using chai-* classes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "vitest run --environment jsdom",
|
|
20
|
+
"test:watch": "vitest --environment jsdom",
|
|
21
|
+
"build": "esbuild src/index.js --bundle --format=esm --outfile=dist/index.js",
|
|
22
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"css",
|
|
26
|
+
"utility-css",
|
|
27
|
+
"tailwind",
|
|
28
|
+
"javascript",
|
|
29
|
+
"dom"
|
|
30
|
+
],
|
|
31
|
+
"author": "Debesh",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"esbuild": "^0.25.1",
|
|
35
|
+
"jsdom": "^26.0.0",
|
|
36
|
+
"vitest": "^3.1.1"
|
|
37
|
+
}
|
|
38
|
+
}
|