@hanzo/ui 8.0.82 → 8.0.86

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 (42) hide show
  1. package/dist/backends/gui/button.d.ts +3 -3
  2. package/dist/backends/gui/card.d.ts.map +1 -1
  3. package/dist/backends/gui/card.js.map +1 -1
  4. package/dist/backends/gui/command.js.map +1 -1
  5. package/dist/backends/gui/context-menu.d.ts +3 -3
  6. package/dist/backends/gui/dropdown-menu.d.ts +4 -4
  7. package/dist/backends/gui/resizable.cjs +4 -1
  8. package/dist/backends/gui/resizable.d.ts.map +1 -1
  9. package/dist/backends/gui/resizable.js +4 -1
  10. package/dist/backends/gui/resizable.js.map +1 -1
  11. package/dist/backends/gui/scroll-area.d.ts.map +1 -1
  12. package/dist/backends/gui/scroll-area.js.map +1 -1
  13. package/dist/backends/gui/textarea.js.map +1 -1
  14. package/dist/box.cjs +31 -0
  15. package/dist/box.d.ts +26 -0
  16. package/dist/box.d.ts.map +1 -0
  17. package/dist/box.js +29 -0
  18. package/dist/box.js.map +1 -0
  19. package/dist/chat/Thread.d.ts.map +1 -1
  20. package/dist/chat/Thread.js.map +1 -1
  21. package/dist/gallery.cjs +2 -1
  22. package/dist/gallery.d.ts.map +1 -1
  23. package/dist/gallery.js +2 -1
  24. package/dist/gallery.js.map +1 -1
  25. package/dist/gui-config.cjs +8 -2
  26. package/dist/gui-config.d.ts +469 -461
  27. package/dist/gui-config.d.ts.map +1 -1
  28. package/dist/gui-config.js +8 -2
  29. package/dist/gui-config.js.map +1 -1
  30. package/dist/index.cjs +5 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/models/ModelSelector.js.map +1 -1
  36. package/dist/styles.css +10 -0
  37. package/dist/tw.cjs +468 -0
  38. package/dist/tw.d.ts +31 -0
  39. package/dist/tw.d.ts.map +1 -0
  40. package/dist/tw.js +466 -0
  41. package/dist/tw.js.map +1 -0
  42. package/package.json +10 -2
package/dist/tw.cjs ADDED
@@ -0,0 +1,468 @@
1
+ 'use client';"use strict";
2
+ /**
3
+ * A utility class and a gui style prop are two notations for one value: `px-4`
4
+ * and `px={16}` say the same thing. This is the function between them.
5
+ *
6
+ * It exists so an app carrying a hundred thousand utility classes can render
7
+ * through gui without either rewriting every class first or keeping a second
8
+ * styling engine around to read them. Convert the notation, delete the engine.
9
+ *
10
+ * Values are CONCRETE — `p-4` is 16, not `$4`. The token scale is where these
11
+ * apps are going, but a token substitution moves every measurement on the page
12
+ * at the same time as the engine underneath it changes, and then nothing
13
+ * identifies which of the two moved it. Retheming is its own step.
14
+ *
15
+ * A class this does not know is returned in `rest`, never dropped. A caller
16
+ * passes it through as a class name, so an unconverted page degrades to what it
17
+ * does today rather than to an unstyled one.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.tw = tw;
21
+ /** Tailwind's 4px ramp, and the three fractional stops below it. */
22
+ const SPACE = {
23
+ '0': 0, px: 1, '0.5': 2, '1': 4, '1.5': 6, '2': 8, '2.5': 10, '3': 12,
24
+ '3.5': 14, '4': 16, '5': 20, '6': 24, '7': 28, '8': 32, '9': 36, '10': 40,
25
+ '11': 44, '12': 48, '14': 56, '16': 64, '20': 80, '24': 96, '28': 112,
26
+ '32': 128, '36': 144, '40': 160, '44': 176, '48': 192, '52': 208, '56': 224,
27
+ '60': 240, '64': 256, '72': 288, '80': 320, '96': 384,
28
+ };
29
+ const FONT_SIZE = {
30
+ xs: 12, sm: 14, base: 16, lg: 18, xl: 20, '2xl': 24, '3xl': 30, '4xl': 36,
31
+ '5xl': 48, '6xl': 60, '7xl': 72, '8xl': 96, '9xl': 128,
32
+ };
33
+ const WEIGHT = {
34
+ thin: 100, extralight: 200, light: 300, normal: 400, medium: 500,
35
+ semibold: 600, bold: 700, extrabold: 800, black: 900,
36
+ };
37
+ const RADIUS = {
38
+ none: 0, sm: 2, '': 4, md: 6, lg: 8, xl: 12, '2xl': 16, '3xl': 24, full: 9999,
39
+ };
40
+ const LEADING = {
41
+ none: 1, tight: 1.25, snug: 1.375, normal: 1.5, relaxed: 1.625, loose: 2,
42
+ };
43
+ const TRACKING = {
44
+ tighter: -0.8, tight: -0.4, normal: 0, wide: 0.4, wider: 0.8, widest: 1.6,
45
+ };
46
+ /** Flex words Tailwind and the style property spell differently. */
47
+ const ALIGN = {
48
+ start: 'flex-start', end: 'flex-end', center: 'center',
49
+ between: 'space-between', around: 'space-around', evenly: 'space-evenly',
50
+ stretch: 'stretch', baseline: 'baseline',
51
+ };
52
+ const SIDE = { t: 'Top', r: 'Right', b: 'Bottom', l: 'Left' };
53
+ /** Longest first, so `max-w` wins over `max` and `gap-x` over `gap`. */
54
+ const HEADS = [
55
+ 'min-w', 'min-h', 'max-w', 'max-h', 'grid-cols', 'grid-rows', 'col-span',
56
+ 'row-span', 'translate-x', 'translate-y', 'gap-x', 'gap-y', 'space-x', 'space-y', 'overflow-x', 'overflow-y',
57
+ 'transition', 'duration', 'delay', 'ease', 'blur', 'backdrop-blur', 'shadow',
58
+ 'overflow',
59
+ 'tracking', 'leading', 'rounded', 'border', 'divide', 'aspect', 'object',
60
+ 'whitespace', 'align', 'inset', 'items', 'justify', 'content', 'self',
61
+ 'opacity', 'text', 'font', 'bg', 'gap', 'top', 'right', 'bottom', 'left',
62
+ 'w', 'h', 'z', 'p', 'px', 'py', 'pt', 'pr', 'pb', 'pl',
63
+ 'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml',
64
+ ].sort((a, b) => b.length - a.length);
65
+ /** Tailwind names the gradient's destination; CSS names its angle. */
66
+ const DIRECTION = {
67
+ t: 'to top', tr: 'to top right', r: 'to right', br: 'to bottom right',
68
+ b: 'to bottom', bl: 'to bottom left', l: 'to left', tl: 'to top left',
69
+ };
70
+ const FAMILY = {
71
+ sans: 'var(--font-sans)', serif: 'var(--font-serif)', mono: 'var(--font-mono)',
72
+ };
73
+ const DURATION = {
74
+ '0': 0, '75': 75, '100': 100, '150': 150, '200': 200, '300': 300,
75
+ '500': 500, '700': 700, '1000': 1000,
76
+ };
77
+ /** What Tailwind animates under each word. */
78
+ const TRANSITION = {
79
+ none: 'none', all: 'all', DEFAULT: 'color, background-color, border-color, opacity, box-shadow, transform',
80
+ colors: 'color, background-color, border-color, text-decoration-color, fill, stroke',
81
+ opacity: 'opacity',
82
+ shadow: 'box-shadow', transform: 'transform',
83
+ };
84
+ const SHADOW = {
85
+ none: 'none', sm: '0 1px 2px rgb(0 0 0 / .05)',
86
+ DEFAULT: '0 1px 3px rgb(0 0 0 / .1), 0 1px 2px rgb(0 0 0 / .06)',
87
+ md: '0 4px 6px rgb(0 0 0 / .1), 0 2px 4px rgb(0 0 0 / .06)',
88
+ lg: '0 10px 15px rgb(0 0 0 / .1), 0 4px 6px rgb(0 0 0 / .05)',
89
+ xl: '0 20px 25px rgb(0 0 0 / .1), 0 10px 10px rgb(0 0 0 / .04)',
90
+ '2xl': '0 25px 50px rgb(0 0 0 / .25)', inner: 'inset 0 2px 4px rgb(0 0 0 / .06)',
91
+ };
92
+ const BLUR = {
93
+ none: 0, xs: 4, sm: 8, DEFAULT: 8, md: 12, lg: 16, xl: 24, '2xl': 40, '3xl': 64,
94
+ };
95
+ /** A size word, a fraction, or a step on the ramp. */
96
+ function size(v) {
97
+ if (v === 'full')
98
+ return '100%';
99
+ if (v === 'screen')
100
+ return '100vh';
101
+ if (v === 'auto' || v === 'fit' || v === 'min' || v === 'max')
102
+ return v === 'fit' ? 'fit-content' : v;
103
+ if (v in SPACE)
104
+ return SPACE[v];
105
+ const frac = /^(\d+)\/(\d+)$/.exec(v);
106
+ if (frac)
107
+ return `${((+frac[1] / +frac[2]) * 100).toFixed(4).replace(/\.?0+$/, '')}%`;
108
+ const arb = /^\[(.+)]$/.exec(v);
109
+ if (arb)
110
+ return arb[1];
111
+ return undefined;
112
+ }
113
+ /**
114
+ * A colour is a CSS custom property, so `text-foreground` stays exactly the
115
+ * value the sheet already resolves — the palette is not restated here. The
116
+ * `/NN` suffix is an alpha, which `color-mix` applies without needing to know
117
+ * what the variable holds.
118
+ */
119
+ function color(v) {
120
+ if (v === 'transparent')
121
+ return 'transparent';
122
+ if (v === 'current')
123
+ return 'currentColor';
124
+ if (v === 'white')
125
+ return '#fff';
126
+ if (v === 'black')
127
+ return '#000';
128
+ const m = /^(.+?)(?:\/(\d{1,3}))?$/.exec(v);
129
+ if (!m)
130
+ return undefined;
131
+ const [, name, alpha] = m;
132
+ const arb = /^\[(.+)]$/.exec(name);
133
+ const base = arb ? arb[1]
134
+ : name === 'white' ? '#fff' : name === 'black' ? '#000'
135
+ : `var(--${name})`;
136
+ if (!alpha)
137
+ return base;
138
+ return `color-mix(in srgb, ${base} ${alpha}%, transparent)`;
139
+ }
140
+ /** Properties a negative class may set — the rest have no negative form. */
141
+ const NEGATABLE = /^(margin|top|right|bottom|left|zIndex|translate)/;
142
+ /** One class to zero or more props. Returns null when the class is unknown. */
143
+ function one(c) {
144
+ // display and the flex words
145
+ switch (c) {
146
+ case 'flex': return { display: 'flex', flexDirection: 'row' };
147
+ case 'inline-flex': return { display: 'inline-flex', flexDirection: 'row' };
148
+ case 'grid': return { display: 'grid' };
149
+ case 'block': return { display: 'block' };
150
+ case 'inline': return { display: 'inline' };
151
+ case 'inline-block': return { display: 'inline-block' };
152
+ case 'hidden': return { display: 'none' };
153
+ case 'flex-col': return { flexDirection: 'column' };
154
+ case 'flex-row': return { flexDirection: 'row' };
155
+ case 'flex-col-reverse': return { flexDirection: 'column-reverse' };
156
+ case 'flex-row-reverse': return { flexDirection: 'row-reverse' };
157
+ case 'flex-wrap': return { flexWrap: 'wrap' };
158
+ case 'flex-nowrap': return { flexWrap: 'nowrap' };
159
+ case 'flex-1': return { flex: 1 };
160
+ case 'flex-auto': return { flex: 'auto' };
161
+ case 'flex-none': return { flex: 'none' };
162
+ case 'flex-initial': return { flex: 'initial' };
163
+ case 'flex-shrink-0':
164
+ case 'shrink-0': return { flexShrink: 0 };
165
+ case 'flex-grow':
166
+ case 'grow': return { flexGrow: 1 };
167
+ case 'flex-grow-0':
168
+ case 'grow-0': return { flexGrow: 0 };
169
+ case 'absolute': return { position: 'absolute' };
170
+ case 'relative': return { position: 'relative' };
171
+ case 'fixed': return { position: 'fixed' };
172
+ case 'sticky': return { position: 'sticky' };
173
+ case 'static': return { position: 'static' };
174
+ case 'inset-0': return { top: 0, right: 0, bottom: 0, left: 0 };
175
+ case 'uppercase': return { textTransform: 'uppercase' };
176
+ case 'lowercase': return { textTransform: 'lowercase' };
177
+ case 'capitalize': return { textTransform: 'capitalize' };
178
+ case 'normal-case': return { textTransform: 'none' };
179
+ case 'italic': return { fontStyle: 'italic' };
180
+ case 'not-italic': return { fontStyle: 'normal' };
181
+ case 'underline': return { textDecorationLine: 'underline' };
182
+ case 'line-through': return { textDecorationLine: 'line-through' };
183
+ case 'no-underline': return { textDecorationLine: 'none' };
184
+ case 'truncate': return { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' };
185
+ case 'mx-auto': return { marginLeft: 'auto', marginRight: 'auto' };
186
+ case 'my-auto': return { marginTop: 'auto', marginBottom: 'auto' };
187
+ case 'border': return { borderWidth: 1 };
188
+ case 'rounded': return { borderRadius: RADIUS[''] };
189
+ case 'cursor-pointer': return { cursor: 'pointer' };
190
+ case 'select-none': return { userSelect: 'none' };
191
+ case 'pointer-events-none': return { pointerEvents: 'none' };
192
+ case 'sr-only': return { position: 'absolute', width: 1, height: 1, overflow: 'hidden' };
193
+ case 'tabular-nums': return { fontVariantNumeric: 'tabular-nums' };
194
+ case 'transform': return { transform: 'translateZ(0)' };
195
+ case 'transition': return { transitionProperty: TRANSITION.DEFAULT };
196
+ case 'blur': return { filter: `blur(${BLUR.DEFAULT}px)` };
197
+ case 'shadow': return { boxShadow: SHADOW.DEFAULT };
198
+ }
199
+ // A leading `-` negates whatever the rest of the class resolves to.
200
+ if (c.startsWith('-')) {
201
+ const pos = one(c.slice(1));
202
+ if (!pos)
203
+ return null;
204
+ const out = {};
205
+ for (const k in pos) {
206
+ const v = pos[k];
207
+ out[k] = typeof v === 'number' && NEGATABLE.test(k) ? -v
208
+ : typeof v === 'string' && /^[\d.]+%$/.test(v) && NEGATABLE.test(k) ? `-${v}`
209
+ : v;
210
+ }
211
+ return out;
212
+ }
213
+ // Heads are matched longest-first. A shortest-match split reads `max-w-3xl`
214
+ // as `max` + `w-3xl`, so every compound head silently fell through.
215
+ let head = '', v = '';
216
+ for (const h of HEADS) {
217
+ if (c.length > h.length + 1 && c.startsWith(h + '-')) {
218
+ head = h;
219
+ v = c.slice(h.length + 1);
220
+ break;
221
+ }
222
+ }
223
+ if (!head)
224
+ return null;
225
+ // padding and margin, whole box or one side
226
+ const box = /^([pm])([xytrbl])?$/.exec(head);
227
+ if (box) {
228
+ const [, kind, where] = box;
229
+ const n = v === 'auto' ? 'auto' : SPACE[v] ?? size(v);
230
+ if (n === undefined)
231
+ return null;
232
+ const base = kind === 'p' ? 'padding' : 'margin';
233
+ if (!where)
234
+ return { [base]: n };
235
+ if (where === 'x')
236
+ return { [`${base}Left`]: n, [`${base}Right`]: n };
237
+ if (where === 'y')
238
+ return { [`${base}Top`]: n, [`${base}Bottom`]: n };
239
+ return { [`${base}${SIDE[where]}`]: n };
240
+ }
241
+ switch (head) {
242
+ case 'gap': {
243
+ const n = SPACE[v] ?? size(v);
244
+ return n === undefined ? null : { gap: n };
245
+ }
246
+ case 'gap-x': {
247
+ const n = SPACE[v] ?? size(v);
248
+ return n === undefined ? null : { columnGap: n };
249
+ }
250
+ case 'gap-y': {
251
+ const n = SPACE[v] ?? size(v);
252
+ return n === undefined ? null : { rowGap: n };
253
+ }
254
+ case 'items': return ALIGN[v] ? { alignItems: ALIGN[v] } : null;
255
+ case 'justify': return ALIGN[v] ? { justifyContent: ALIGN[v] } : null;
256
+ case 'self': return ALIGN[v] ? { alignSelf: ALIGN[v] } : null;
257
+ case 'content': return ALIGN[v] ? { alignContent: ALIGN[v] } : null;
258
+ case 'w': {
259
+ const n = size(v);
260
+ return n === undefined ? null : { width: n };
261
+ }
262
+ case 'h': {
263
+ const n = v === 'screen' ? '100vh' : size(v);
264
+ return n === undefined ? null : { height: n };
265
+ }
266
+ case 'min-w': {
267
+ const n = size(v);
268
+ return n === undefined ? null : { minWidth: n };
269
+ }
270
+ case 'min-h': {
271
+ const n = v === 'screen' ? '100vh' : size(v);
272
+ return n === undefined ? null : { minHeight: n };
273
+ }
274
+ case 'max-w': {
275
+ const n = MAX_W[v] ?? size(v);
276
+ return n === undefined ? null : { maxWidth: n };
277
+ }
278
+ case 'max-h': {
279
+ const n = v === 'screen' ? '100vh' : size(v);
280
+ return n === undefined ? null : { maxHeight: n };
281
+ }
282
+ case 'text': {
283
+ if (v in FONT_SIZE)
284
+ return { fontSize: FONT_SIZE[v] };
285
+ if (v === 'left' || v === 'center' || v === 'right' || v === 'justify')
286
+ return { textAlign: v };
287
+ const col = color(v);
288
+ return col ? { color: col } : null;
289
+ }
290
+ case 'bg': {
291
+ const col = color(v);
292
+ return col ? { backgroundColor: col } : null;
293
+ }
294
+ case 'font': return WEIGHT[v] !== undefined ? { fontWeight: WEIGHT[v] }
295
+ : FAMILY[v] ? { fontFamily: FAMILY[v] } : null;
296
+ case 'leading': {
297
+ const arb = /^\[(.+)]$/.exec(v);
298
+ const n = arb ? Number(arb[1]) || arb[1] : LEADING[v] ?? SPACE[v];
299
+ return n === undefined ? null : { lineHeight: n };
300
+ }
301
+ case 'tracking': return TRACKING[v] !== undefined ? { letterSpacing: TRACKING[v] } : null;
302
+ case 'rounded': {
303
+ if (v in RADIUS)
304
+ return { borderRadius: RADIUS[v] };
305
+ const side = /^([trbl]|tl|tr|br|bl)-(.+)$/.exec(v);
306
+ if (side && side[2] in RADIUS)
307
+ return { [`border${CORNER[side[1]]}Radius`]: RADIUS[side[2]] };
308
+ return null;
309
+ }
310
+ case 'border': {
311
+ if (/^\d+$/.test(v))
312
+ return { borderWidth: +v };
313
+ const side = /^([trbl])(?:-(\d+))?$/.exec(v);
314
+ if (side)
315
+ return { [`border${SIDE[side[1]]}Width`]: side[2] ? +side[2] : 1 };
316
+ const col = color(v);
317
+ return col ? { borderColor: col } : null;
318
+ }
319
+ case 'opacity': return /^\d+$/.test(v) ? { opacity: +v / 100 } : null;
320
+ case 'z': return /^\d+$/.test(v) ? { zIndex: +v } : null;
321
+ case 'overflow': return { overflow: v };
322
+ case 'overflow-x': return { overflowX: v };
323
+ case 'overflow-y': return { overflowY: v };
324
+ case 'top':
325
+ case 'right':
326
+ case 'bottom':
327
+ case 'left': {
328
+ const n = v === 'auto' ? 'auto' : SPACE[v] ?? size(v);
329
+ return n === undefined ? null : { [head]: n };
330
+ }
331
+ case 'inset': {
332
+ const n = SPACE[v] ?? size(v);
333
+ return n === undefined ? null : { top: n, right: n, bottom: n, left: n };
334
+ }
335
+ case 'grid-cols': return /^\d+$/.test(v)
336
+ ? { gridTemplateColumns: `repeat(${v}, minmax(0, 1fr))` } : null;
337
+ case 'grid-rows': return /^\d+$/.test(v)
338
+ ? { gridTemplateRows: `repeat(${v}, minmax(0, 1fr))` } : null;
339
+ case 'col-span': return v === 'full'
340
+ ? { gridColumn: '1 / -1' } : /^\d+$/.test(v) ? { gridColumn: `span ${v} / span ${v}` } : null;
341
+ case 'aspect': return v === 'square' ? { aspectRatio: 1 } : v === 'video' ? { aspectRatio: 16 / 9 } : null;
342
+ case 'object': return { objectFit: v };
343
+ case 'whitespace': return { whiteSpace: v };
344
+ case 'align': return { verticalAlign: v };
345
+ case 'transition': return TRANSITION[v] ? { transitionProperty: TRANSITION[v] } : null;
346
+ case 'duration': return DURATION[v] !== undefined ? { transitionDuration: `${DURATION[v]}ms` } : null;
347
+ case 'delay': return DURATION[v] !== undefined ? { transitionDelay: `${DURATION[v]}ms` } : null;
348
+ case 'blur': return BLUR[v] !== undefined ? { filter: `blur(${BLUR[v]}px)` } : null;
349
+ case 'backdrop-blur': return BLUR[v] !== undefined ? { backdropFilter: `blur(${BLUR[v]}px)` } : null;
350
+ // `space-y` puts the gap on the children's margins; `gap` puts it on the
351
+ // container. The rendering matches wherever the container is flex or grid,
352
+ // which is everywhere these classes are used, and `gap` is the one a stack
353
+ // already speaks.
354
+ case 'space-y': {
355
+ const n = SPACE[v] ?? size(v);
356
+ return n === undefined ? null : { rowGap: n };
357
+ }
358
+ case 'space-x': {
359
+ const n = SPACE[v] ?? size(v);
360
+ return n === undefined ? null : { columnGap: n };
361
+ }
362
+ case 'row-span': return /^\d+$/.test(v) ? { gridRow: `span ${v} / span ${v}` } : null;
363
+ case 'translate-x': {
364
+ const n = SPACE[v] ?? size(v);
365
+ return n === undefined ? null
366
+ : { transform: `translateX(${typeof n === 'number' ? n + 'px' : n})` };
367
+ }
368
+ case 'translate-y': {
369
+ const n = SPACE[v] ?? size(v);
370
+ return n === undefined ? null
371
+ : { transform: `translateY(${typeof n === 'number' ? n + 'px' : n})` };
372
+ }
373
+ case 'shadow': return SHADOW[v] ? { boxShadow: SHADOW[v] } : null;
374
+ }
375
+ return null;
376
+ }
377
+ const MAX_W = {
378
+ none: 'none', xs: 320, sm: 384, md: 448, lg: 512, xl: 576, '2xl': 672,
379
+ '3xl': 768, '4xl': 896, '5xl': 1024, '6xl': 1152, '7xl': 1280, full: '100%',
380
+ prose: '65ch', screen: '100vw',
381
+ };
382
+ const CORNER = {
383
+ t: 'Top', r: 'Right', b: 'Bottom', l: 'Left',
384
+ tl: 'TopLeft', tr: 'TopRight', br: 'BottomRight', bl: 'BottomLeft',
385
+ };
386
+ /** Where a prefixed class lands: a gui media prop, or a pseudo-style. */
387
+ const VARIANT = {
388
+ sm: '$sm', md: '$md', lg: '$lg', xl: '$xl', '2xl': '$2xl',
389
+ hover: 'hoverStyle', focus: 'focusStyle', 'focus-visible': 'focusVisibleStyle',
390
+ active: 'pressStyle', disabled: 'disabledStyle', dark: '$theme-dark',
391
+ 'group-hover': 'groupHoverStyle',
392
+ };
393
+ /** clsx's input shapes, since callers already write class names that way. */
394
+ function flatten(v, out = []) {
395
+ if (!v)
396
+ return out;
397
+ if (typeof v === 'string') {
398
+ for (const s of v.split(/\s+/))
399
+ if (s)
400
+ out.push(s);
401
+ return out;
402
+ }
403
+ if (typeof v === 'number') {
404
+ out.push(String(v));
405
+ return out;
406
+ }
407
+ if (Array.isArray(v)) {
408
+ for (const x of v)
409
+ flatten(x, out);
410
+ return out;
411
+ }
412
+ for (const k in v)
413
+ if (v[k])
414
+ out.push(k);
415
+ return out;
416
+ }
417
+ /**
418
+ * `tw('flex items-center gap-4 md:gap-8 hover:bg-muted')` becomes
419
+ * `{ display, flexDirection, alignItems, gap, $md: { gap }, hoverStyle: { backgroundColor } }`.
420
+ */
421
+ function tw(input) {
422
+ const props = {};
423
+ const rest = [];
424
+ // A gradient is the one value spelled across several classes — a direction
425
+ // and its stops — so it is assembled here rather than in `one`, which sees
426
+ // a single class and could only ever produce a fragment of it.
427
+ const grad = {};
428
+ for (const raw of flatten(input)) {
429
+ // A class may carry several prefixes; the last one is the variant that
430
+ // decides where it lands, and an unknown prefix keeps the whole class.
431
+ const parts = raw.split(':');
432
+ const bare = parts.pop();
433
+ if (!parts.length) {
434
+ const g = /^(?:bg-gradient-to-([a-z]{1,2})|(from|via|to)-(.+))$/.exec(bare);
435
+ if (g) {
436
+ if (g[1])
437
+ grad.dir = DIRECTION[g[1]];
438
+ else
439
+ grad[g[2]] = color(g[3]);
440
+ continue;
441
+ }
442
+ }
443
+ const found = one(bare);
444
+ if (!found) {
445
+ rest.push(raw);
446
+ continue;
447
+ }
448
+ if (!parts.length) {
449
+ Object.assign(props, found);
450
+ continue;
451
+ }
452
+ const key = VARIANT[parts[parts.length - 1]];
453
+ if (!key) {
454
+ rest.push(raw);
455
+ continue;
456
+ }
457
+ const bucket = (props[key] ??= {});
458
+ Object.assign(bucket, found);
459
+ }
460
+ // A stop with no direction still reads as a gradient; Tailwind's own
461
+ // default is left-to-right.
462
+ const stops = [grad.from, grad.via, grad.to].filter(Boolean);
463
+ if (stops.length > 1) {
464
+ props.backgroundImage = `linear-gradient(${grad.dir ?? 'to right'}, ${stops.join(', ')})`;
465
+ }
466
+ return { props, rest: rest.join(' ') };
467
+ }
468
+ exports.default = tw;
package/dist/tw.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A utility class and a gui style prop are two notations for one value: `px-4`
3
+ * and `px={16}` say the same thing. This is the function between them.
4
+ *
5
+ * It exists so an app carrying a hundred thousand utility classes can render
6
+ * through gui without either rewriting every class first or keeping a second
7
+ * styling engine around to read them. Convert the notation, delete the engine.
8
+ *
9
+ * Values are CONCRETE — `p-4` is 16, not `$4`. The token scale is where these
10
+ * apps are going, but a token substitution moves every measurement on the page
11
+ * at the same time as the engine underneath it changes, and then nothing
12
+ * identifies which of the two moved it. Retheming is its own step.
13
+ *
14
+ * A class this does not know is returned in `rest`, never dropped. A caller
15
+ * passes it through as a class name, so an unconverted page degrades to what it
16
+ * does today rather than to an unstyled one.
17
+ */
18
+ export type Props = Record<string, unknown>;
19
+ /** Recognized props, plus the classes that stay class names. */
20
+ export type Parsed = {
21
+ props: Props;
22
+ rest: string;
23
+ };
24
+ export type ClassValue = string | number | null | undefined | false | ClassValue[] | Record<string, unknown>;
25
+ /**
26
+ * `tw('flex items-center gap-4 md:gap-8 hover:bg-muted')` becomes
27
+ * `{ display, flexDirection, alignItems, gap, $md: { gap }, hoverStyle: { backgroundColor } }`.
28
+ */
29
+ export declare function tw(input: ClassValue): Parsed;
30
+ export default tw;
31
+ //# sourceMappingURL=tw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tw.d.ts","sourceRoot":"","sources":["../src/tw.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE3C,gEAAgE;AAChE,MAAM,MAAM,MAAM,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAiUnD,MAAM,MAAM,UAAU,GAClB,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,GAC1C,UAAU,EAAE,GACZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAY3B;;;GAGG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAyC5C;AAED,eAAe,EAAE,CAAA"}