Skip to content

Commit 1070df8

Browse files
committed
Add peer variant
1 parent 05d26a5 commit 1070df8

File tree

4 files changed

+245
-19
lines changed

4 files changed

+245
-19
lines changed

src/jit/corePlugins.js

+31-19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as corePlugins from '../plugins'
33
import buildMediaQuery from '../util/buildMediaQuery'
44
import prefixSelector from '../util/prefixSelector'
55
import {
6+
applyPseudoToMarker,
67
updateLastClasses,
78
updateAllClasses,
89
transformAllSelectors,
@@ -156,7 +157,7 @@ export default {
156157
)
157158
}
158159

159-
let baseGroupSelector = prefixSelector(config('prefix'), '.group')
160+
let groupMarker = prefixSelector(config('prefix'), '.group')
160161
for (let variant of pseudoVariants) {
161162
let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant]
162163
let groupVariantName = `group-${variantName}`
@@ -165,36 +166,47 @@ export default {
165166
groupVariantName,
166167
transformAllSelectors((selector) => {
167168
let variantSelector = updateAllClasses(selector, (className) => {
168-
if (`.${className}` === baseGroupSelector) return className
169+
if (`.${className}` === groupMarker) return className
169170
return `${groupVariantName}${config('separator')}${className}`
170171
})
171172

172173
if (variantSelector === selector) {
173174
return null
174175
}
175176

176-
let states = [state]
177+
return applyPseudoToMarker(
178+
variantSelector,
179+
groupMarker,
180+
state,
181+
(marker, selector) => `${marker} ${selector}`
182+
)
183+
})
184+
)
185+
}
177186

178-
// Stack group variants
179-
let baseGroupIdx = variantSelector.indexOf(baseGroupSelector + ':')
180-
if (baseGroupIdx !== -1) {
181-
let groupClassName = variantSelector.slice(
182-
baseGroupIdx,
183-
variantSelector.indexOf(' ', baseGroupIdx)
184-
)
187+
let peerMarker = prefixSelector(config('prefix'), '.peer')
188+
for (let variant of pseudoVariants) {
189+
let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant]
190+
let peerVariantName = `peer-${variantName}`
185191

186-
// Pick all the states
187-
states = states.concat(
188-
variantSelector
189-
.slice(baseGroupIdx + baseGroupSelector.length + 1, groupClassName.length)
190-
.split(':')
191-
)
192+
addVariant(
193+
peerVariantName,
194+
transformAllSelectors((selector) => {
195+
let variantSelector = updateAllClasses(selector, (className) => {
196+
if (`.${className}` === peerMarker) return className
197+
return `${peerVariantName}${config('separator')}${className}`
198+
})
192199

193-
// Remove the base `.group:...`
194-
variantSelector = variantSelector.replace(groupClassName, '')
200+
if (variantSelector === selector) {
201+
return null
195202
}
196203

197-
return `${[baseGroupSelector, ...states].join(':')} ${variantSelector}`
204+
return applyPseudoToMarker(
205+
variantSelector,
206+
peerMarker,
207+
state,
208+
(marker, selector) => `${marker} ~ ${selector}`
209+
)
198210
})
199211
)
200212
}

src/util/pluginUtils.js

+18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,24 @@ import createColor from 'color'
44
import escapeCommas from './escapeCommas'
55
import { withAlphaValue } from './withAlphaVariable'
66

7+
export function applyPseudoToMarker(selector, marker, state, join) {
8+
let states = [state]
9+
10+
let markerIdx = selector.indexOf(marker + ':')
11+
12+
if (markerIdx !== -1) {
13+
let existingMarker = selector.slice(markerIdx, selector.indexOf(' ', markerIdx))
14+
15+
states = states.concat(
16+
selector.slice(markerIdx + marker.length + 1, existingMarker.length).split(':')
17+
)
18+
19+
selector = selector.replace(existingMarker, '')
20+
}
21+
22+
return join(`${[marker, ...states].join(':')}`, selector)
23+
}
24+
725
export function updateAllClasses(selectors, updateClass) {
826
let parser = selectorParser((selectors) => {
927
selectors.walkClasses((sel) => {

tests/jit/variants.test.css

+160
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,161 @@
378378
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
379379
var(--tw-shadow);
380380
}
381+
.peer:first-child ~ .peer-first\:shadow-md {
382+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
383+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
384+
var(--tw-shadow);
385+
}
386+
.peer:last-child ~ .peer-last\:shadow-md {
387+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
388+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
389+
var(--tw-shadow);
390+
}
391+
.peer:only-child ~ .peer-only\:shadow-md {
392+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
393+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
394+
var(--tw-shadow);
395+
}
396+
.peer:nth-child(odd) ~ .peer-odd\:shadow-md {
397+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
398+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
399+
var(--tw-shadow);
400+
}
401+
.peer:nth-child(even) ~ .peer-even\:shadow-md {
402+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
403+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
404+
var(--tw-shadow);
405+
}
406+
.peer:first-of-type ~ .peer-first-of-type\:shadow-md {
407+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
408+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
409+
var(--tw-shadow);
410+
}
411+
.peer:last-of-type ~ .peer-last-of-type\:shadow-md {
412+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
413+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
414+
var(--tw-shadow);
415+
}
416+
.peer:only-of-type ~ .peer-only-of-type\:shadow-md {
417+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
418+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
419+
var(--tw-shadow);
420+
}
421+
.peer:visited ~ .peer-visited\:shadow-md {
422+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
423+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
424+
var(--tw-shadow);
425+
}
426+
.peer:target ~ .peer-target\:shadow-md {
427+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
428+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
429+
var(--tw-shadow);
430+
}
431+
.peer:default ~ .peer-default\:shadow-md {
432+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
433+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
434+
var(--tw-shadow);
435+
}
436+
.peer:checked ~ .peer-checked\:shadow-md {
437+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
438+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
439+
var(--tw-shadow);
440+
}
441+
.peer:indeterminate ~ .peer-indeterminate\:shadow-md {
442+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
443+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
444+
var(--tw-shadow);
445+
}
446+
.peer:placeholder-shown ~ .peer-placeholder-shown\:shadow-md {
447+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
448+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
449+
var(--tw-shadow);
450+
}
451+
.peer:autofill ~ .peer-autofill\:shadow-md {
452+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
453+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
454+
var(--tw-shadow);
455+
}
456+
.peer:required ~ .peer-required\:shadow-md {
457+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
458+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
459+
var(--tw-shadow);
460+
}
461+
.peer:valid ~ .peer-valid\:shadow-md {
462+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
463+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
464+
var(--tw-shadow);
465+
}
466+
.peer:invalid ~ .peer-invalid\:shadow-md {
467+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
468+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
469+
var(--tw-shadow);
470+
}
471+
.peer:in-range ~ .peer-in-range\:shadow-md {
472+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
473+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
474+
var(--tw-shadow);
475+
}
476+
.peer:out-of-range ~ .peer-out-of-range\:shadow-md {
477+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
478+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
479+
var(--tw-shadow);
480+
}
481+
.peer:read-only ~ .peer-read-only\:shadow-md {
482+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
483+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
484+
var(--tw-shadow);
485+
}
486+
.peer:empty ~ .peer-empty\:shadow-md {
487+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
488+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
489+
var(--tw-shadow);
490+
}
491+
.peer:focus-within ~ .peer-focus-within\:shadow-md {
492+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
493+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
494+
var(--tw-shadow);
495+
}
496+
.peer:hover ~ .peer-hover\:shadow-md {
497+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
498+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
499+
var(--tw-shadow);
500+
}
501+
.peer:focus ~ .peer-focus\:shadow-md {
502+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
503+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
504+
var(--tw-shadow);
505+
}
506+
.peer:focus:hover ~ .peer-focus\:peer-hover\:shadow-md {
507+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
508+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
509+
var(--tw-shadow);
510+
}
511+
.peer:focus-visible ~ .peer-focus-visible\:shadow-md {
512+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
513+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
514+
var(--tw-shadow);
515+
}
516+
.peer:active ~ .peer-active\:shadow-md {
517+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
518+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
519+
var(--tw-shadow);
520+
}
521+
.peer:disabled ~ .peer-disabled\:shadow-md {
522+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
523+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
524+
var(--tw-shadow);
525+
}
526+
.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:shadow-md {
527+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
528+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
529+
var(--tw-shadow);
530+
}
531+
.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:first\:shadow-md:first-child {
532+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
533+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
534+
var(--tw-shadow);
535+
}
381536
[dir='ltr'] .ltr\:shadow-md {
382537
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
383538
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
@@ -412,6 +567,11 @@
412567
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
413568
var(--tw-shadow);
414569
}
570+
.dark .peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md {
571+
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
572+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
573+
var(--tw-shadow);
574+
}
415575
@media (min-width: 640px) {
416576
.sm\:shadow-md {
417577
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);

tests/jit/variants.test.html

+36
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,36 @@
7878
<div class="group-read-only:shadow-md"></div>
7979
<div class="group-empty:shadow-md"></div>
8080

81+
<!-- Peer variants -->
82+
<div class="peer-first:shadow-md"></div>
83+
<div class="peer-last:shadow-md"></div>
84+
<div class="peer-only:shadow-md"></div>
85+
<div class="peer-even:shadow-md"></div>
86+
<div class="peer-odd:shadow-md"></div>
87+
<div class="peer-first-of-type:shadow-md"></div>
88+
<div class="peer-last-of-type:shadow-md"></div>
89+
<div class="peer-only-of-type:shadow-md"></div>
90+
<div class="peer-hover:shadow-md"></div>
91+
<div class="peer-focus:shadow-md"></div>
92+
<div class="peer-disabled:shadow-md"></div>
93+
<div class="peer-active:shadow-md"></div>
94+
<div class="peer-target:shadow-md"></div>
95+
<div class="peer-visited:shadow-md"></div>
96+
<div class="peer-default:shadow-md"></div>
97+
<div class="peer-checked:shadow-md"></div>
98+
<div class="peer-indeterminate:shadow-md"></div>
99+
<div class="peer-placeholder-shown:shadow-md"></div>
100+
<div class="peer-autofill:shadow-md"></div>
101+
<div class="peer-focus-within:shadow-md"></div>
102+
<div class="peer-focus-visible:shadow-md"></div>
103+
<div class="peer-required:shadow-md"></div>
104+
<div class="peer-valid:shadow-md"></div>
105+
<div class="peer-invalid:shadow-md"></div>
106+
<div class="peer-in-range:shadow-md"></div>
107+
<div class="peer-out-of-range:shadow-md"></div>
108+
<div class="peer-read-only:shadow-md"></div>
109+
<div class="peer-empty:shadow-md"></div>
110+
81111
<!-- Reduced motion variants -->
82112
<div class="motion-safe:shadow-md"></div>
83113
<div class="motion-reduce:shadow-md"></div>
@@ -109,5 +139,11 @@
109139
<div class="group-disabled:group-focus:group-hover:shadow-md"></div>
110140
<div class="dark:group-disabled:group-focus:group-hover:shadow-md"></div>
111141
<div class="group-disabled:group-focus:group-hover:first:shadow-md"></div>
142+
143+
<!-- Stacked peer variants -->
144+
<div class="peer-focus:peer-hover:shadow-md"></div>
145+
<div class="peer-disabled:peer-focus:peer-hover:shadow-md"></div>
146+
<div class="dark:peer-disabled:peer-focus:peer-hover:shadow-md"></div>
147+
<div class="peer-disabled:peer-focus:peer-hover:first:shadow-md"></div>
112148
</body>
113149
</html>

0 commit comments

Comments
 (0)