@urbicon-ui/blocks 6.19.3 → 6.21.1
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/dist/components/Calendar/Calendar.svelte +3 -0
- package/dist/components/Calendar/calendar.variants.d.ts +132 -419
- package/dist/components/CommandPalette/commandPalette.variants.d.ts +24 -51
- package/dist/components/CompositionBar/CompositionBar.svelte +6 -1
- package/dist/components/CompositionBar/composition-bar.variants.d.ts +23 -83
- package/dist/components/CompositionBar/composition-bar.variants.js +9 -3
- package/dist/components/EmptyState/emptyState.variants.d.ts +11 -27
- package/dist/components/FileUpload/fileUpload.variants.d.ts +24 -93
- package/dist/components/Guide/GuidePanel.svelte +38 -0
- package/dist/components/Guide/guide.variants.d.ts +69 -124
- package/dist/components/Guide/guide.variants.js +6 -1
- package/dist/components/Planner/planner.variants.d.ts +23 -94
- package/dist/components/Sankey/sankey.variants.d.ts +15 -48
- package/dist/components/SidebarLayout/sidebar-layout.variants.d.ts +10 -38
- package/dist/components/SidebarLayout/sidebar-layout.variants.js +12 -2
- package/dist/components/ThemeSwitcher/themeSwitcher.variants.d.ts +9 -36
- package/dist/i18n/index.d.ts +2 -372
- package/dist/icons/IconProvider.svelte +3 -0
- package/dist/internal/charts/variants.d.ts +18 -31
- package/dist/mint/styles.css +1 -1
- package/dist/primitives/Accordion/Accordion.svelte +14 -0
- package/dist/primitives/Accordion/AccordionItem.svelte +9 -3
- package/dist/primitives/Accordion/accordion.variants.d.ts +12 -43
- package/dist/primitives/Accordion/accordion.variants.js +6 -2
- package/dist/primitives/Accordion/index.d.ts +12 -0
- package/dist/primitives/Alert/alert.variants.d.ts +21 -62
- package/dist/primitives/Avatar/Avatar.svelte +74 -48
- package/dist/primitives/Avatar/avatar.variants.d.ts +34 -128
- package/dist/primitives/Avatar/avatar.variants.js +43 -18
- package/dist/primitives/Avatar/index.d.ts +6 -1
- package/dist/primitives/Badge/badge.variants.d.ts +19 -92
- package/dist/primitives/Badge/badge.variants.js +11 -4
- package/dist/primitives/Breadcrumb/breadcrumb.variants.d.ts +13 -40
- package/dist/primitives/Button/button.variants.d.ts +19 -98
- package/dist/primitives/Button/button.variants.js +13 -9
- package/dist/primitives/ButtonGroup/buttongroup.variants.d.ts +9 -29
- package/dist/primitives/ButtonGroup/buttongroup.variants.js +3 -1
- package/dist/primitives/Card/card.variants.d.ts +14 -81
- package/dist/primitives/Checkbox/checkbox.variants.d.ts +18 -77
- package/dist/primitives/Collapsible/Collapsible.svelte +13 -0
- package/dist/primitives/Collapsible/collapsible.variants.d.ts +11 -40
- package/dist/primitives/Collapsible/collapsible.variants.js +7 -2
- package/dist/primitives/Collapsible/index.d.ts +16 -0
- package/dist/primitives/Combobox/combobox.variants.d.ts +24 -78
- package/dist/primitives/ConfirmDialog/index.d.ts +12 -0
- package/dist/primitives/Dialog/Dialog.svelte +23 -10
- package/dist/primitives/Dialog/dialog.variants.d.ts +15 -54
- package/dist/primitives/Drawer/Drawer.svelte +24 -11
- package/dist/primitives/Drawer/drawer.variants.d.ts +14 -49
- package/dist/primitives/Input/input.variants.d.ts +32 -139
- package/dist/primitives/Input/input.variants.js +12 -10
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +55 -197
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.js +4 -1
- package/dist/primitives/Menu/MenuSubmenu.svelte +1 -0
- package/dist/primitives/Menu/menu.variants.d.ts +29 -93
- package/dist/primitives/Pagination/pagination.variants.d.ts +16 -54
- package/dist/primitives/Pagination/pagination.variants.js +16 -14
- package/dist/primitives/Popover/Popover.svelte +1 -1
- package/dist/primitives/Popover/popover.variants.d.ts +4 -2
- package/dist/primitives/Progress/progress.variants.d.ts +19 -83
- package/dist/primitives/Progress/progress.variants.js +2 -1
- package/dist/primitives/RadioGroup/radioGroup.variants.d.ts +28 -109
- package/dist/primitives/SegmentGroup/SegmentGroup.svelte +14 -6
- package/dist/primitives/SegmentGroup/segmentgroup.variants.d.ts +12 -53
- package/dist/primitives/SegmentGroup/segmentgroup.variants.js +10 -10
- package/dist/primitives/Select/Select.svelte +1 -1
- package/dist/primitives/Select/select.variants.d.ts +45 -134
- package/dist/primitives/Select/select.variants.js +6 -4
- package/dist/primitives/Separator/separator.variants.d.ts +4 -2
- package/dist/primitives/Sidebar/Sidebar.svelte +10 -15
- package/dist/primitives/Sidebar/sidebar.variants.d.ts +11 -29
- package/dist/primitives/Skeleton/skeleton.variants.d.ts +9 -36
- package/dist/primitives/Slider/slider.variants.d.ts +37 -136
- package/dist/primitives/Slider/slider.variants.js +8 -5
- package/dist/primitives/Spinner/spinner.variants.d.ts +38 -135
- package/dist/primitives/Stepper/stepper.variants.d.ts +32 -130
- package/dist/primitives/Tab/Tab.svelte +17 -6
- package/dist/primitives/Tab/tab.variants.d.ts +17 -78
- package/dist/primitives/Textarea/textarea.variants.d.ts +27 -112
- package/dist/primitives/Textarea/textarea.variants.js +12 -10
- package/dist/primitives/Toast/Toaster.svelte +16 -3
- package/dist/primitives/Toast/index.d.ts +9 -0
- package/dist/primitives/Toast/toast.variants.d.ts +22 -78
- package/dist/primitives/Toggle/toggle.variants.d.ts +17 -71
- package/dist/primitives/Toolbar/toolbar.variants.d.ts +9 -60
- package/dist/primitives/Tooltip/Tooltip.svelte +11 -0
- package/dist/primitives/Tooltip/index.d.ts +14 -0
- package/dist/primitives/Tooltip/tooltip.variants.d.ts +11 -50
- package/dist/primitives/Tooltip/tooltip.variants.js +7 -1
- package/dist/style/foundation.css +4 -1
- package/dist/style/interaction.css +40 -12
- package/dist/style/semantic.css +2 -2
- package/dist/style/themes/index.css +1 -1
- package/dist/system/attachments/ContextIsolation.svelte +2 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +5 -0
- package/dist/utils/overlay.d.ts +33 -4
- package/dist/utils/overlay.js +61 -18
- package/dist/utils/roving.d.ts +15 -0
- package/dist/utils/roving.js +44 -0
- package/dist/utils/use-floating-panel.svelte.d.ts +4 -1
- package/dist/utils/use-floating-panel.svelte.js +23 -1
- package/dist/utils/variants.d.ts +60 -16
- package/dist/utils/variants.js +424 -123
- package/package.json +9 -6
package/dist/utils/variants.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// ─── cx: class concatenation ─────────────────────────────────────────────────
|
|
3
3
|
/**
|
|
4
4
|
* Concatenate class inputs into a single string. Accepts strings, nested
|
|
5
|
-
* arrays,
|
|
6
|
-
*
|
|
5
|
+
* arrays, `{ class: condition }` records (Svelte 5 `ClassValue` / clsx
|
|
6
|
+
* shape — keys with truthy values are included) and falsy values (filtered
|
|
7
|
+
* out). Trims and joins with single spaces.
|
|
7
8
|
*
|
|
8
9
|
* Note: `cx()` does **not** deduplicate — `cx('foo', 'foo bar')` returns
|
|
9
10
|
* `'foo foo bar'`. Duplicate Tailwind classes are harmless at runtime
|
|
@@ -20,11 +21,24 @@ export function cx(...inputs) {
|
|
|
20
21
|
if (trimmed)
|
|
21
22
|
parts.push(trimmed);
|
|
22
23
|
}
|
|
24
|
+
else if (typeof input === 'number') {
|
|
25
|
+
// clsx/ClassValue parity: numbers stringify (0 is already filtered).
|
|
26
|
+
parts.push(String(input));
|
|
27
|
+
}
|
|
23
28
|
else if (Array.isArray(input)) {
|
|
24
29
|
const nested = cx(...input);
|
|
25
30
|
if (nested)
|
|
26
31
|
parts.push(nested);
|
|
27
32
|
}
|
|
33
|
+
else if (typeof input === 'object') {
|
|
34
|
+
for (const key of Object.keys(input)) {
|
|
35
|
+
if (!input[key])
|
|
36
|
+
continue;
|
|
37
|
+
const trimmed = key.trim();
|
|
38
|
+
if (trimmed)
|
|
39
|
+
parts.push(trimmed);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
28
42
|
}
|
|
29
43
|
return parts.join(' ');
|
|
30
44
|
}
|
|
@@ -53,27 +67,34 @@ const BUCKET_PATTERNS = [
|
|
|
53
67
|
// Flex / Grid
|
|
54
68
|
[/^flex-(row|col|row-reverse|col-reverse)$/, 'flex-direction'],
|
|
55
69
|
[/^flex-(wrap|nowrap|wrap-reverse)$/, 'flex-wrap'],
|
|
56
|
-
|
|
57
|
-
[/^
|
|
58
|
-
[/^
|
|
70
|
+
// v4 accepts any integer (`flex-2`) and arbitrary values (`flex-[2_2_0%]`).
|
|
71
|
+
[/^flex-(\d+|auto|initial|none|\[[^\]]+\])$/, 'flex'],
|
|
72
|
+
[/^grow(-\d+|-\[.+\])?$/, 'flex-grow'],
|
|
73
|
+
[/^shrink(-\d+|-\[.+\])?$/, 'flex-shrink'],
|
|
59
74
|
[/^basis-/, 'flex-basis'],
|
|
60
75
|
[/^order-/, 'order'],
|
|
61
|
-
[/^items-(start|end|center|baseline|stretch)$/, 'align-items'],
|
|
76
|
+
[/^items-(start|end|center|baseline|baseline-last|stretch)$/, 'align-items'],
|
|
62
77
|
[/^justify-(start|end|center|between|around|evenly|normal|stretch)$/, 'justify-content'],
|
|
63
|
-
[/^justify-items-(start|end|center|stretch)$/, 'justify-items'],
|
|
78
|
+
[/^justify-items-(start|end|center|stretch|normal)$/, 'justify-items'],
|
|
64
79
|
[/^justify-self-(auto|start|end|center|stretch)$/, 'justify-self'],
|
|
65
80
|
[/^content-(start|end|center|between|around|evenly|baseline|stretch|normal)$/, 'align-content'],
|
|
66
|
-
[/^self-(auto|start|end|center|stretch|baseline)$/, 'align-self'],
|
|
81
|
+
[/^self-(auto|start|end|center|stretch|baseline|baseline-last)$/, 'align-self'],
|
|
67
82
|
[/^place-content-/, 'place-content'],
|
|
68
83
|
[/^place-items-/, 'place-items'],
|
|
69
84
|
[/^place-self-/, 'place-self'],
|
|
70
85
|
[/^grid-cols-/, 'grid-template-columns'],
|
|
71
86
|
[/^grid-rows-/, 'grid-template-rows'],
|
|
87
|
+
[/^grid-flow-/, 'grid-auto-flow'],
|
|
88
|
+
// grid-column vs -start vs -end are three CSS properties — `col-span-2`
|
|
89
|
+
// must not conflict with `col-start-3`.
|
|
90
|
+
[/^col-start-/, 'grid-column-start'],
|
|
91
|
+
[/^col-end-/, 'grid-column-end'],
|
|
72
92
|
[/^col-/, 'grid-column'],
|
|
93
|
+
[/^row-start-/, 'grid-row-start'],
|
|
94
|
+
[/^row-end-/, 'grid-row-end'],
|
|
73
95
|
[/^row-/, 'grid-row'],
|
|
74
96
|
[/^auto-cols-/, 'grid-auto-columns'],
|
|
75
97
|
[/^auto-rows-/, 'grid-auto-rows'],
|
|
76
|
-
[/^auto-flow-/, 'grid-auto-flow'],
|
|
77
98
|
// Sizing — width / height
|
|
78
99
|
[/^min-w-/, 'min-w'],
|
|
79
100
|
[/^min-h-/, 'min-h'],
|
|
@@ -103,13 +124,27 @@ const BUCKET_PATTERNS = [
|
|
|
103
124
|
[/^ms-/, 'ms'],
|
|
104
125
|
[/^me-/, 'me'],
|
|
105
126
|
[/^m-/, 'm'],
|
|
106
|
-
// Gap / space-between
|
|
127
|
+
// Gap / space-between / divide
|
|
107
128
|
[/^gap-x-/, 'gap-x'],
|
|
108
129
|
[/^gap-y-/, 'gap-y'],
|
|
109
130
|
[/^gap-/, 'gap'],
|
|
131
|
+
[/^space-x-reverse$/, 'space-x-reverse'],
|
|
132
|
+
[/^space-y-reverse$/, 'space-y-reverse'],
|
|
110
133
|
[/^space-x-/, 'space-x'],
|
|
111
134
|
[/^space-y-/, 'space-y'],
|
|
112
|
-
//
|
|
135
|
+
// divide-* — width / style / color are orthogonal properties.
|
|
136
|
+
[/^divide-(x|y)-reverse$/, (m) => `divide-${m[1]}-reverse`],
|
|
137
|
+
[/^divide-(x|y)(-(\d+|\[[^\]]+\]))?$/, (m) => `divide-${m[1]}-width`],
|
|
138
|
+
[/^divide-(solid|dashed|dotted|double|none)$/, 'divide-style'],
|
|
139
|
+
[/^divide-/, 'divide-color'],
|
|
140
|
+
// Position offsets. v4.1 inset-shadow-* / inset-ring-* must not fall into
|
|
141
|
+
// the `inset-` position bucket.
|
|
142
|
+
[/^inset-shadow-\[/, 'inset-shadow'],
|
|
143
|
+
[/^inset-shadow-(2xs|xs|sm|none)$/, 'inset-shadow'],
|
|
144
|
+
[/^inset-shadow$/, 'inset-shadow'],
|
|
145
|
+
[/^inset-shadow-/, 'inset-shadow-color'],
|
|
146
|
+
[/^inset-ring(-\d+|-\[[^\]]+\])?$/, 'inset-ring-width'],
|
|
147
|
+
[/^inset-ring-/, 'inset-ring-color'],
|
|
113
148
|
[/^inset-x-/, 'inset-x'],
|
|
114
149
|
[/^inset-y-/, 'inset-y'],
|
|
115
150
|
[/^inset-/, 'inset'],
|
|
@@ -136,6 +171,11 @@ const BUCKET_PATTERNS = [
|
|
|
136
171
|
[/^rounded-s(-|$)/, 'rounded-s'],
|
|
137
172
|
[/^rounded-e(-|$)/, 'rounded-e'],
|
|
138
173
|
[/^rounded(-|$)/, 'rounded'],
|
|
174
|
+
// border-collapse / border-spacing are table-layout properties, not colors.
|
|
175
|
+
[/^border-(collapse|separate)$/, 'border-collapse'],
|
|
176
|
+
[/^border-spacing-x-/, 'border-spacing-x'],
|
|
177
|
+
[/^border-spacing-y-/, 'border-spacing-y'],
|
|
178
|
+
[/^border-spacing-/, 'border-spacing'],
|
|
139
179
|
// Border style (must come before width/color)
|
|
140
180
|
[/^border-(solid|dashed|dotted|double|hidden|none)$/, 'border-style'],
|
|
141
181
|
// Border width — `border`, `border-1`, `border-2`, `border-[3px]` (Tailwind v4 accepts any integer)
|
|
@@ -145,10 +185,12 @@ const BUCKET_PATTERNS = [
|
|
|
145
185
|
// Border color (catch-all, e.g. `border-red-500`, `border-primary`, `border-border-subtle`)
|
|
146
186
|
[/^border-(x|y|t|r|b|l|s|e)-/, (m) => `border-${m[1]}-color`],
|
|
147
187
|
[/^border-/, 'border-color'],
|
|
148
|
-
// Outline / ring
|
|
149
|
-
|
|
188
|
+
// Outline / ring. In v4 bare `outline` sets outline-style: solid — it is a
|
|
189
|
+
// style, not a width.
|
|
190
|
+
[/^outline-(solid|dashed|dotted|double|none|hidden)$/, 'outline-style'],
|
|
191
|
+
[/^outline$/, 'outline-style'],
|
|
150
192
|
[/^outline-offset-/, 'outline-offset'],
|
|
151
|
-
[/^outline(-\d+|-\[[^\]]+\])
|
|
193
|
+
[/^outline(-\d+|-\[[^\]]+\])$/, 'outline-width'],
|
|
152
194
|
[/^outline-/, 'outline-color'],
|
|
153
195
|
[/^ring-offset-(\d+|\[[^\]]+\])$/, 'ring-offset-width'],
|
|
154
196
|
[/^ring-offset-/, 'ring-offset-color'],
|
|
@@ -162,30 +204,58 @@ const BUCKET_PATTERNS = [
|
|
|
162
204
|
[/^bg-clip-/, 'bg-clip'],
|
|
163
205
|
[/^bg-origin-/, 'bg-origin'],
|
|
164
206
|
[
|
|
165
|
-
/^bg-(top|right|bottom|left|center|left-top|left-bottom|right-top|right-bottom)$/,
|
|
207
|
+
/^bg-(top|right|bottom|left|center|left-top|left-bottom|right-top|right-bottom|top-left|top-right|bottom-left|bottom-right)$/,
|
|
166
208
|
'bg-position'
|
|
167
209
|
],
|
|
210
|
+
// Data-type hints and image-valued arbitraries must not read as bg-color —
|
|
211
|
+
// background-image and background-color are independent CSS properties
|
|
212
|
+
// (a gradient overlay must never strip the color underneath, see the
|
|
213
|
+
// Skeleton wave shimmer and Progress striped fill).
|
|
214
|
+
[/^bg-\[(?:length|size):/, 'bg-size'],
|
|
215
|
+
[/^bg-\[position:/, 'bg-position'],
|
|
216
|
+
[/^bg-\[(?:image:|url\(|linear-gradient\(|radial-gradient\(|conic-gradient\()/, 'bg-image'],
|
|
217
|
+
[/^bg-\[color:/, 'bg-color'],
|
|
218
|
+
[/^bg-position-/, 'bg-position'],
|
|
219
|
+
[/^bg-size-/, 'bg-size'],
|
|
220
|
+
[/^bg-(?:linear|radial|conic)(-|$)/, 'bg-image'],
|
|
168
221
|
[/^bg-gradient-to-/, 'bg-image'],
|
|
222
|
+
[/^bg-none$/, 'bg-image'],
|
|
169
223
|
[/^bg-/, 'bg-color'],
|
|
170
224
|
[/^from-/, 'gradient-from'],
|
|
171
225
|
[/^via-/, 'gradient-via'],
|
|
172
226
|
[/^to-/, 'gradient-to'],
|
|
173
227
|
// Text — size / align / weight / color (specific first). v4 size+leading shorthand: `text-sm/6`, `text-base/relaxed`.
|
|
228
|
+
// v4.1 text-shadow-* must not read as text-color — and like `shadow`,
|
|
229
|
+
// the named size scale is finite; other single-word values are colors
|
|
230
|
+
// (`text-shadow-white` must not strip `text-shadow-lg`).
|
|
231
|
+
[/^text-shadow-\[/, 'text-shadow'],
|
|
232
|
+
[/^text-shadow-(2xs|xs|sm|md|lg|none)$/, 'text-shadow'],
|
|
233
|
+
[/^text-shadow$/, 'text-shadow'],
|
|
234
|
+
[/^text-shadow-/, 'text-shadow-color'],
|
|
174
235
|
[/^text-(xs|sm|base|lg|xl|\d+xl)(\/[\w.-]+)?$/, 'text-size'],
|
|
236
|
+
// Data-type hints disambiguate the overloaded `text-` arbitraries.
|
|
237
|
+
[/^text-\[length:/, 'text-size'],
|
|
238
|
+
[/^text-\[color:/, 'text-color'],
|
|
175
239
|
// Arbitrary text-size values starting with a digit (e.g. `text-[11px]`,
|
|
176
240
|
// `text-[1.5rem]`). Hex/named values like `text-[#fff]` / `text-[var(--x)]`
|
|
177
241
|
// still fall through to the text-color catch-all below.
|
|
178
242
|
[/^text-\[[0-9.]/, 'text-size'],
|
|
179
243
|
[/^text-(left|center|right|justify|start|end)$/, 'text-align'],
|
|
180
|
-
|
|
244
|
+
// text-overflow (ellipsis/clip) and text-wrap (wrap/nowrap/balance/pretty)
|
|
245
|
+
// are different CSS properties — a wrap override must not strip ellipsis'
|
|
246
|
+
// sibling and vice versa.
|
|
247
|
+
[/^text-(ellipsis|clip)$/, 'text-overflow'],
|
|
248
|
+
[/^text-(wrap|nowrap|balance|pretty)$/, 'text-wrap'],
|
|
181
249
|
[/^text-(current|inherit|transparent)$/, 'text-color'],
|
|
182
250
|
[/^text-/, 'text-color'],
|
|
183
251
|
[/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/, 'font-weight'],
|
|
252
|
+
[/^font-stretch-/, 'font-stretch'],
|
|
184
253
|
[/^font-(sans|serif|mono)$/, 'font-family'],
|
|
185
254
|
[/^font-/, 'font-family'],
|
|
186
255
|
[/^italic$|^not-italic$/, 'font-style'],
|
|
187
256
|
[/^tracking-/, 'letter-spacing'],
|
|
188
257
|
[/^leading-/, 'line-height'],
|
|
258
|
+
[/^line-clamp-/, 'line-clamp'],
|
|
189
259
|
[/^underline$|^overline$|^line-through$|^no-underline$/, 'text-decoration'],
|
|
190
260
|
// text-decoration sub-properties are orthogonal CSS — three separate buckets.
|
|
191
261
|
// Order matters: style names first, then thickness (numeric/arbitrary), then color catch-all.
|
|
@@ -201,8 +271,10 @@ const BUCKET_PATTERNS = [
|
|
|
201
271
|
[/^align-(baseline|top|middle|bottom|text-top|text-bottom|sub|super)$/, 'vertical-align'],
|
|
202
272
|
// Effects
|
|
203
273
|
[/^opacity-/, 'opacity'],
|
|
204
|
-
|
|
205
|
-
|
|
274
|
+
// Named shadow scale is finite; any other `shadow-<word>` is a color
|
|
275
|
+
// (`shadow-primary` must not strip `shadow-md`).
|
|
276
|
+
[/^shadow-(2xs|xs|sm|md|lg|xl|2xl|inner|none)$/, 'shadow'],
|
|
277
|
+
[/^shadow$/, 'shadow'],
|
|
206
278
|
[/^shadow-\[/, 'shadow'],
|
|
207
279
|
[/^shadow-/, 'shadow-color'],
|
|
208
280
|
[/^blur(-\w+)?$/, 'blur'],
|
|
@@ -220,13 +292,20 @@ const BUCKET_PATTERNS = [
|
|
|
220
292
|
[/^scale-x-/, 'scale-x'],
|
|
221
293
|
[/^scale-y-/, 'scale-y'],
|
|
222
294
|
[/^scale-/, 'scale'],
|
|
295
|
+
// v4 3D rotate axes are orthogonal to plain rotate.
|
|
296
|
+
[/^rotate-(x|y|z)-/, (m) => `rotate-${m[1]}`],
|
|
223
297
|
[/^rotate-/, 'rotate'],
|
|
224
298
|
[/^translate-x-/, 'translate-x'],
|
|
225
299
|
[/^translate-y-/, 'translate-y'],
|
|
300
|
+
[/^translate-z-/, 'translate-z'],
|
|
301
|
+
// v4 plain `translate-*` sets both axes.
|
|
302
|
+
[/^translate-/, 'translate'],
|
|
226
303
|
[/^skew-x-/, 'skew-x'],
|
|
227
304
|
[/^skew-y-/, 'skew-y'],
|
|
228
305
|
[/^origin-/, 'transform-origin'],
|
|
229
|
-
// Transitions / animations
|
|
306
|
+
// Transitions / animations. v4 transition-behavior must not conflict with
|
|
307
|
+
// transition-property (`transition-discrete` composes with `transition-colors`).
|
|
308
|
+
[/^transition-(discrete|normal)$/, 'transition-behavior'],
|
|
230
309
|
[/^transition(-\w+)?$/, 'transition'],
|
|
231
310
|
[/^transition-\[/, 'transition'],
|
|
232
311
|
[/^duration-/, 'transition-duration'],
|
|
@@ -239,6 +318,37 @@ const BUCKET_PATTERNS = [
|
|
|
239
318
|
[/^accent-/, 'accent-color'],
|
|
240
319
|
[/^caret-/, 'caret-color']
|
|
241
320
|
];
|
|
321
|
+
// Arbitrary CSS properties whose utility twin should share a bucket, so
|
|
322
|
+
// `[gap:inherit]` and `gap-4` resolve against each other (Codeberg #21).
|
|
323
|
+
// Everything else buckets as `@<property>` — same-property arbitraries
|
|
324
|
+
// conflict, utilities stay untouched (twMerge behaves the same way).
|
|
325
|
+
const ARBITRARY_PROP_ALIAS = {
|
|
326
|
+
gap: 'gap',
|
|
327
|
+
'column-gap': 'gap-x',
|
|
328
|
+
'row-gap': 'gap-y',
|
|
329
|
+
transform: 'transform'
|
|
330
|
+
};
|
|
331
|
+
const ARBITRARY_PROP_PATTERN = /^\[((?:--)?[a-zA-Z][a-zA-Z0-9-]*):.+\]$/;
|
|
332
|
+
/**
|
|
333
|
+
* Index of the last `:` outside any `[]`/`()` nesting, or -1. This is the
|
|
334
|
+
* modifier/base boundary: colons inside arbitrary values (`bg-[url(https://…)]`),
|
|
335
|
+
* arbitrary properties (`[gap:inherit]`) and selector modifiers
|
|
336
|
+
* (`supports-[display:grid]:grid`) must not split the class.
|
|
337
|
+
*/
|
|
338
|
+
function lastTopLevelColon(cls) {
|
|
339
|
+
let depth = 0;
|
|
340
|
+
let last = -1;
|
|
341
|
+
for (let i = 0; i < cls.length; i++) {
|
|
342
|
+
const ch = cls[i];
|
|
343
|
+
if (ch === '[' || ch === '(')
|
|
344
|
+
depth++;
|
|
345
|
+
else if (ch === ']' || ch === ')')
|
|
346
|
+
depth = depth > 0 ? depth - 1 : 0;
|
|
347
|
+
else if (ch === ':' && depth === 0)
|
|
348
|
+
last = i;
|
|
349
|
+
}
|
|
350
|
+
return last;
|
|
351
|
+
}
|
|
242
352
|
/**
|
|
243
353
|
* Returns the conflict-bucket key of a Tailwind class, or `null` if the class
|
|
244
354
|
* does not match any catalogued utility family. Unknown classes never conflict.
|
|
@@ -246,8 +356,10 @@ const BUCKET_PATTERNS = [
|
|
|
246
356
|
* Modifier prefixes are part of the key — `hover:bg-red` returns `hover:bg-color`
|
|
247
357
|
* while `bg-red` returns `bg-color`. The two never strip each other. `!`-important
|
|
248
358
|
* is normalized away entirely so `!bg-red` and `bg-red` share a bucket.
|
|
359
|
+
* Arbitrary properties bucket per property name (`[gap:inherit]` → `gap`,
|
|
360
|
+
* `[--spinner-speed:1s]` → `@--spinner-speed`).
|
|
249
361
|
*
|
|
250
|
-
* Results are memoized per process — `tailwindBucket` is called O(
|
|
362
|
+
* Results are memoized per process — `tailwindBucket` is called O(sources × classes)
|
|
251
363
|
* per `tv()` resolve, and the same handful of class strings appear thousands of
|
|
252
364
|
* times across a render tree. Cap kept high enough for any realistic application
|
|
253
365
|
* surface; values are short and the map never frees.
|
|
@@ -255,11 +367,13 @@ const BUCKET_PATTERNS = [
|
|
|
255
367
|
const BUCKET_CACHE = new Map();
|
|
256
368
|
const BUCKET_CACHE_MAX = 4096;
|
|
257
369
|
function tailwindBucket(cls) {
|
|
370
|
+
// Stored values are `string | null`, never `undefined` — a single lookup
|
|
371
|
+
// distinguishes hit from miss.
|
|
258
372
|
const cached = BUCKET_CACHE.get(cls);
|
|
259
|
-
if (cached !== undefined
|
|
373
|
+
if (cached !== undefined)
|
|
260
374
|
return cached;
|
|
261
375
|
// Split off all modifier prefixes (`hover:focus-visible:dark:md:bg-red`).
|
|
262
|
-
const lastColon = cls
|
|
376
|
+
const lastColon = lastTopLevelColon(cls);
|
|
263
377
|
let modifiers = lastColon >= 0 ? cls.slice(0, lastColon + 1) : '';
|
|
264
378
|
let base = lastColon >= 0 ? cls.slice(lastColon + 1) : cls;
|
|
265
379
|
// Strip `!`-important markers — Tailwind v3 leading (`!bg-red`, `hover:!bg-red`)
|
|
@@ -273,23 +387,103 @@ function tailwindBucket(cls) {
|
|
|
273
387
|
if (base.endsWith('!'))
|
|
274
388
|
base = base.slice(0, -1);
|
|
275
389
|
// Negative spacing like `-mx-2` shares its bucket with `mx-2`.
|
|
276
|
-
if (base.startsWith('-'))
|
|
390
|
+
if (base.startsWith('-') && !base.startsWith('-['))
|
|
277
391
|
base = base.slice(1);
|
|
278
392
|
let result = null;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
393
|
+
const arbitraryProp = base.match(ARBITRARY_PROP_PATTERN);
|
|
394
|
+
if (arbitraryProp) {
|
|
395
|
+
const prop = arbitraryProp[1];
|
|
396
|
+
result = modifiers + (ARBITRARY_PROP_ALIAS[prop] ?? `@${prop}`);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
for (const [pattern, key] of BUCKET_PATTERNS) {
|
|
400
|
+
const match = base.match(pattern);
|
|
401
|
+
if (match) {
|
|
402
|
+
const bucketKey = typeof key === 'string' ? key : key(match);
|
|
403
|
+
result = modifiers + bucketKey;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
285
406
|
}
|
|
286
407
|
}
|
|
287
408
|
if (BUCKET_CACHE.size < BUCKET_CACHE_MAX)
|
|
288
409
|
BUCKET_CACHE.set(cls, result);
|
|
289
410
|
return result;
|
|
290
411
|
}
|
|
412
|
+
// Directional shorthand dominance: a LATER class in bucket K also strips
|
|
413
|
+
// earlier classes in DOMINANCE[K]. Longhands never strip their shorthand —
|
|
414
|
+
// a later `pl-2` composes with an earlier `p-4` (Tailwind's own cascade
|
|
415
|
+
// resolves the left side), but a later `p-0` fully replaces earlier `px-4`
|
|
416
|
+
// or `pl-10`, matching what the override author means.
|
|
417
|
+
//
|
|
418
|
+
// Deliberately absent: `text-size` → `line-height`. The library pairs a
|
|
419
|
+
// slot-base `leading-*` with axis-supplied `text-*` across sources by
|
|
420
|
+
// design (labels, timeline meta, table cells) — stripping the leading would
|
|
421
|
+
// silently change typography; leading-* also wins Tailwind's own cascade.
|
|
422
|
+
const DOMINANCE = {
|
|
423
|
+
p: ['px', 'py', 'ps', 'pe', 'pt', 'pr', 'pb', 'pl'],
|
|
424
|
+
px: ['pr', 'pl'],
|
|
425
|
+
py: ['pt', 'pb'],
|
|
426
|
+
m: ['mx', 'my', 'ms', 'me', 'mt', 'mr', 'mb', 'ml'],
|
|
427
|
+
mx: ['mr', 'ml'],
|
|
428
|
+
my: ['mt', 'mb'],
|
|
429
|
+
inset: ['inset-x', 'inset-y', 'start', 'end', 'top', 'right', 'bottom', 'left'],
|
|
430
|
+
'inset-x': ['right', 'left'],
|
|
431
|
+
'inset-y': ['top', 'bottom'],
|
|
432
|
+
size: ['w', 'h'],
|
|
433
|
+
gap: ['gap-x', 'gap-y'],
|
|
434
|
+
rounded: [
|
|
435
|
+
'rounded-tl',
|
|
436
|
+
'rounded-tr',
|
|
437
|
+
'rounded-bl',
|
|
438
|
+
'rounded-br',
|
|
439
|
+
'rounded-ss',
|
|
440
|
+
'rounded-se',
|
|
441
|
+
'rounded-es',
|
|
442
|
+
'rounded-ee',
|
|
443
|
+
'rounded-t',
|
|
444
|
+
'rounded-r',
|
|
445
|
+
'rounded-b',
|
|
446
|
+
'rounded-l',
|
|
447
|
+
'rounded-s',
|
|
448
|
+
'rounded-e'
|
|
449
|
+
],
|
|
450
|
+
'rounded-t': ['rounded-tl', 'rounded-tr'],
|
|
451
|
+
'rounded-r': ['rounded-tr', 'rounded-br'],
|
|
452
|
+
'rounded-b': ['rounded-bl', 'rounded-br'],
|
|
453
|
+
'rounded-l': ['rounded-tl', 'rounded-bl'],
|
|
454
|
+
'rounded-s': ['rounded-ss', 'rounded-es'],
|
|
455
|
+
'rounded-e': ['rounded-se', 'rounded-ee'],
|
|
456
|
+
'border-width': [
|
|
457
|
+
'border-x-width',
|
|
458
|
+
'border-y-width',
|
|
459
|
+
'border-s-width',
|
|
460
|
+
'border-e-width',
|
|
461
|
+
'border-t-width',
|
|
462
|
+
'border-r-width',
|
|
463
|
+
'border-b-width',
|
|
464
|
+
'border-l-width'
|
|
465
|
+
],
|
|
466
|
+
'border-x-width': ['border-r-width', 'border-l-width'],
|
|
467
|
+
'border-y-width': ['border-t-width', 'border-b-width'],
|
|
468
|
+
'border-color': [
|
|
469
|
+
'border-x-color',
|
|
470
|
+
'border-y-color',
|
|
471
|
+
'border-s-color',
|
|
472
|
+
'border-e-color',
|
|
473
|
+
'border-t-color',
|
|
474
|
+
'border-r-color',
|
|
475
|
+
'border-b-color',
|
|
476
|
+
'border-l-color'
|
|
477
|
+
],
|
|
478
|
+
'border-x-color': ['border-r-color', 'border-l-color'],
|
|
479
|
+
'border-y-color': ['border-t-color', 'border-b-color'],
|
|
480
|
+
overflow: ['overflow-x', 'overflow-y'],
|
|
481
|
+
scale: ['scale-x', 'scale-y'],
|
|
482
|
+
translate: ['translate-x', 'translate-y', 'translate-z']
|
|
483
|
+
};
|
|
291
484
|
/**
|
|
292
|
-
* Removes classes from `prior` whose conflict-bucket appears in `current
|
|
485
|
+
* Removes classes from `prior` whose conflict-bucket appears in `current`,
|
|
486
|
+
* including buckets a later shorthand dominates (`p-0` strips `px-4`).
|
|
293
487
|
* Stable order preserved within each side.
|
|
294
488
|
*/
|
|
295
489
|
function stripConflicts(prior, current) {
|
|
@@ -298,8 +492,19 @@ function stripConflicts(prior, current) {
|
|
|
298
492
|
const used = new Set();
|
|
299
493
|
for (const cls of current) {
|
|
300
494
|
const b = tailwindBucket(cls);
|
|
301
|
-
if (b)
|
|
302
|
-
|
|
495
|
+
if (!b)
|
|
496
|
+
continue;
|
|
497
|
+
used.add(b);
|
|
498
|
+
// Dominance expansion keeps the modifier prefix: `hover:p` dominates
|
|
499
|
+
// `hover:px`, never the plain-state `px`. Bucket keys contain no `:`,
|
|
500
|
+
// so the last colon is the modifier boundary.
|
|
501
|
+
const keyStart = b.lastIndexOf(':') + 1;
|
|
502
|
+
const dominated = DOMINANCE[b.slice(keyStart)];
|
|
503
|
+
if (dominated) {
|
|
504
|
+
const mods = b.slice(0, keyStart);
|
|
505
|
+
for (const d of dominated)
|
|
506
|
+
used.add(mods + d);
|
|
507
|
+
}
|
|
303
508
|
}
|
|
304
509
|
if (used.size === 0)
|
|
305
510
|
return prior;
|
|
@@ -308,6 +513,25 @@ function stripConflicts(prior, current) {
|
|
|
308
513
|
return b == null || !used.has(b);
|
|
309
514
|
});
|
|
310
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Folds an ordered list of class-token sources into one list: each source
|
|
518
|
+
* strips the accumulated earlier sources' conflicting buckets, then appends
|
|
519
|
+
* its own tokens. The last source therefore wins per bucket — deterministic,
|
|
520
|
+
* independent of stylesheet order.
|
|
521
|
+
*
|
|
522
|
+
* Within a single source, order is preserved and same-bucket pairs fall
|
|
523
|
+
* through to the CSS cascade — author-controlled pairings inside one class
|
|
524
|
+
* string (`rounded-md rounded-t-none`) stay intact.
|
|
525
|
+
*/
|
|
526
|
+
function foldSources(sources) {
|
|
527
|
+
let acc = [];
|
|
528
|
+
for (const tokens of sources) {
|
|
529
|
+
if (tokens.length === 0)
|
|
530
|
+
continue;
|
|
531
|
+
acc = acc.length === 0 ? tokens : [...stripConflicts(acc, tokens), ...tokens];
|
|
532
|
+
}
|
|
533
|
+
return acc;
|
|
534
|
+
}
|
|
311
535
|
/**
|
|
312
536
|
* Tokenize a class-string into individual class tokens. Whitespace-only
|
|
313
537
|
* fragments and empty strings are filtered.
|
|
@@ -364,7 +588,7 @@ function stripUndefined(obj) {
|
|
|
364
588
|
}
|
|
365
589
|
export function matchesCompound(compound, effectiveProps) {
|
|
366
590
|
for (const key of Object.keys(compound)) {
|
|
367
|
-
if (key === 'class'
|
|
591
|
+
if (key === 'class')
|
|
368
592
|
continue;
|
|
369
593
|
const constraint = compound[key];
|
|
370
594
|
const propValue = falsyToString(effectiveProps[key]);
|
|
@@ -388,111 +612,188 @@ function resolveClassValue(value) {
|
|
|
388
612
|
return cx(...value);
|
|
389
613
|
return '';
|
|
390
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* Config-time validation — every violation here is a programming error in a
|
|
617
|
+
* `*.variants.ts` (or a consumer's own tv() call), so it throws immediately
|
|
618
|
+
* at module init instead of degrading silently at render time. The compile-
|
|
619
|
+
* time layer (ValidSlotVariants, VariantPropsMap) catches the same class of
|
|
620
|
+
* mistakes for literal configs; this covers JS consumers and values built
|
|
621
|
+
* from imported constants.
|
|
622
|
+
*/
|
|
623
|
+
function validateTvConfig(config) {
|
|
624
|
+
const { base, slots, variants = {}, compoundVariants = [], defaultVariants = {} } = config;
|
|
625
|
+
const slotNames = slots != null && Object.keys(slots).length > 0 ? Object.keys(slots) : null;
|
|
626
|
+
if (slotNames && base != null) {
|
|
627
|
+
// Fail loud instead of the historical silent drop (base only ever
|
|
628
|
+
// reached a slot literally named 'base').
|
|
629
|
+
throw new Error('tv(): `base` and `slots` are mutually exclusive — declare the primary slot as `slots.base` instead.');
|
|
630
|
+
}
|
|
631
|
+
const requireBaseSlot = (context) => {
|
|
632
|
+
if (slotNames && !slotNames.includes('base')) {
|
|
633
|
+
throw new Error(`tv(): ${context} is a plain class value, which routes to the 'base' slot — but no slot named 'base' is declared.`);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
// Leaf values must bottom out in class strings (nested arrays allowed,
|
|
637
|
+
// falsy entries tolerated for conditional-array patterns). Objects inside
|
|
638
|
+
// arrays and scalar garbage would silently render as '' — or worse, leak
|
|
639
|
+
// through cx()'s record form as literal slot-name classes — so they throw.
|
|
640
|
+
const checkClassLeaf = (value, context) => {
|
|
641
|
+
if (value == null || value === false)
|
|
642
|
+
return;
|
|
643
|
+
if (typeof value === 'string')
|
|
644
|
+
return;
|
|
645
|
+
if (Array.isArray(value)) {
|
|
646
|
+
for (const [i, v] of value.entries()) {
|
|
647
|
+
checkClassLeaf(v, `${context}[${i}]`);
|
|
648
|
+
}
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
throw new Error(`tv(): ${context} must be a class string (or nested array of strings), got ${typeof value}.`);
|
|
652
|
+
};
|
|
653
|
+
const checkSlotKeys = (value, context) => {
|
|
654
|
+
if (value == null)
|
|
655
|
+
return;
|
|
656
|
+
if (typeof value === 'string' || Array.isArray(value)) {
|
|
657
|
+
if (slotNames)
|
|
658
|
+
requireBaseSlot(context);
|
|
659
|
+
checkClassLeaf(value, context);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (typeof value === 'object') {
|
|
663
|
+
if (!slotNames) {
|
|
664
|
+
throw new Error(`tv(): ${context} is a slot map, but this tv() declares no slots — use a plain class string/array.`);
|
|
665
|
+
}
|
|
666
|
+
for (const key of Object.keys(value)) {
|
|
667
|
+
if (!slotNames.includes(key)) {
|
|
668
|
+
throw new Error(`tv(): ${context} targets unknown slot '${key}' (declared slots: ${slotNames.join(', ')}).`);
|
|
669
|
+
}
|
|
670
|
+
checkClassLeaf(value[key], `${context}.${key}`);
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
throw new Error(`tv(): ${context} must be a class string, array or slot map, got ${typeof value}.`);
|
|
675
|
+
};
|
|
676
|
+
for (const [axis, values] of Object.entries(variants)) {
|
|
677
|
+
for (const [valueName, value] of Object.entries(values)) {
|
|
678
|
+
checkSlotKeys(value, `variants.${axis}.${valueName}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
for (const [i, cv] of compoundVariants.entries()) {
|
|
682
|
+
for (const key of Object.keys(cv)) {
|
|
683
|
+
if (key === 'class')
|
|
684
|
+
continue;
|
|
685
|
+
const axis = variants[key];
|
|
686
|
+
if (axis == null) {
|
|
687
|
+
throw new Error(`tv(): compoundVariants[${i}] references unknown variant axis '${key}'.`);
|
|
688
|
+
}
|
|
689
|
+
const constraint = cv[key];
|
|
690
|
+
const values = Array.isArray(constraint) ? constraint : [constraint];
|
|
691
|
+
const booleanAxis = 'true' in axis || 'false' in axis;
|
|
692
|
+
for (const v of values) {
|
|
693
|
+
const normalized = falsyToString(v);
|
|
694
|
+
// 'true'/'false' may be matched even when only one of them is
|
|
695
|
+
// declared — half-declared boolean axes are idiomatic
|
|
696
|
+
// (`loading: { true: … }` + a compound on `loading: false`). The
|
|
697
|
+
// escape only applies to boolean-ish axes; `size: true` on a string
|
|
698
|
+
// axis is a config error.
|
|
699
|
+
if ((normalized === 'true' || normalized === 'false') && booleanAxis)
|
|
700
|
+
continue;
|
|
701
|
+
if (normalized == null || !(normalized in axis)) {
|
|
702
|
+
throw new Error(`tv(): compoundVariants[${i}] matches '${key}: ${String(v)}', but axis '${key}' declares no such value (values: ${Object.keys(axis).join(', ')}).`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
checkSlotKeys(cv.class, `compoundVariants[${i}].class`);
|
|
707
|
+
}
|
|
708
|
+
for (const [key, value] of Object.entries(defaultVariants)) {
|
|
709
|
+
const axis = variants[key];
|
|
710
|
+
if (axis == null) {
|
|
711
|
+
throw new Error(`tv(): defaultVariants references unknown variant axis '${key}'.`);
|
|
712
|
+
}
|
|
713
|
+
const normalized = falsyToString(value);
|
|
714
|
+
const booleanAxis = 'true' in axis || 'false' in axis;
|
|
715
|
+
if ((normalized === 'true' || normalized === 'false') && booleanAxis)
|
|
716
|
+
continue;
|
|
717
|
+
if (normalized == null || !(normalized in axis)) {
|
|
718
|
+
throw new Error(`tv(): defaultVariants.${key} = '${String(value)}' is not a declared value of axis '${key}' (values: ${Object.keys(axis).join(', ')}).`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
391
722
|
// Implementation
|
|
392
723
|
export function tv(config) {
|
|
724
|
+
validateTvConfig(config);
|
|
393
725
|
const { base, slots, variants = {}, compoundVariants = [], defaultVariants = {} } = config;
|
|
394
726
|
const variantEntries = Object.entries(variants);
|
|
395
727
|
const hasSlots = slots != null && Object.keys(slots).length > 0;
|
|
728
|
+
/**
|
|
729
|
+
* Shared pipeline: first source → each variant axis in declaration order →
|
|
730
|
+
* each matching compoundVariant in array order → call-site class override.
|
|
731
|
+
* `foldSources` lets every later source strip earlier sources' conflicting
|
|
732
|
+
* buckets, so axis and compound order are semantic: declare the axis that
|
|
733
|
+
* must win a shared bucket later. See "tv() engine — explicit trade-offs"
|
|
734
|
+
* in ARCHITECTURE.md. `pickValue` routes config values: identity in no-slot
|
|
735
|
+
* mode; per-slot lookup (slot maps by name, plain strings to the 'base'
|
|
736
|
+
* slot) in slot mode.
|
|
737
|
+
*/
|
|
738
|
+
function foldFor(firstSource, effective, pickValue, overrideTokens) {
|
|
739
|
+
const sources = [firstSource];
|
|
740
|
+
for (const [vName, vMap] of variantEntries) {
|
|
741
|
+
const key = falsyToString(effective[vName]);
|
|
742
|
+
if (key == null || !(key in vMap))
|
|
743
|
+
continue;
|
|
744
|
+
sources.push(tokenize(resolveClassValue(pickValue(vMap[key]))));
|
|
745
|
+
}
|
|
746
|
+
for (const cv of compoundVariants) {
|
|
747
|
+
if (!matchesCompound(cv, effective))
|
|
748
|
+
continue;
|
|
749
|
+
sources.push(tokenize(resolveClassValue(pickValue(cv.class))));
|
|
750
|
+
}
|
|
751
|
+
sources.push(overrideTokens);
|
|
752
|
+
return foldSources(sources).join(' ');
|
|
753
|
+
}
|
|
396
754
|
if (!hasSlots) {
|
|
397
|
-
|
|
755
|
+
const identity = (value) => value;
|
|
756
|
+
const baseTokens = tokenize(resolveClassValue(base));
|
|
757
|
+
const resolve = function resolve(props) {
|
|
398
758
|
const effective = { ...defaultVariants, ...stripUndefined(props || {}) };
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const baseTokens = tokenize(resolveClassValue(base));
|
|
402
|
-
const variantTokens = [];
|
|
403
|
-
for (const [vName, vMap] of variantEntries) {
|
|
404
|
-
const key = falsyToString(effective[vName]);
|
|
405
|
-
if (key != null && key in vMap) {
|
|
406
|
-
variantTokens.push(...tokenize(resolveClassValue(vMap[key])));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
const compoundTokens = [];
|
|
410
|
-
for (const cv of compoundVariants) {
|
|
411
|
-
if (matchesCompound(cv, effective)) {
|
|
412
|
-
compoundTokens.push(...tokenize(resolveClassValue(cv.class ?? cv.className)));
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const overrideTokens = [];
|
|
416
|
-
if (props?.class)
|
|
417
|
-
overrideTokens.push(...tokenize(resolveClassValue(props.class)));
|
|
418
|
-
if (props?.className)
|
|
419
|
-
overrideTokens.push(...tokenize(resolveClassValue(props.className)));
|
|
420
|
-
// Strip in pipeline order: variants strip base, compounds strip
|
|
421
|
-
// (base+variants), overrides strip all prior. Each stage's own classes
|
|
422
|
-
// remain untouched.
|
|
423
|
-
const afterVariants = [...stripConflicts(baseTokens, variantTokens), ...variantTokens];
|
|
424
|
-
const afterCompounds = [...stripConflicts(afterVariants, compoundTokens), ...compoundTokens];
|
|
425
|
-
const final = [...stripConflicts(afterCompounds, overrideTokens), ...overrideTokens];
|
|
426
|
-
return final.join(' ');
|
|
759
|
+
const overrideTokens = props?.class ? tokenize(cx(props.class)) : [];
|
|
760
|
+
return foldFor(baseTokens, effective, identity, overrideTokens);
|
|
427
761
|
};
|
|
762
|
+
// Introspection hook for tooling (variants linter, docs-gen) — the
|
|
763
|
+
// config is static module data, exposing it costs nothing.
|
|
764
|
+
Object.defineProperty(resolve, 'config', { value: config, enumerable: false });
|
|
765
|
+
return resolve;
|
|
428
766
|
}
|
|
429
|
-
// Slot mode
|
|
430
|
-
const
|
|
431
|
-
|
|
767
|
+
// Slot mode — `slots` is narrowed to non-null by `hasSlots`.
|
|
768
|
+
const slotNames = Object.keys(slots);
|
|
769
|
+
const slotBases = new Map();
|
|
770
|
+
for (const slotName of slotNames) {
|
|
771
|
+
slotBases.set(slotName, tokenize(resolveClassValue(slots[slotName])));
|
|
772
|
+
}
|
|
773
|
+
const pickerFor = (slotName) => (value) => {
|
|
774
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
775
|
+
return value[slotName];
|
|
776
|
+
}
|
|
777
|
+
// Plain string/array config values route to the 'base' slot only.
|
|
778
|
+
return slotName === 'base' ? value : undefined;
|
|
779
|
+
};
|
|
780
|
+
const resolve = function resolve(props) {
|
|
781
|
+
if (import.meta.env?.DEV && props != null && props.class != null) {
|
|
782
|
+
console.warn(`tv(): a top-level \`class\` is ignored in slot mode (slots: ${slotNames.join(', ')}) — pass it to the slot function instead, e.g. styles.base({ class }).`);
|
|
783
|
+
}
|
|
432
784
|
const topProps = { ...defaultVariants, ...stripUndefined(props || {}) };
|
|
433
785
|
const result = {};
|
|
434
|
-
for (const slotName of
|
|
786
|
+
for (const slotName of slotNames) {
|
|
787
|
+
const pick = pickerFor(slotName);
|
|
788
|
+
const slotBase = slotBases.get(slotName);
|
|
435
789
|
result[slotName] = function slotFn(slotProps) {
|
|
436
790
|
const effective = { ...topProps, ...stripUndefined(slotProps || {}) };
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// `'base'`. If a component renames its primary slot (e.g. to
|
|
440
|
-
// `wrapper`), `config.base` is silently dropped — slots and
|
|
441
|
-
// config.base must agree on the name.
|
|
442
|
-
const baseTokens = [];
|
|
443
|
-
if (slotName === 'base' && base) {
|
|
444
|
-
baseTokens.push(...tokenize(resolveClassValue(base)));
|
|
445
|
-
}
|
|
446
|
-
baseTokens.push(...tokenize(resolveClassValue(slots[slotName])));
|
|
447
|
-
// 2. Per-slot variant resolution
|
|
448
|
-
const variantTokens = [];
|
|
449
|
-
for (const [vName, vMap] of variantEntries) {
|
|
450
|
-
const key = falsyToString(effective[vName]);
|
|
451
|
-
if (key == null || !(key in vMap))
|
|
452
|
-
continue;
|
|
453
|
-
const vValue = vMap[key];
|
|
454
|
-
if (vValue != null && typeof vValue === 'object' && !Array.isArray(vValue)) {
|
|
455
|
-
const obj = vValue;
|
|
456
|
-
if (slotName in obj) {
|
|
457
|
-
variantTokens.push(...tokenize(resolveClassValue(obj[slotName])));
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
else if (slotName === 'base') {
|
|
461
|
-
variantTokens.push(...tokenize(resolveClassValue(vValue)));
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// 3. Compound variant resolution (per-slot)
|
|
465
|
-
const compoundTokens = [];
|
|
466
|
-
for (const cv of compoundVariants) {
|
|
467
|
-
if (!matchesCompound(cv, effective))
|
|
468
|
-
continue;
|
|
469
|
-
const cvClass = cv.class ?? cv.className;
|
|
470
|
-
if (cvClass != null && typeof cvClass === 'object' && !Array.isArray(cvClass)) {
|
|
471
|
-
const obj = cvClass;
|
|
472
|
-
if (slotName in obj) {
|
|
473
|
-
compoundTokens.push(...tokenize(resolveClassValue(obj[slotName])));
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
else if (slotName === 'base') {
|
|
477
|
-
compoundTokens.push(...tokenize(resolveClassValue(cvClass)));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
// 4. Class override from slotProps
|
|
481
|
-
const overrideTokens = [];
|
|
482
|
-
if (slotProps?.class)
|
|
483
|
-
overrideTokens.push(...tokenize(resolveClassValue(slotProps.class)));
|
|
484
|
-
if (slotProps?.className)
|
|
485
|
-
overrideTokens.push(...tokenize(resolveClassValue(slotProps.className)));
|
|
486
|
-
// Pipeline strip: later stage wins over earlier stage's conflicts.
|
|
487
|
-
const afterVariants = [...stripConflicts(baseTokens, variantTokens), ...variantTokens];
|
|
488
|
-
const afterCompounds = [
|
|
489
|
-
...stripConflicts(afterVariants, compoundTokens),
|
|
490
|
-
...compoundTokens
|
|
491
|
-
];
|
|
492
|
-
const final = [...stripConflicts(afterCompounds, overrideTokens), ...overrideTokens];
|
|
493
|
-
return final.join(' ');
|
|
791
|
+
const overrideTokens = slotProps?.class ? tokenize(cx(slotProps.class)) : [];
|
|
792
|
+
return foldFor(slotBase, effective, pick, overrideTokens);
|
|
494
793
|
};
|
|
495
794
|
}
|
|
496
795
|
return result;
|
|
497
796
|
};
|
|
797
|
+
Object.defineProperty(resolve, 'config', { value: config, enumerable: false });
|
|
798
|
+
return resolve;
|
|
498
799
|
}
|