@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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/index.js +241 -0
  4. 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
+ }