@flow-os/style 0.0.71 → 0.0.73

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flow-os/style",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "license": "PolyForm-Shield-1.0.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -14,10 +14,18 @@ const BP_MAP: Record<string, string> = { mob: 'max-sm', tab: 'md', des: 'lg' };
14
14
  const BP_KEYS = ['base', 'mob', 'tab', 'des'] as const;
15
15
  const BP_KEYS_SET = new Set(BP_KEYS);
16
16
 
17
+ import { tailwindToStyle, tailwindUnconverted } from '../tailwindUtils';
18
+
17
19
  function cssProp(js: string): string {
18
20
  return js.replace(/([A-Z])/g, '-$1').toLowerCase();
19
21
  }
20
22
 
23
+ function extractInlineFromClasses(classes: string[]): { style: Record<string, string>; rest: string[] } {
24
+ const style = tailwindToStyle(classes);
25
+ const rest = tailwindUnconverted(classes);
26
+ return { style, rest };
27
+ }
28
+
21
29
  function parseBreakpointString(str: string): string {
22
30
  const classes: string[] = [];
23
31
  let base = str;
@@ -38,8 +46,12 @@ function parseBreakpointString(str: string): string {
38
46
 
39
47
  export function resolveS(s: SValue): SResult {
40
48
  if (typeof s === 'string') {
41
- const className = parseBreakpointString(s);
42
- return { className: className || undefined };
49
+ const rawClasses = parseBreakpointString(s).split(/\s+/).filter(Boolean);
50
+ const { style, rest } = extractInlineFromClasses(rawClasses);
51
+ return {
52
+ ...(rest.length ? { className: rest.join(' ') } : {}),
53
+ ...(Object.keys(style).length ? { style } : {}),
54
+ };
43
55
  }
44
56
  if (s && typeof s === 'object') {
45
57
  const obj = s as Record<string, string | number | undefined>;
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Parses shorthand CSS in base: '...' for styleFlow.
3
- * Format: prop value [value...] separated by ;
4
- * Examples: bg red; p 4; flex; c #fff
3
+ * Supports both:
4
+ * - Tailwind-style: flex flex-col p-4 m-2 w-full
5
+ * - Shorthand: p 4; c red; bg #fff
5
6
  */
6
7
 
8
+ import { tailwindToStyle } from '../tailwindUtils';
9
+
7
10
  const SHORTHAND: Record<string, string> = {
8
11
  bg: 'background',
9
12
  background: 'background',
@@ -65,23 +68,20 @@ export function parseBaseShorthand(str: string): Record<string, string> {
65
68
  const tokens = part.split(/\s+/).filter(Boolean);
66
69
  if (tokens.length === 0) continue;
67
70
 
71
+ const twStyle = tailwindToStyle(tokens);
72
+ Object.assign(style, twStyle);
73
+
68
74
  const first = tokens[0].toLowerCase();
69
- if (DISPLAY_TOKENS[first]) {
75
+ if (DISPLAY_TOKENS[first] && !twStyle.display) {
70
76
  style.display = DISPLAY_TOKENS[first];
71
77
  continue;
72
78
  }
73
-
74
- const prop = SHORTHAND[first] ?? first.replace(/([A-Z])/g, '-$1').toLowerCase();
75
- const values = tokens.slice(1);
76
-
77
- if (values.length === 0) continue;
78
- if (values.length === 1) style[prop] = toValue(values[0]);
79
- else if (values.length === 2) {
80
- style[prop] = `${toValue(values[0])} ${toValue(values[1])}`;
81
- } else if (values.length === 4) {
82
- style[prop] = values.map(toValue).join(' ');
83
- } else {
84
- style[prop] = values.map(toValue).join(' ');
79
+ if (first in SHORTHAND && tokens.length >= 2) {
80
+ const prop = SHORTHAND[first];
81
+ const values = tokens.slice(1);
82
+ if (values.length === 1) style[prop] = toValue(values[0]);
83
+ else if (values.length === 2) style[prop] = `${toValue(values[0])} ${toValue(values[1])}`;
84
+ else style[prop] = values.map(toValue).join(' ');
85
85
  }
86
86
  }
87
87
  return style;
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Tailwind-like utilities → inline CSS.
3
+ * Works without Tailwind. Super short: flex flex-col p-4 m-2 w-full
4
+ */
5
+
6
+ const SCALE: Record<number, string> = {
7
+ 0: '0',
8
+ 0.5: '0.125rem',
9
+ 1: '0.25rem',
10
+ 1.5: '0.375rem',
11
+ 2: '0.5rem',
12
+ 2.5: '0.625rem',
13
+ 3: '0.75rem',
14
+ 3.5: '0.875rem',
15
+ 4: '1rem',
16
+ 5: '1.25rem',
17
+ 6: '1.5rem',
18
+ 7: '1.75rem',
19
+ 8: '2rem',
20
+ 9: '2.25rem',
21
+ 10: '2.5rem',
22
+ 11: '2.75rem',
23
+ 12: '3rem',
24
+ 14: '3.5rem',
25
+ 16: '4rem',
26
+ 20: '5rem',
27
+ 24: '6rem',
28
+ 28: '7rem',
29
+ 32: '8rem',
30
+ 36: '9rem',
31
+ 40: '10rem',
32
+ 44: '11rem',
33
+ 48: '12rem',
34
+ 52: '13rem',
35
+ 56: '14rem',
36
+ 60: '15rem',
37
+ 64: '16rem',
38
+ 72: '18rem',
39
+ 80: '20rem',
40
+ 96: '24rem',
41
+ };
42
+
43
+ function scale(n: number): string {
44
+ return SCALE[n] ?? (n <= 96 ? `${n * 0.25}rem` : `${n}px`);
45
+ }
46
+
47
+ type Converter = (m: RegExpMatchArray) => [string, string] | [string, string, string, string] | null;
48
+
49
+ const CONVERTERS: Array<{ re: RegExp; fn: Converter }> = [
50
+ // Display
51
+ { re: /^flex$/, fn: () => ['display', 'flex'] },
52
+ { re: /^inline-flex$/, fn: () => ['display', 'inline-flex'] },
53
+ { re: /^block$/, fn: () => ['display', 'block'] },
54
+ { re: /^inline-block$/, fn: () => ['display', 'inline-block'] },
55
+ { re: /^inline$/, fn: () => ['display', 'inline'] },
56
+ { re: /^grid$/, fn: () => ['display', 'grid'] },
57
+ { re: /^hidden$/, fn: () => ['display', 'none'] },
58
+
59
+ // Flex direction
60
+ { re: /^flex-row$/, fn: () => ['flexDirection', 'row'] },
61
+ { re: /^flex-row-reverse$/, fn: () => ['flexDirection', 'row-reverse'] },
62
+ { re: /^flex-col$/, fn: () => ['flexDirection', 'column'] },
63
+ { re: /^flex-col-reverse$/, fn: () => ['flexDirection', 'column-reverse'] },
64
+
65
+ // Flex wrap
66
+ { re: /^flex-wrap$/, fn: () => ['flexWrap', 'wrap'] },
67
+ { re: /^flex-nowrap$/, fn: () => ['flexWrap', 'nowrap'] },
68
+
69
+ // Flex grow/shrink
70
+ { re: /^flex-1$/, fn: () => ['flex', '1 1 0%'] },
71
+ { re: /^flex-auto$/, fn: () => ['flex', '1 1 auto'] },
72
+ { re: /^flex-none$/, fn: () => ['flex', 'none'] },
73
+ { re: /^flex-initial$/, fn: () => ['flex', '0 1 auto'] },
74
+ { re: /^grow$/, fn: () => ['flexGrow', '1'] },
75
+ { re: /^grow-0$/, fn: () => ['flexGrow', '0'] },
76
+ { re: /^shrink$/, fn: () => ['flexShrink', '1'] },
77
+ { re: /^shrink-0$/, fn: () => ['flexShrink', '0'] },
78
+
79
+ // Justify
80
+ { re: /^justify-start$/, fn: () => ['justifyContent', 'flex-start'] },
81
+ { re: /^justify-end$/, fn: () => ['justifyContent', 'flex-end'] },
82
+ { re: /^justify-center$/, fn: () => ['justifyContent', 'center'] },
83
+ { re: /^justify-between$/, fn: () => ['justifyContent', 'space-between'] },
84
+ { re: /^justify-around$/, fn: () => ['justifyContent', 'space-around'] },
85
+ { re: /^justify-evenly$/, fn: () => ['justifyContent', 'space-evenly'] },
86
+
87
+ // Align
88
+ { re: /^items-start$/, fn: () => ['alignItems', 'flex-start'] },
89
+ { re: /^items-end$/, fn: () => ['alignItems', 'flex-end'] },
90
+ { re: /^items-center$/, fn: () => ['alignItems', 'center'] },
91
+ { re: /^items-stretch$/, fn: () => ['alignItems', 'stretch'] },
92
+ { re: /^items-baseline$/, fn: () => ['alignItems', 'baseline'] },
93
+
94
+ // Self
95
+ { re: /^self-auto$/, fn: () => ['alignSelf', 'auto'] },
96
+ { re: /^self-start$/, fn: () => ['alignSelf', 'flex-start'] },
97
+ { re: /^self-end$/, fn: () => ['alignSelf', 'flex-end'] },
98
+ { re: /^self-center$/, fn: () => ['alignSelf', 'center'] },
99
+ { re: /^self-stretch$/, fn: () => ['alignSelf', 'stretch'] },
100
+
101
+ // Gap
102
+ { re: /^gap-(\d+(?:\.\d+)?)$/, fn: (m) => ['gap', scale(parseFloat(m[1]))] },
103
+ { re: /^gap-x-(\d+(?:\.\d+)?)$/, fn: (m) => ['columnGap', scale(parseFloat(m[1]))] },
104
+ { re: /^gap-y-(\d+(?:\.\d+)?)$/, fn: (m) => ['rowGap', scale(parseFloat(m[1]))] },
105
+
106
+ // Padding
107
+ { re: /^p-(\d+(?:\.\d+)?)$/, fn: (m) => ['padding', scale(parseFloat(m[1]))] },
108
+ { re: /^px-(\d+(?:\.\d+)?)$/, fn: (m) => ['paddingLeft', scale(parseFloat(m[1])), 'paddingRight', scale(parseFloat(m[1]))] },
109
+ { re: /^py-(\d+(?:\.\d+)?)$/, fn: (m) => ['paddingTop', scale(parseFloat(m[1])), 'paddingBottom', scale(parseFloat(m[1]))] },
110
+ { re: /^pt-(\d+(?:\.\d+)?)$/, fn: (m) => ['paddingTop', scale(parseFloat(m[1]))] },
111
+ { re: /^pr-(\d+(?:\.\d+)?)$/, fn: (m) => ['paddingRight', scale(parseFloat(m[1]))] },
112
+ { re: /^pb-(\d+(?:\.\d+)?)$/, fn: (m) => ['paddingBottom', scale(parseFloat(m[1]))] },
113
+ { re: /^pl-(\d+(?:\.\d+)?)$/, fn: (m) => ['paddingLeft', scale(parseFloat(m[1]))] },
114
+ { re: /^p-0$/, fn: () => ['padding', '0'] },
115
+ { re: /^px-0$/, fn: () => ['paddingLeft', '0', 'paddingRight', '0'] },
116
+ { re: /^py-0$/, fn: () => ['paddingTop', '0', 'paddingBottom', '0'] },
117
+
118
+ // Margin
119
+ { re: /^m-(\d+(?:\.\d+)?)$/, fn: (m) => ['margin', scale(parseFloat(m[1]))] },
120
+ { re: /^m-0$/, fn: () => ['margin', '0'] },
121
+ { re: /^mx-(\d+(?:\.\d+)?)$/, fn: (m) => ['marginLeft', scale(parseFloat(m[1])), 'marginRight', scale(parseFloat(m[1]))] },
122
+ { re: /^my-(\d+(?:\.\d+)?)$/, fn: (m) => ['marginTop', scale(parseFloat(m[1])), 'marginBottom', scale(parseFloat(m[1]))] },
123
+ { re: /^mt-(\d+(?:\.\d+)?)$/, fn: (m) => ['marginTop', scale(parseFloat(m[1]))] },
124
+ { re: /^mr-(\d+(?:\.\d+)?)$/, fn: (m) => ['marginRight', scale(parseFloat(m[1]))] },
125
+ { re: /^mb-(\d+(?:\.\d+)?)$/, fn: (m) => ['marginBottom', scale(parseFloat(m[1]))] },
126
+ { re: /^ml-(\d+(?:\.\d+)?)$/, fn: (m) => ['marginLeft', scale(parseFloat(m[1]))] },
127
+ { re: /^mx-auto$/, fn: () => ['marginLeft', 'auto', 'marginRight', 'auto'] },
128
+
129
+ // Width
130
+ { re: /^w-full$/, fn: () => ['width', '100%'] },
131
+ { re: /^w-screen$/, fn: () => ['width', '100vw'] },
132
+ { re: /^w-auto$/, fn: () => ['width', 'auto'] },
133
+ { re: /^w-(\d+(?:\.\d+)?)$/, fn: (m) => ['width', scale(parseFloat(m[1]))] },
134
+ { re: /^w-1\/2$/, fn: () => ['width', '50%'] },
135
+ { re: /^w-1\/3$/, fn: () => ['width', '33.333333%'] },
136
+ { re: /^w-2\/3$/, fn: () => ['width', '66.666667%'] },
137
+ { re: /^w-1\/4$/, fn: () => ['width', '25%'] },
138
+ { re: /^w-3\/4$/, fn: () => ['width', '75%'] },
139
+ { re: /^w-min$/, fn: () => ['width', 'min-content'] },
140
+ { re: /^w-max$/, fn: () => ['width', 'max-content'] },
141
+ { re: /^min-w-0$/, fn: () => ['minWidth', '0'] },
142
+ { re: /^min-w-full$/, fn: () => ['minWidth', '100%'] },
143
+ { re: /^max-w-full$/, fn: () => ['maxWidth', '100%'] },
144
+
145
+ // Height
146
+ { re: /^h-full$/, fn: () => ['height', '100%'] },
147
+ { re: /^h-screen$/, fn: () => ['height', '100vh'] },
148
+ { re: /^h-auto$/, fn: () => ['height', 'auto'] },
149
+ { re: /^h-(\d+(?:\.\d+)?)$/, fn: (m) => ['height', scale(parseFloat(m[1]))] },
150
+ { re: /^min-h-0$/, fn: () => ['minHeight', '0'] },
151
+ { re: /^min-h-full$/, fn: () => ['minHeight', '100%'] },
152
+ { re: /^min-h-screen$/, fn: () => ['minHeight', '100vh'] },
153
+
154
+ // Text
155
+ { re: /^text-left$/, fn: () => ['textAlign', 'left'] },
156
+ { re: /^text-center$/, fn: () => ['textAlign', 'center'] },
157
+ { re: /^text-right$/, fn: () => ['textAlign', 'right'] },
158
+ { re: /^text-justify$/, fn: () => ['textAlign', 'justify'] },
159
+ { re: /^font-bold$/, fn: () => ['fontWeight', '700'] },
160
+ { re: /^font-normal$/, fn: () => ['fontWeight', '400'] },
161
+ { re: /^font-medium$/, fn: () => ['fontWeight', '500'] },
162
+ { re: /^font-semibold$/, fn: () => ['fontWeight', '600'] },
163
+ { re: /^font-light$/, fn: () => ['fontWeight', '300'] },
164
+ { re: /^text-sm$/, fn: () => ['fontSize', '0.875rem'] },
165
+ { re: /^text-base$/, fn: () => ['fontSize', '1rem'] },
166
+ { re: /^text-lg$/, fn: () => ['fontSize', '1.125rem'] },
167
+ { re: /^text-xl$/, fn: () => ['fontSize', '1.25rem'] },
168
+ { re: /^text-2xl$/, fn: () => ['fontSize', '1.5rem'] },
169
+ { re: /^text-3xl$/, fn: () => ['fontSize', '1.875rem'] },
170
+ { re: /^text-xs$/, fn: () => ['fontSize', '0.75rem'] },
171
+ { re: /^text-\[#([0-9a-fA-F]{3,8})\]$/, fn: (m) => ['color', `#${m[1].length === 3 ? m[1].replace(/(.)/g, '$1$1') : m[1]}`] },
172
+ { re: /^text-#([0-9a-fA-F]{3,8})$/, fn: (m) => ['color', `#${m[1].length === 3 ? m[1].replace(/(.)/g, '$1$1') : m[1]}`] },
173
+ { re: /^text-white$/, fn: () => ['color', '#fff'] },
174
+ { re: /^text-black$/, fn: () => ['color', '#000'] },
175
+ { re: /^text-transparent$/, fn: () => ['color', 'transparent'] },
176
+
177
+ // Background
178
+ { re: /^bg-\[#([0-9a-fA-F]{3,8})\]$/, fn: (m) => ['background', `#${m[1].length === 3 ? m[1].replace(/(.)/g, '$1$1') : m[1]}`] },
179
+ { re: /^bg-#([0-9a-fA-F]{3,8})$/, fn: (m) => ['background', `#${m[1].length === 3 ? m[1].replace(/(.)/g, '$1$1') : m[1]}`] },
180
+ { re: /^bg-white$/, fn: () => ['background', '#fff'] },
181
+ { re: /^bg-black$/, fn: () => ['background', '#000'] },
182
+ { re: /^bg-transparent$/, fn: () => ['background', 'transparent'] },
183
+
184
+ // Border
185
+ { re: /^border$/, fn: () => ['borderWidth', '1px'] },
186
+ { re: /^border-0$/, fn: () => ['borderWidth', '0'] },
187
+ { re: /^border-2$/, fn: () => ['borderWidth', '2px'] },
188
+ { re: /^border-4$/, fn: () => ['borderWidth', '4px'] },
189
+ { re: /^rounded$/, fn: () => ['borderRadius', '0.25rem'] },
190
+ { re: /^rounded-sm$/, fn: () => ['borderRadius', '0.125rem'] },
191
+ { re: /^rounded-md$/, fn: () => ['borderRadius', '0.375rem'] },
192
+ { re: /^rounded-lg$/, fn: () => ['borderRadius', '0.5rem'] },
193
+ { re: /^rounded-xl$/, fn: () => ['borderRadius', '0.75rem'] },
194
+ { re: /^rounded-2xl$/, fn: () => ['borderRadius', '1rem'] },
195
+ { re: /^rounded-full$/, fn: () => ['borderRadius', '9999px'] },
196
+ { re: /^rounded-none$/, fn: () => ['borderRadius', '0'] },
197
+
198
+ // Position
199
+ { re: /^relative$/, fn: () => ['position', 'relative'] },
200
+ { re: /^absolute$/, fn: () => ['position', 'absolute'] },
201
+ { re: /^fixed$/, fn: () => ['position', 'fixed'] },
202
+ { re: /^sticky$/, fn: () => ['position', 'sticky'] },
203
+
204
+ // Overflow
205
+ { re: /^overflow-auto$/, fn: () => ['overflow', 'auto'] },
206
+ { re: /^overflow-hidden$/, fn: () => ['overflow', 'hidden'] },
207
+ { re: /^overflow-visible$/, fn: () => ['overflow', 'visible'] },
208
+ { re: /^overflow-scroll$/, fn: () => ['overflow', 'scroll'] },
209
+
210
+ // Opacity
211
+ { re: /^opacity-0$/, fn: () => ['opacity', '0'] },
212
+ { re: /^opacity-50$/, fn: () => ['opacity', '0.5'] },
213
+ { re: /^opacity-100$/, fn: () => ['opacity', '1'] },
214
+
215
+ // Cursor
216
+ { re: /^cursor-pointer$/, fn: () => ['cursor', 'pointer'] },
217
+ { re: /^cursor-default$/, fn: () => ['cursor', 'default'] },
218
+ { re: /^cursor-not-allowed$/, fn: () => ['cursor', 'not-allowed'] },
219
+
220
+ // Grid
221
+ { re: /^grid-cols-1$/, fn: () => ['gridTemplateColumns', 'repeat(1, minmax(0, 1fr))'] },
222
+ { re: /^grid-cols-2$/, fn: () => ['gridTemplateColumns', 'repeat(2, minmax(0, 1fr))'] },
223
+ { re: /^grid-cols-3$/, fn: () => ['gridTemplateColumns', 'repeat(3, minmax(0, 1fr))'] },
224
+ { re: /^grid-cols-4$/, fn: () => ['gridTemplateColumns', 'repeat(4, minmax(0, 1fr))'] },
225
+ { re: /^grid-rows-1$/, fn: () => ['gridTemplateRows', 'repeat(1, minmax(0, 1fr))'] },
226
+ { re: /^grid-rows-2$/, fn: () => ['gridTemplateRows', 'repeat(2, minmax(0, 1fr))'] },
227
+ ];
228
+
229
+ function toCssProp(js: string): string {
230
+ return js.replace(/([A-Z])/g, '-$1').toLowerCase();
231
+ }
232
+
233
+ function classMatchesConverter(c: string): boolean {
234
+ return CONVERTERS.some(({ re }) => re.test(c));
235
+ }
236
+
237
+ export function tailwindToStyle(classes: string[]): Record<string, string> {
238
+ const style: Record<string, string> = {};
239
+ for (const c of classes) {
240
+ for (const { re, fn } of CONVERTERS) {
241
+ const m = c.match(re);
242
+ if (m) {
243
+ const result = fn(m);
244
+ if (result) {
245
+ if (result.length === 2) {
246
+ style[toCssProp(result[0])] = result[1];
247
+ } else {
248
+ style[toCssProp(result[0])] = result[1];
249
+ style[toCssProp(result[2])] = result[3];
250
+ }
251
+ }
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ return style;
257
+ }
258
+
259
+ export function tailwindUnconverted(classes: string[]): string[] {
260
+ return classes.filter((c) => !classMatchesConverter(c));
261
+ }