slot-variants 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -7
- package/SKILL.md +68 -396
- package/dist/eslint-plugin.cjs +112 -50
- package/dist/eslint-plugin.js +112 -50
- package/dist/index.cjs +105 -5
- package/dist/index.d.ts +32 -21
- package/dist/index.js +105 -5
- package/package.json +2 -2
- package/dist/src/cn.d.ts +0 -14
- package/dist/src/eslint-plugin.d.ts +0 -25
- package/dist/src/index.d.ts +0 -2
- package/dist/src/sv.d.ts +0 -189
- package/dist/test/cn.test.d.ts +0 -1
- package/dist/test/eslint-plugin.test.d.ts +0 -1
- package/dist/test/sv.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -384,6 +384,21 @@ button({ intent: 'primary', size: 'lg' }); // OK
|
|
|
384
384
|
button({ size: 'lg' }); // Throws: Missing required variant: "intent"
|
|
385
385
|
```
|
|
386
386
|
|
|
387
|
+
Pass `true` to make every variant required, or `false` to require none:
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const button = sv('btn', {
|
|
391
|
+
variants: {
|
|
392
|
+
size: { sm: 'text-sm', lg: 'text-lg' },
|
|
393
|
+
intent: { primary: 'bg-blue-500', danger: 'bg-red-500' }
|
|
394
|
+
},
|
|
395
|
+
requiredVariants: true
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
button({ size: 'sm', intent: 'primary' }); // OK
|
|
399
|
+
button({ size: 'sm' }); // Throws: Missing required variant: "intent"
|
|
400
|
+
```
|
|
401
|
+
|
|
387
402
|
### Presets
|
|
388
403
|
|
|
389
404
|
Presets are predefined named combinations of variant values. Use them to create reusable variant shortcuts:
|
|
@@ -597,6 +612,58 @@ compoundSlots: [
|
|
|
597
612
|
]
|
|
598
613
|
```
|
|
599
614
|
|
|
615
|
+
### Multi Slots
|
|
616
|
+
|
|
617
|
+
By default each slot in the result object is a plain class string. The
|
|
618
|
+
`multiSlots` option turns the listed slots into reconfigurable functions
|
|
619
|
+
instead. A slot function accepts variant prop overrides and a
|
|
620
|
+
`class`/`className` override, and returns that slot's class string.
|
|
621
|
+
|
|
622
|
+
This is designed for cases where a single slot is rendered multiple times
|
|
623
|
+
with different props — for example a list of items where each item needs
|
|
624
|
+
its own variant values — so the same slot can be re-evaluated per use
|
|
625
|
+
without recreating the whole variant function:
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
const card = sv('border', {
|
|
629
|
+
slots: {
|
|
630
|
+
header: 'font-bold',
|
|
631
|
+
body: 'py-4'
|
|
632
|
+
},
|
|
633
|
+
variants: {
|
|
634
|
+
size: {
|
|
635
|
+
sm: { base: 'p-2', header: 'text-sm' },
|
|
636
|
+
lg: { base: 'p-6', header: 'text-lg' }
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
multiSlots: ['header']
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const result = card({ size: 'sm' });
|
|
643
|
+
// result.base -> 'border p-2' (plain string)
|
|
644
|
+
// result.body -> 'py-4' (plain string)
|
|
645
|
+
// result.header -> function
|
|
646
|
+
|
|
647
|
+
result.header(); // 'font-bold text-sm'
|
|
648
|
+
result.header({ size: 'lg' }); // 'font-bold text-lg'
|
|
649
|
+
result.header({ class: 'mt-2' }); // 'font-bold text-sm mt-2'
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Slots not listed in `multiSlots` stay plain strings. Pass `true` to make
|
|
653
|
+
every slot a function, or `false` (the default) to keep them all strings:
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
const card = sv('border', {
|
|
657
|
+
slots: { header: 'font-bold', body: 'py-4' },
|
|
658
|
+
multiSlots: true
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const { base, header, body } = card();
|
|
662
|
+
base(); // 'border'
|
|
663
|
+
header(); // 'font-bold'
|
|
664
|
+
body(); // 'py-4'
|
|
665
|
+
```
|
|
666
|
+
|
|
600
667
|
### Class Override at Runtime
|
|
601
668
|
|
|
602
669
|
Append additional classes at call time using `class` or `className`:
|
|
@@ -670,6 +737,16 @@ The `postProcess` function is applied to each slot's final class string independ
|
|
|
670
737
|
|
|
671
738
|
Results are cached automatically for performance. The default cache size is **256** entries.
|
|
672
739
|
|
|
740
|
+
Each cache entry corresponds to one distinct combination of resolved variant values. The largest number of combinations a config can produce is the product, over every variant, of its value count plus one — the `+ 1` counts the variant being left unset:
|
|
741
|
+
|
|
742
|
+
```
|
|
743
|
+
maxEntries = (values₁ + 1) × (values₂ + 1) × ... × (valuesₙ + 1)
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
For example, four variants with three values each yield `(3 + 1) ** 4 = 256` combinations — exactly the default. A config whose `maxEntries` is at or below its `cacheSize` never evicts, so raising `cacheSize` past that point has no effect.
|
|
747
|
+
|
|
748
|
+
The `+ 1` is dropped for any variant that cannot actually be left unset — one listed in `requiredVariants` (or all of them when `requiredVariants` is `true`), or one with a static `defaultVariants` value that always fills it in. A function-based default keeps the `+ 1`, since it may return `undefined`. The `getMaxEntries()` introspection method computes this exact count for a given config — see [Introspection](#introspection).
|
|
749
|
+
|
|
673
750
|
```typescript
|
|
674
751
|
const button = sv('btn', {
|
|
675
752
|
variants: {
|
|
@@ -719,10 +796,12 @@ button.slotKeys; // ['base', 'icon']
|
|
|
719
796
|
button.slots; // { icon: 'w-4 h-4' }
|
|
720
797
|
button.defaultVariants; // { size: 'sm' }
|
|
721
798
|
button.requiredVariants; // ['intent']
|
|
799
|
+
button.multiSlots; // [] (slot names exposed as functions)
|
|
722
800
|
button.presetKeys; // ['cta']
|
|
723
801
|
button.presets; // { cta: { size: 'lg', intent: 'primary' } }
|
|
724
802
|
button.getVariantValues('size'); // ['sm', 'lg']
|
|
725
803
|
button.getVariantValues('intent'); // ['primary', 'danger']
|
|
804
|
+
button.getMaxEntries(); // 4 — distinct variant combinations
|
|
726
805
|
button.getCacheSize(); // current number of cached entries
|
|
727
806
|
button.clearCache(); // clear all cached entries
|
|
728
807
|
```
|
|
@@ -884,7 +963,8 @@ Class values inside the config (`base`, `variants`, `slots`, and `compound*` `cl
|
|
|
884
963
|
| `compoundVariants` | `Array` | Additional classes applied when multiple variant conditions match |
|
|
885
964
|
| `compoundSlots` | `Array` | Classes applied to multiple slots based on variant conditions |
|
|
886
965
|
| `defaultVariants` | `Object` | Default values for variants (static values or functions) |
|
|
887
|
-
| `requiredVariants` | `string[]` | Variant names that must be provided at call time |
|
|
966
|
+
| `requiredVariants` | `string[] \| boolean` | Variant names that must be provided at call time; `true` makes every variant required, `false` none |
|
|
967
|
+
| `multiSlots` | `string[] \| boolean` | Slot names exposed as reconfigurable functions instead of strings; `true` makes every slot a function, `false` none |
|
|
888
968
|
| `presets` | `Record<string, Partial<VariantProps>>` | Named combinations of variant values selectable via `preset` prop |
|
|
889
969
|
| `postProcess` | `(className: string) => string` | Custom transformation applied to final class strings |
|
|
890
970
|
| `cacheSize` | `number` | Maximum number of cached results (default: `256`) |
|
|
@@ -896,15 +976,15 @@ Class values inside the config (`base`, `variants`, `slots`, and `compound*` `cl
|
|
|
896
976
|
|
|
897
977
|
### Rules
|
|
898
978
|
|
|
899
|
-
- **`slot-variants/no-conflicting-classes`** — flags class tokens that collide within the output of an `sv()` or `cn()` call: both exact-duplicate tokens that will appear more than once and distinct tokens that target the same Tailwind-style utility namespace (e.g. `w-100` and `w-200`). For `sv()`, detects collisions within `base`, across different variant keys, inside compound variants and compound slots, between `base` and a variant value, and within a single literal. For `cn()` (and the cn-style calling convention of `sv()` without a config), flags collisions across args, inside arrays, template literals, or within a single literal. Tokens with different variant prefixes (`w-100` vs `hover:w-200`) don't conflict,
|
|
979
|
+
- **`slot-variants/no-conflicting-classes`** — flags class tokens that collide within the output of an `sv()` or `cn()` call: both exact-duplicate tokens that will appear more than once and distinct tokens that target the same Tailwind-style utility namespace (e.g. `w-100` and `w-200`). For `sv()`, detects collisions within `base`, across different variant keys, inside compound variants and compound slots, between `base` and a variant value, and within a single literal. For `cn()` (and the cn-style calling convention of `sv()` without a config), flags collisions across args, inside arrays, template literals, or within a single literal. Tokens with different variant prefixes (`w-100` vs `hover:w-200`) don't conflict, a leading or trailing `!` important marker (`!w-100`, `w-100!`) is ignored when computing the namespace, and tokens that only co-occur across mutually-exclusive variant values are skipped.
|
|
900
980
|
|
|
901
|
-
- **`slot-variants/no-dynamic-classes`** — flags class-bearing positions in `sv()` and `cn()` calls that aren't statically inferrable. Only string literals, template literals without expressions, flat arrays of those in config, and explicit `undefined` config class values are accepted, and config objects must use static keys (no spreads, no computed keys). Identifiers, member access, calls, spreads, non-string literals, templates with expressions, nested config arrays, and runtime conditional records are reported. Non-class-bearing config keys (`defaultVariants`, `presets`, `requiredVariants`, `cacheSize`, `postProcess`, `introspection`) are not validated, and runtime variant matchers inside compound entries are left alone — only the `class`/`className` value (and the `slots` array of `compoundSlots`) is checked.
|
|
981
|
+
- **`slot-variants/no-dynamic-classes`** — flags class-bearing positions in `sv()` and `cn()` calls that aren't statically inferrable. Only string literals, template literals without expressions, flat arrays of those in config, and explicit `undefined` config class values are accepted, and config objects must use static keys (no spreads, no computed keys). Identifiers, member access, calls, spreads, non-string literals, templates with expressions, nested config arrays, and runtime conditional records are reported. Non-class-bearing config keys (`defaultVariants`, `presets`, `requiredVariants`, `multiSlots`, `cacheSize`, `postProcess`, `introspection`) are not validated, and runtime variant matchers inside compound entries are left alone — only the `class`/`className` value (and the `slots` array of `compoundSlots`) is checked.
|
|
902
982
|
|
|
903
|
-
- **`slot-variants/no-empty-classes`** — flags empty class values — empty strings, empty arrays, and empty objects — at any class-bearing position reachable from an `sv()` or `cn()` call, plus zero-argument `sv()` / `cn()` calls themselves (which always produce an empty class string). Reports empties in positional arguments (and inside arrays nested in those), in `base`, in variants including slot-keyed variant branches, in the `class`/`className` of `compoundVariants` and `compoundSlots` entries, and in the top-level `slots`, `variants`, `compoundVariants`, and `compoundSlots` containers themselves. Inside an `sv()` config, an empty string is still allowed as a direct `slots[key]` value — declaring a slot with no default classes is a valid use case.
|
|
983
|
+
- **`slot-variants/no-empty-classes`** — flags empty class values — empty strings, empty arrays, and empty objects — at any class-bearing position reachable from an `sv()` or `cn()` call, plus zero-argument `sv()` / `cn()` calls themselves (which always produce an empty class string). Reports empties in positional arguments (and inside arrays nested in those), in `base`, in variants including slot-keyed variant branches, in the `class`/`className` of `compoundVariants` and `compoundSlots` entries, and in the top-level `slots`, `variants`, `compoundVariants`, and `compoundSlots` containers themselves. Inside an `sv()` config, an empty string is still allowed as a direct `slots[key]` value — declaring a slot with no default classes is a valid use case. **Partially auto-fixable**: `eslint --fix` removes an empty positional argument, an empty class-array element, or an empty top-level config property (`base`, `slots`, `variants`, `compoundVariants`, `compoundSlots`), along with its comma, when other items remain in that list or config; empties at other positions are reported without a fix.
|
|
904
984
|
|
|
905
985
|
- **`slot-variants/no-redundant-spaces`** — flags class strings whose whitespace isn't canonical. Inside a class string, whitespace is canonical only as a single ASCII space between two non-whitespace tokens, so leading or trailing whitespace, repeated spaces, and non-space whitespace (tabs, newlines, etc.) are reported. The rule walks every string and expressionless template literal reachable from a call's arguments — including values nested inside arrays and objects — and bails silently on dynamic expressions. **Auto-fixable**: `eslint --fix` rewrites each offending literal in place, preserving its original quote style.
|
|
906
986
|
|
|
907
|
-
- **`slot-variants/no-shared-tokens`** — flags class tokens that appear in every value of an exhaustively-covered variant, where “exhaustive” means the variant has a statically defined default value
|
|
987
|
+
- **`slot-variants/no-shared-tokens`** — flags class tokens that appear in every value of an exhaustively-covered variant, where “exhaustive” means the variant has a statically defined default value, is listed in `requiredVariants`, or `requiredVariants` is `true` (every variant required). Those tokens are constant in the rendered output, so they belong in `base` or the corresponding `slots[slot]` entry rather than being repeated in every variant value. The rule only analyzes `sv()` calls with a config, compares tokens per-slot, skips non-exhaustive variants, single-value variants, boolean shorthand, undefined or dynamic defaults, and dynamic or partially-analyzable variant value records, and reports every repeated occurrence that should be lifted out.
|
|
908
988
|
|
|
909
989
|
Only calls where `sv` or `cn` is a named import from `'slot-variants'` are analyzed. `no-conflicting-classes` skips dynamic inputs silently to avoid false positives; `no-dynamic-classes` is the opposite — it flags exactly those positions so the static analyzer can fully reason about every call. `no-shared-tokens` sits between them: it needs a fully statically analyzable, exhaustive variant before it can prove a token is constant across every value. `no-empty-classes` and `no-redundant-spaces` are independent and complement the structural rules: they cover empty and badly-shaped literals reachable from a call's arguments, regardless of whether the surrounding call is fully static.
|
|
910
990
|
|
|
@@ -1111,11 +1191,11 @@ Key differences to be aware of:
|
|
|
1111
1191
|
|
|
1112
1192
|
| Feature | tailwind-variants | slot-variants |
|
|
1113
1193
|
| --- | --- | --- |
|
|
1114
|
-
| Slot return type |
|
|
1194
|
+
| Slot return type | Always functions: `slot({ class: '...' })` | Strings by default; functions for slots listed in `multiSlots` |
|
|
1115
1195
|
| `extend` (composition) | Supported | Not supported |
|
|
1116
1196
|
| Built-in `twMerge` | Enabled by default | Use `postProcess: twMerge` |
|
|
1117
1197
|
|
|
1118
|
-
**Slot return type** is the most significant difference. In `tv()`, each slot returns a function that can accept additional props. In `sv()`, slots resolve to strings directly — use the `class` prop with a slot object for per-slot overrides:
|
|
1198
|
+
**Slot return type** is the most significant difference. In `tv()`, each slot returns a function that can accept additional props. In `sv()`, slots resolve to strings directly — use the `class` prop with a slot object for per-slot overrides, or list a slot in [`multiSlots`](#multi-slots) to expose it as a `tv`-style reconfigurable function:
|
|
1119
1199
|
|
|
1120
1200
|
```typescript
|
|
1121
1201
|
// tailwind-variants
|