@formepdf/tailwind 0.7.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.
@@ -0,0 +1 @@
1
+ export declare function resolveColor(name: string): string | undefined;
package/dist/colors.js ADDED
@@ -0,0 +1,126 @@
1
+ const palette = {
2
+ slate: {
3
+ "50": "#f8fafc", "100": "#f1f5f9", "200": "#e2e8f0", "300": "#cbd5e1",
4
+ "400": "#94a3b8", "500": "#64748b", "600": "#475569", "700": "#334155",
5
+ "800": "#1e293b", "900": "#0f172a", "950": "#020617",
6
+ },
7
+ gray: {
8
+ "50": "#f9fafb", "100": "#f3f4f6", "200": "#e5e7eb", "300": "#d1d5db",
9
+ "400": "#9ca3af", "500": "#6b7280", "600": "#4b5563", "700": "#374151",
10
+ "800": "#1f2937", "900": "#111827", "950": "#030712",
11
+ },
12
+ zinc: {
13
+ "50": "#fafafa", "100": "#f4f4f5", "200": "#e4e4e7", "300": "#d4d4d8",
14
+ "400": "#a1a1aa", "500": "#71717a", "600": "#52525b", "700": "#3f3f46",
15
+ "800": "#27272a", "900": "#18181b", "950": "#09090b",
16
+ },
17
+ neutral: {
18
+ "50": "#fafafa", "100": "#f5f5f5", "200": "#e5e5e5", "300": "#d4d4d4",
19
+ "400": "#a3a3a3", "500": "#737373", "600": "#525252", "700": "#404040",
20
+ "800": "#262626", "900": "#171717", "950": "#0a0a0a",
21
+ },
22
+ stone: {
23
+ "50": "#fafaf9", "100": "#f5f5f4", "200": "#e7e5e4", "300": "#d6d3d1",
24
+ "400": "#a8a29e", "500": "#78716c", "600": "#57534e", "700": "#44403c",
25
+ "800": "#292524", "900": "#1c1917", "950": "#0c0a09",
26
+ },
27
+ red: {
28
+ "50": "#fef2f2", "100": "#fee2e2", "200": "#fecaca", "300": "#fca5a5",
29
+ "400": "#f87171", "500": "#ef4444", "600": "#dc2626", "700": "#b91c1c",
30
+ "800": "#991b1b", "900": "#7f1d1d", "950": "#450a0a",
31
+ },
32
+ orange: {
33
+ "50": "#fff7ed", "100": "#ffedd5", "200": "#fed7aa", "300": "#fdba74",
34
+ "400": "#fb923c", "500": "#f97316", "600": "#ea580c", "700": "#c2410c",
35
+ "800": "#9a3412", "900": "#7c2d12", "950": "#431407",
36
+ },
37
+ amber: {
38
+ "50": "#fffbeb", "100": "#fef3c7", "200": "#fde68a", "300": "#fcd34d",
39
+ "400": "#fbbf24", "500": "#f59e0b", "600": "#d97706", "700": "#b45309",
40
+ "800": "#92400e", "900": "#78350f", "950": "#451a03",
41
+ },
42
+ yellow: {
43
+ "50": "#fefce8", "100": "#fef9c3", "200": "#fef08a", "300": "#fde047",
44
+ "400": "#facc15", "500": "#eab308", "600": "#ca8a04", "700": "#a16207",
45
+ "800": "#854d0e", "900": "#713f12", "950": "#422006",
46
+ },
47
+ lime: {
48
+ "50": "#f7fee7", "100": "#ecfccb", "200": "#d9f99d", "300": "#bef264",
49
+ "400": "#a3e635", "500": "#84cc16", "600": "#65a30d", "700": "#4d7c0f",
50
+ "800": "#3f6212", "900": "#365314", "950": "#1a2e05",
51
+ },
52
+ green: {
53
+ "50": "#f0fdf4", "100": "#dcfce7", "200": "#bbf7d0", "300": "#86efac",
54
+ "400": "#4ade80", "500": "#22c55e", "600": "#16a34a", "700": "#15803d",
55
+ "800": "#166534", "900": "#14532d", "950": "#052e16",
56
+ },
57
+ emerald: {
58
+ "50": "#ecfdf5", "100": "#d1fae5", "200": "#a7f3d0", "300": "#6ee7b7",
59
+ "400": "#34d399", "500": "#10b981", "600": "#059669", "700": "#047857",
60
+ "800": "#065f46", "900": "#064e3b", "950": "#022c22",
61
+ },
62
+ teal: {
63
+ "50": "#f0fdfa", "100": "#ccfbf1", "200": "#99f6e4", "300": "#5eead4",
64
+ "400": "#2dd4bf", "500": "#14b8a6", "600": "#0d9488", "700": "#0f766e",
65
+ "800": "#115e59", "900": "#134e4a", "950": "#042f2e",
66
+ },
67
+ cyan: {
68
+ "50": "#ecfeff", "100": "#cffafe", "200": "#a5f3fc", "300": "#67e8f9",
69
+ "400": "#22d3ee", "500": "#06b6d4", "600": "#0891b2", "700": "#0e7490",
70
+ "800": "#155e75", "900": "#164e63", "950": "#083344",
71
+ },
72
+ sky: {
73
+ "50": "#f0f9ff", "100": "#e0f2fe", "200": "#bae6fd", "300": "#7dd3fc",
74
+ "400": "#38bdf8", "500": "#0ea5e9", "600": "#0284c7", "700": "#0369a1",
75
+ "800": "#075985", "900": "#0c4a6e", "950": "#082f49",
76
+ },
77
+ blue: {
78
+ "50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd",
79
+ "400": "#60a5fa", "500": "#3b82f6", "600": "#2563eb", "700": "#1d4ed8",
80
+ "800": "#1e40af", "900": "#1e3a8a", "950": "#172554",
81
+ },
82
+ indigo: {
83
+ "50": "#eef2ff", "100": "#e0e7ff", "200": "#c7d2fe", "300": "#a5b4fc",
84
+ "400": "#818cf8", "500": "#6366f1", "600": "#4f46e5", "700": "#4338ca",
85
+ "800": "#3730a3", "900": "#312e81", "950": "#1e1b4e",
86
+ },
87
+ violet: {
88
+ "50": "#f5f3ff", "100": "#ede9fe", "200": "#ddd6fe", "300": "#c4b5fd",
89
+ "400": "#a78bfa", "500": "#8b5cf6", "600": "#7c3aed", "700": "#6d28d9",
90
+ "800": "#5b21b6", "900": "#4c1d95", "950": "#2e1065",
91
+ },
92
+ purple: {
93
+ "50": "#faf5ff", "100": "#f3e8ff", "200": "#e9d5ff", "300": "#d8b4fe",
94
+ "400": "#c084fc", "500": "#a855f7", "600": "#9333ea", "700": "#7e22ce",
95
+ "800": "#6b21a8", "900": "#581c87", "950": "#3b0764",
96
+ },
97
+ fuchsia: {
98
+ "50": "#fdf4ff", "100": "#fae8ff", "200": "#f5d0fe", "300": "#f0abfc",
99
+ "400": "#e879f9", "500": "#d946ef", "600": "#c026d3", "700": "#a21caf",
100
+ "800": "#86198f", "900": "#701a75", "950": "#4a044e",
101
+ },
102
+ pink: {
103
+ "50": "#fdf2f8", "100": "#fce7f3", "200": "#fbcfe8", "300": "#f9a8d4",
104
+ "400": "#f472b6", "500": "#ec4899", "600": "#db2777", "700": "#be185d",
105
+ "800": "#9d174d", "900": "#831843", "950": "#500724",
106
+ },
107
+ rose: {
108
+ "50": "#fff1f2", "100": "#ffe4e6", "200": "#fecdd3", "300": "#fda4af",
109
+ "400": "#fb7185", "500": "#f43f5e", "600": "#e11d48", "700": "#be123c",
110
+ "800": "#9f1239", "900": "#881337", "950": "#4c0519",
111
+ },
112
+ };
113
+ export function resolveColor(name) {
114
+ if (name === "black")
115
+ return "#000000";
116
+ if (name === "white")
117
+ return "#ffffff";
118
+ if (name === "transparent")
119
+ return "transparent";
120
+ const dashIndex = name.lastIndexOf("-");
121
+ if (dashIndex === -1)
122
+ return undefined;
123
+ const colorName = name.substring(0, dashIndex);
124
+ const shade = name.substring(dashIndex + 1);
125
+ return palette[colorName]?.[shade];
126
+ }
@@ -0,0 +1,9 @@
1
+ import type { FormeStyle } from "./types.js";
2
+ export type { FormeStyle } from "./types.js";
3
+ /**
4
+ * Convert Tailwind CSS class names to a Forme style object.
5
+ *
6
+ * Unknown classes are silently ignored. When classes conflict,
7
+ * the last one wins (natural Object.assign behavior).
8
+ */
9
+ export declare function tw(classes: string): FormeStyle;
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import { parseClass } from "./parsers.js";
2
+ /**
3
+ * Convert Tailwind CSS class names to a Forme style object.
4
+ *
5
+ * Unknown classes are silently ignored. When classes conflict,
6
+ * the last one wins (natural Object.assign behavior).
7
+ */
8
+ export function tw(classes) {
9
+ const result = {};
10
+ if (!classes)
11
+ return result;
12
+ for (const cls of classes.trim().split(/\s+/)) {
13
+ if (!cls)
14
+ continue;
15
+ // Negative values: -mt-4, -top-2, etc.
16
+ if (cls.startsWith("-") && !cls.startsWith("-[")) {
17
+ const parsed = parseClass(cls.substring(1));
18
+ if (parsed) {
19
+ for (const [k, v] of Object.entries(parsed)) {
20
+ if (typeof v === "number")
21
+ parsed[k] = -v;
22
+ }
23
+ Object.assign(result, parsed);
24
+ continue;
25
+ }
26
+ }
27
+ const parsed = parseClass(cls);
28
+ if (parsed)
29
+ Object.assign(result, parsed);
30
+ }
31
+ return result;
32
+ }
@@ -0,0 +1,2 @@
1
+ import type { FormeStyle } from "./types.js";
2
+ export declare function parseClass(cls: string): Partial<FormeStyle> | null;
@@ -0,0 +1,526 @@
1
+ import { resolveColor } from "./colors.js";
2
+ // ── Spacing ──────────────────────────────────────────────────────────
3
+ function parseSpacingValue(val) {
4
+ if (val === "px")
5
+ return 1;
6
+ const n = parseFloat(val);
7
+ if (isNaN(n))
8
+ return undefined;
9
+ return n * 4;
10
+ }
11
+ const spacingMap = {
12
+ pt: "paddingTop", pr: "paddingRight", pb: "paddingBottom", pl: "paddingLeft",
13
+ px: "paddingHorizontal", py: "paddingVertical",
14
+ mt: "marginTop", mr: "marginRight", mb: "marginBottom", ml: "marginLeft",
15
+ mx: "marginHorizontal", my: "marginVertical",
16
+ };
17
+ function parseSpacing(cls) {
18
+ // p-{n} / m-{n}
19
+ const pMatch = cls.match(/^(p|m)-(.+)$/);
20
+ if (pMatch) {
21
+ const prop = pMatch[1] === "p" ? "padding" : "margin";
22
+ if (pMatch[2] === "auto" && prop === "margin")
23
+ return { margin: "auto" };
24
+ const val = parseSpacingValue(pMatch[2]);
25
+ if (val !== undefined)
26
+ return { [prop]: val };
27
+ return null;
28
+ }
29
+ // pt-{n}, px-{n}, mx-auto, etc.
30
+ const sideMatch = cls.match(/^(pt|pr|pb|pl|px|py|mt|mr|mb|ml|mx|my)-(.+)$/);
31
+ if (sideMatch) {
32
+ const key = spacingMap[sideMatch[1]];
33
+ if (!key)
34
+ return null;
35
+ // Margin sides and axes support "auto" for centering
36
+ if (sideMatch[2] === "auto") {
37
+ if (sideMatch[1].startsWith("m"))
38
+ return { [key]: "auto" };
39
+ return null; // padding doesn't support auto
40
+ }
41
+ const val = parseSpacingValue(sideMatch[2]);
42
+ if (val !== undefined)
43
+ return { [key]: val };
44
+ }
45
+ return null;
46
+ }
47
+ // ── Typography ───────────────────────────────────────────────────────
48
+ const textSizes = {
49
+ xs: 12, sm: 14, base: 16, lg: 18, xl: 20,
50
+ "2xl": 24, "3xl": 30, "4xl": 36, "5xl": 48, "6xl": 60, "7xl": 72, "8xl": 96, "9xl": 128,
51
+ };
52
+ const fontWeights = {
53
+ thin: 100, extralight: 200, light: 300, normal: 400, medium: 500,
54
+ semibold: 600, bold: 700, extrabold: 800, black: 900,
55
+ };
56
+ const namedLeading = {
57
+ none: 1, tight: 1.25, snug: 1.375, normal: 1.5, relaxed: 1.625, loose: 2,
58
+ };
59
+ const trackingValues = {
60
+ tighter: -0.05, tight: -0.025, normal: 0, wide: 0.025, wider: 0.05, widest: 0.1,
61
+ };
62
+ function parseTypography(cls) {
63
+ // text-{size}
64
+ if (cls.startsWith("text-")) {
65
+ const rest = cls.substring(5);
66
+ if (textSizes[rest] !== undefined)
67
+ return { fontSize: textSizes[rest] };
68
+ // text-left/center/right/justify
69
+ if (rest === "left" || rest === "center" || rest === "right" || rest === "justify") {
70
+ return { textAlign: rest };
71
+ }
72
+ // text-{color}-{shade} or text-black/white — handled in parseColor
73
+ return null;
74
+ }
75
+ // font-{weight}
76
+ if (cls.startsWith("font-")) {
77
+ const weight = fontWeights[cls.substring(5)];
78
+ if (weight !== undefined)
79
+ return { fontWeight: weight };
80
+ return null;
81
+ }
82
+ // italic
83
+ if (cls === "italic")
84
+ return { fontStyle: "italic" };
85
+ // leading-{value}
86
+ if (cls.startsWith("leading-")) {
87
+ const rest = cls.substring(8);
88
+ if (namedLeading[rest] !== undefined)
89
+ return { lineHeight: namedLeading[rest] };
90
+ const n = parseFloat(rest);
91
+ if (!isNaN(n))
92
+ return { lineHeight: n * 4 };
93
+ return null;
94
+ }
95
+ // tracking-{value}
96
+ if (cls.startsWith("tracking-")) {
97
+ const val = trackingValues[cls.substring(9)];
98
+ if (val !== undefined)
99
+ return { letterSpacing: val };
100
+ return null;
101
+ }
102
+ // text decorations
103
+ if (cls === "underline")
104
+ return { textDecoration: "underline" };
105
+ if (cls === "line-through")
106
+ return { textDecoration: "line-through" };
107
+ if (cls === "no-underline")
108
+ return { textDecoration: "none" };
109
+ // text transforms
110
+ if (cls === "uppercase")
111
+ return { textTransform: "uppercase" };
112
+ if (cls === "lowercase")
113
+ return { textTransform: "lowercase" };
114
+ if (cls === "capitalize")
115
+ return { textTransform: "capitalize" };
116
+ if (cls === "normal-case")
117
+ return { textTransform: "none" };
118
+ return null;
119
+ }
120
+ // ── Colors ───────────────────────────────────────────────────────────
121
+ function parseColorClass(cls) {
122
+ // text-{color}-{shade} or text-black/white/transparent
123
+ if (cls.startsWith("text-")) {
124
+ const rest = cls.substring(5);
125
+ // Skip size and alignment keywords
126
+ if (textSizes[rest] !== undefined)
127
+ return null;
128
+ if (rest === "left" || rest === "center" || rest === "right" || rest === "justify")
129
+ return null;
130
+ const color = resolveColor(rest);
131
+ if (color)
132
+ return { color };
133
+ return null;
134
+ }
135
+ // bg-{color}-{shade}
136
+ if (cls.startsWith("bg-")) {
137
+ const color = resolveColor(cls.substring(3));
138
+ if (color)
139
+ return { backgroundColor: color };
140
+ return null;
141
+ }
142
+ // border-{color}-{shade} — only when it looks like a color, not a width or side
143
+ if (cls.startsWith("border-")) {
144
+ const rest = cls.substring(7);
145
+ // Skip width values and sides
146
+ if (/^\d+$/.test(rest))
147
+ return null;
148
+ if (["t", "r", "b", "l"].includes(rest))
149
+ return null;
150
+ if (rest.startsWith("t-") || rest.startsWith("r-") || rest.startsWith("b-") || rest.startsWith("l-"))
151
+ return null;
152
+ const color = resolveColor(rest);
153
+ if (color)
154
+ return { borderColor: color };
155
+ return null;
156
+ }
157
+ return null;
158
+ }
159
+ // ── Layout ───────────────────────────────────────────────────────────
160
+ const alignItemsMap = {
161
+ start: "flex-start", end: "flex-end", center: "center", stretch: "stretch", baseline: "baseline",
162
+ };
163
+ const justifyContentMap = {
164
+ start: "flex-start", end: "flex-end", center: "center",
165
+ between: "space-between", around: "space-around", evenly: "space-evenly",
166
+ };
167
+ function parseDimension(val) {
168
+ if (val === "full")
169
+ return "100%";
170
+ if (val === "auto")
171
+ return "auto";
172
+ if (val === "screen")
173
+ return "100%";
174
+ // Fractional like 1/2, 1/3, etc. — check before numeric parse since parseFloat("1/2") === 1
175
+ const fracMatch = val.match(/^(\d+)\/(\d+)$/);
176
+ if (fracMatch)
177
+ return `${(parseInt(fracMatch[1]) / parseInt(fracMatch[2]) * 100).toFixed(6)}%`;
178
+ const n = parseFloat(val);
179
+ if (!isNaN(n))
180
+ return n * 4;
181
+ return undefined;
182
+ }
183
+ function parseLayout(cls) {
184
+ // flex direction
185
+ if (cls === "flex")
186
+ return {}; // no-op, flex is default in Forme
187
+ if (cls === "flex-row")
188
+ return { flexDirection: "row" };
189
+ if (cls === "flex-col")
190
+ return { flexDirection: "column" };
191
+ if (cls === "flex-row-reverse")
192
+ return { flexDirection: "row-reverse" };
193
+ if (cls === "flex-col-reverse")
194
+ return { flexDirection: "column-reverse" };
195
+ // align/justify
196
+ if (cls.startsWith("items-")) {
197
+ const val = alignItemsMap[cls.substring(6)];
198
+ if (val)
199
+ return { alignItems: val };
200
+ return null;
201
+ }
202
+ if (cls.startsWith("justify-")) {
203
+ const val = justifyContentMap[cls.substring(8)];
204
+ if (val)
205
+ return { justifyContent: val };
206
+ return null;
207
+ }
208
+ // flex shorthand
209
+ if (cls === "flex-1")
210
+ return { flex: 1 };
211
+ if (cls === "flex-auto")
212
+ return { flexGrow: 1, flexShrink: 1, flexBasis: "auto" };
213
+ if (cls === "flex-initial")
214
+ return { flexGrow: 0, flexShrink: 1, flexBasis: "auto" };
215
+ if (cls === "flex-none")
216
+ return { flex: 0 };
217
+ if (cls === "flex-grow")
218
+ return { flexGrow: 1 };
219
+ if (cls === "flex-grow-0")
220
+ return { flexGrow: 0 };
221
+ if (cls === "flex-shrink")
222
+ return { flexShrink: 1 };
223
+ if (cls === "flex-shrink-0")
224
+ return { flexShrink: 0 };
225
+ if (cls === "flex-wrap")
226
+ return { flexWrap: "wrap" };
227
+ if (cls === "flex-nowrap")
228
+ return { flexWrap: "nowrap" };
229
+ // gap
230
+ if (cls.startsWith("gap-x-")) {
231
+ const n = parseFloat(cls.substring(6));
232
+ if (!isNaN(n))
233
+ return { columnGap: n * 4 };
234
+ return null;
235
+ }
236
+ if (cls.startsWith("gap-y-")) {
237
+ const n = parseFloat(cls.substring(6));
238
+ if (!isNaN(n))
239
+ return { rowGap: n * 4 };
240
+ return null;
241
+ }
242
+ if (cls.startsWith("gap-")) {
243
+ const n = parseFloat(cls.substring(4));
244
+ if (!isNaN(n))
245
+ return { gap: n * 4 };
246
+ return null;
247
+ }
248
+ // dimensions
249
+ if (cls.startsWith("w-")) {
250
+ const val = parseDimension(cls.substring(2));
251
+ if (val !== undefined)
252
+ return { width: val };
253
+ return null;
254
+ }
255
+ if (cls.startsWith("h-")) {
256
+ const val = parseDimension(cls.substring(2));
257
+ if (val !== undefined)
258
+ return { height: val };
259
+ return null;
260
+ }
261
+ if (cls.startsWith("min-w-")) {
262
+ const n = parseFloat(cls.substring(6));
263
+ if (!isNaN(n))
264
+ return { minWidth: n * 4 };
265
+ return null;
266
+ }
267
+ if (cls.startsWith("max-w-")) {
268
+ const n = parseFloat(cls.substring(6));
269
+ if (!isNaN(n))
270
+ return { maxWidth: n * 4 };
271
+ return null;
272
+ }
273
+ if (cls.startsWith("min-h-")) {
274
+ const n = parseFloat(cls.substring(6));
275
+ if (!isNaN(n))
276
+ return { minHeight: n * 4 };
277
+ return null;
278
+ }
279
+ if (cls.startsWith("max-h-")) {
280
+ const n = parseFloat(cls.substring(6));
281
+ if (!isNaN(n))
282
+ return { maxHeight: n * 4 };
283
+ return null;
284
+ }
285
+ // position
286
+ if (cls === "relative")
287
+ return { position: "relative" };
288
+ if (cls === "absolute")
289
+ return { position: "absolute" };
290
+ // positional offsets
291
+ if (cls.startsWith("top-")) {
292
+ const n = parseFloat(cls.substring(4));
293
+ if (!isNaN(n))
294
+ return { top: n * 4 };
295
+ return null;
296
+ }
297
+ if (cls.startsWith("right-")) {
298
+ const n = parseFloat(cls.substring(6));
299
+ if (!isNaN(n))
300
+ return { right: n * 4 };
301
+ return null;
302
+ }
303
+ if (cls.startsWith("bottom-")) {
304
+ const n = parseFloat(cls.substring(7));
305
+ if (!isNaN(n))
306
+ return { bottom: n * 4 };
307
+ return null;
308
+ }
309
+ if (cls.startsWith("left-")) {
310
+ const n = parseFloat(cls.substring(5));
311
+ if (!isNaN(n))
312
+ return { left: n * 4 };
313
+ return null;
314
+ }
315
+ // overflow
316
+ if (cls === "overflow-hidden")
317
+ return { overflow: "hidden" };
318
+ if (cls === "overflow-visible")
319
+ return { overflow: "visible" };
320
+ return null;
321
+ }
322
+ // ── Borders ──────────────────────────────────────────────────────────
323
+ const roundedValues = {
324
+ "": 4, sm: 2, md: 6, lg: 8, xl: 12, "2xl": 16, "3xl": 24, full: 9999, none: 0,
325
+ };
326
+ function parseBorder(cls) {
327
+ // border width
328
+ if (cls === "border")
329
+ return { borderWidth: 1 };
330
+ if (cls.match(/^border-[0248]$/)) {
331
+ return { borderWidth: parseInt(cls.substring(7)) };
332
+ }
333
+ // per-side border width
334
+ const sideMap = {
335
+ t: "borderTopWidth", r: "borderRightWidth", b: "borderBottomWidth", l: "borderLeftWidth",
336
+ };
337
+ if (cls.match(/^border-[trbl]$/)) {
338
+ return { [sideMap[cls.substring(7)]]: 1 };
339
+ }
340
+ const sideWidthMatch = cls.match(/^border-([trbl])-(\d+)$/);
341
+ if (sideWidthMatch) {
342
+ return { [sideMap[sideWidthMatch[1]]]: parseInt(sideWidthMatch[2]) };
343
+ }
344
+ // rounded
345
+ if (cls === "rounded")
346
+ return { borderRadius: roundedValues[""] };
347
+ if (cls.startsWith("rounded-")) {
348
+ const val = roundedValues[cls.substring(8)];
349
+ if (val !== undefined)
350
+ return { borderRadius: val };
351
+ // rounded-{n} for arbitrary numeric
352
+ const n = parseFloat(cls.substring(8));
353
+ if (!isNaN(n))
354
+ return { borderRadius: n };
355
+ return null;
356
+ }
357
+ return null;
358
+ }
359
+ // ── Opacity ──────────────────────────────────────────────────────────
360
+ function parseOpacity(cls) {
361
+ if (cls.startsWith("opacity-")) {
362
+ const n = parseInt(cls.substring(8));
363
+ if (!isNaN(n))
364
+ return { opacity: n / 100 };
365
+ }
366
+ return null;
367
+ }
368
+ // ── Grid ────────────────────────────────────────────────────────────
369
+ function parseGrid(cls) {
370
+ if (cls === "grid")
371
+ return { display: "grid" };
372
+ // grid-cols-{n} / grid-cols-none
373
+ if (cls.startsWith("grid-cols-")) {
374
+ const rest = cls.substring(10);
375
+ if (rest === "none")
376
+ return { gridTemplateColumns: "none" };
377
+ const n = parseInt(rest);
378
+ if (!isNaN(n) && n >= 1 && n <= 12)
379
+ return { gridTemplateColumns: `repeat(${n}, 1fr)` };
380
+ return null;
381
+ }
382
+ // grid-rows-{n} / grid-rows-none
383
+ if (cls.startsWith("grid-rows-")) {
384
+ const rest = cls.substring(10);
385
+ if (rest === "none")
386
+ return { gridTemplateRows: "none" };
387
+ const n = parseInt(rest);
388
+ if (!isNaN(n) && n >= 1 && n <= 6)
389
+ return { gridTemplateRows: `repeat(${n}, 1fr)` };
390
+ return null;
391
+ }
392
+ // col-span-{n} / col-span-full
393
+ if (cls.startsWith("col-span-")) {
394
+ const rest = cls.substring(9);
395
+ if (rest === "full")
396
+ return { gridColumnStart: 1, gridColumnEnd: -1 };
397
+ const n = parseInt(rest);
398
+ if (!isNaN(n) && n >= 1 && n <= 12)
399
+ return { gridColumnSpan: n };
400
+ return null;
401
+ }
402
+ // col-start-{n}
403
+ if (cls.startsWith("col-start-")) {
404
+ const n = parseInt(cls.substring(10));
405
+ if (!isNaN(n) && n >= 1 && n <= 13)
406
+ return { gridColumnStart: n };
407
+ return null;
408
+ }
409
+ // col-end-{n}
410
+ if (cls.startsWith("col-end-")) {
411
+ const n = parseInt(cls.substring(8));
412
+ if (!isNaN(n) && n >= 1 && n <= 13)
413
+ return { gridColumnEnd: n };
414
+ return null;
415
+ }
416
+ // row-span-{n} / row-span-full
417
+ if (cls.startsWith("row-span-")) {
418
+ const rest = cls.substring(9);
419
+ if (rest === "full")
420
+ return { gridRowStart: 1, gridRowEnd: -1 };
421
+ const n = parseInt(rest);
422
+ if (!isNaN(n) && n >= 1 && n <= 6)
423
+ return { gridRowSpan: n };
424
+ return null;
425
+ }
426
+ // row-start-{n}
427
+ if (cls.startsWith("row-start-")) {
428
+ const n = parseInt(cls.substring(10));
429
+ if (!isNaN(n) && n >= 1 && n <= 7)
430
+ return { gridRowStart: n };
431
+ return null;
432
+ }
433
+ // row-end-{n}
434
+ if (cls.startsWith("row-end-")) {
435
+ const n = parseInt(cls.substring(8));
436
+ if (!isNaN(n) && n >= 1 && n <= 7)
437
+ return { gridRowEnd: n };
438
+ return null;
439
+ }
440
+ return null;
441
+ }
442
+ // ── Self Alignment ──────────────────────────────────────────────────
443
+ const alignSelfMap = {
444
+ start: "flex-start", end: "flex-end", center: "center", stretch: "stretch", baseline: "baseline",
445
+ };
446
+ function parseSelf(cls) {
447
+ if (cls.startsWith("self-")) {
448
+ const val = alignSelfMap[cls.substring(5)];
449
+ if (val)
450
+ return { alignSelf: val };
451
+ }
452
+ return null;
453
+ }
454
+ // ── Arbitrary Values ────────────────────────────────────────────────
455
+ const arbitraryPrefixMap = {
456
+ w: "width", h: "height",
457
+ "min-w": "minWidth", "max-w": "maxWidth", "min-h": "minHeight", "max-h": "maxHeight",
458
+ p: "padding", px: "paddingHorizontal", py: "paddingVertical",
459
+ pt: "paddingTop", pr: "paddingRight", pb: "paddingBottom", pl: "paddingLeft",
460
+ m: "margin", mx: "marginHorizontal", my: "marginVertical",
461
+ mt: "marginTop", mr: "marginRight", mb: "marginBottom", ml: "marginLeft",
462
+ gap: "gap", "gap-x": "columnGap", "gap-y": "rowGap",
463
+ top: "top", right: "right", bottom: "bottom", left: "left",
464
+ rounded: "borderRadius",
465
+ opacity: "opacity",
466
+ leading: "lineHeight",
467
+ text: "special",
468
+ bg: "special",
469
+ border: "special",
470
+ };
471
+ function parseArbitrary(cls) {
472
+ const bracketIdx = cls.indexOf("[");
473
+ if (bracketIdx === -1 || !cls.endsWith("]"))
474
+ return null;
475
+ const prefix = cls.substring(0, bracketIdx - 1); // strip trailing '-'
476
+ const raw = cls.substring(bracketIdx + 1, cls.length - 1); // content inside brackets
477
+ const prop = arbitraryPrefixMap[prefix];
478
+ if (!prop)
479
+ return null;
480
+ if (prop === "special") {
481
+ // text-[...]: numeric → fontSize, color → color
482
+ if (prefix === "text") {
483
+ if (raw.startsWith("#"))
484
+ return { color: raw };
485
+ const n = parseFloat(raw.replace(/px$/, ""));
486
+ if (!isNaN(n))
487
+ return { fontSize: n };
488
+ return null;
489
+ }
490
+ // bg-[...]: color only
491
+ if (prefix === "bg") {
492
+ if (raw.startsWith("#"))
493
+ return { backgroundColor: raw };
494
+ return null;
495
+ }
496
+ // border-[...]: numeric → borderWidth, color → borderColor
497
+ if (prefix === "border") {
498
+ if (raw.startsWith("#"))
499
+ return { borderColor: raw };
500
+ const n = parseFloat(raw.replace(/px$/, ""));
501
+ if (!isNaN(n))
502
+ return { borderWidth: n };
503
+ return null;
504
+ }
505
+ return null;
506
+ }
507
+ // Numeric value (strip px suffix)
508
+ const n = parseFloat(raw.replace(/px$/, ""));
509
+ if (!isNaN(n))
510
+ return { [prop]: n };
511
+ return null;
512
+ }
513
+ // ── Main dispatcher ──────────────────────────────────────────────────
514
+ export function parseClass(cls) {
515
+ // Check for arbitrary bracket values first
516
+ if (cls.includes("["))
517
+ return parseArbitrary(cls);
518
+ return (parseSpacing(cls) ??
519
+ parseTypography(cls) ??
520
+ parseColorClass(cls) ??
521
+ parseLayout(cls) ??
522
+ parseGrid(cls) ??
523
+ parseSelf(cls) ??
524
+ parseBorder(cls) ??
525
+ parseOpacity(cls));
526
+ }
@@ -0,0 +1,71 @@
1
+ export interface FormeStyle {
2
+ flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
3
+ alignItems?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
4
+ justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
5
+ flex?: number;
6
+ flexGrow?: number;
7
+ flexShrink?: number;
8
+ flexBasis?: number | string;
9
+ flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
10
+ gap?: number;
11
+ columnGap?: number;
12
+ rowGap?: number;
13
+ position?: "relative" | "absolute";
14
+ top?: number;
15
+ right?: number;
16
+ bottom?: number;
17
+ left?: number;
18
+ overflow?: "hidden" | "visible";
19
+ display?: "flex" | "grid";
20
+ alignSelf?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
21
+ gridTemplateColumns?: string;
22
+ gridTemplateRows?: string;
23
+ gridColumnStart?: number;
24
+ gridColumnEnd?: number;
25
+ gridRowStart?: number;
26
+ gridRowEnd?: number;
27
+ gridColumnSpan?: number;
28
+ gridRowSpan?: number;
29
+ width?: number | string;
30
+ height?: number | string;
31
+ minWidth?: number | string;
32
+ maxWidth?: number | string;
33
+ minHeight?: number | string;
34
+ maxHeight?: number | string;
35
+ padding?: number | string;
36
+ paddingTop?: number;
37
+ paddingRight?: number;
38
+ paddingBottom?: number;
39
+ paddingLeft?: number;
40
+ paddingHorizontal?: number;
41
+ paddingVertical?: number;
42
+ margin?: number | string;
43
+ marginTop?: number | "auto";
44
+ marginRight?: number | "auto";
45
+ marginBottom?: number | "auto";
46
+ marginLeft?: number | "auto";
47
+ marginHorizontal?: number | "auto";
48
+ marginVertical?: number | "auto";
49
+ fontSize?: number;
50
+ fontWeight?: number | "normal" | "bold";
51
+ fontStyle?: "normal" | "italic" | "oblique";
52
+ textAlign?: "left" | "center" | "right" | "justify";
53
+ lineHeight?: number;
54
+ letterSpacing?: number;
55
+ textDecoration?: "none" | "underline" | "line-through";
56
+ textTransform?: "none" | "uppercase" | "lowercase" | "capitalize";
57
+ color?: string;
58
+ backgroundColor?: string;
59
+ borderWidth?: number;
60
+ borderTopWidth?: number;
61
+ borderRightWidth?: number;
62
+ borderBottomWidth?: number;
63
+ borderLeftWidth?: number;
64
+ borderColor?: string;
65
+ borderRadius?: number;
66
+ borderTopLeftRadius?: number;
67
+ borderTopRightRadius?: number;
68
+ borderBottomRightRadius?: number;
69
+ borderBottomLeftRadius?: number;
70
+ opacity?: number;
71
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@formepdf/tailwind",
3
+ "version": "0.7.10",
4
+ "description": "Tailwind CSS class support for Forme PDF templates",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.7.0",
21
+ "vitest": "^3.0.0"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/formepdf/forme",
30
+ "directory": "packages/tailwind"
31
+ }
32
+ }