Skip to content

Commit 35bc70b

Browse files
committed
fix: enhance nth-child and nth-last-child handling in tail function; update nth.vue test case
close: #44
1 parent 5d93240 commit 35bc70b

File tree

4 files changed

+114
-59
lines changed

4 files changed

+114
-59
lines changed

src/tail.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,23 @@ export function tail(css: string) {
66
return 'odd'
77
if (css === 'nth-child(even)')
88
return 'even'
9-
return ''
9+
// nth-child(2n+1) => 2n+1
10+
if (css.startsWith('nth-child(')) {
11+
const match = css.match(/nth-child\((.*)\)/)
12+
if (match && match[1])
13+
return `nth-[${match[1]}]`
14+
return ''
15+
}
16+
if (css.startsWith('nth-last-child(')) {
17+
const match = css.match(/nth-last-child\((.*)\)/)
18+
if (match && match[1])
19+
return `nth-last-[${match[1]}]`
20+
return ''
21+
}
22+
if (!css.includes('('))
23+
return css
24+
25+
return css.split('(')[0]
1026
}
1127
if (css.startsWith('aria-') || css.startsWith('data-'))
1228
return css.split('=')[0]
@@ -16,5 +32,20 @@ export function tail(css: string) {
1632
return 'file'
1733
if (css.endsWith('-child'))
1834
return css.split('-')[0]
35+
if (css.startsWith('has(')) {
36+
const match = css.match(/has\((.*)\)/)
37+
if (match && match[1])
38+
return `has-[${match[1]}]`
39+
return ''
40+
}
41+
if (css.startsWith('where(')) {
42+
const match = css.match(/where\((.*)\)/)
43+
if (match && match[1])
44+
return `in-[${match[1]}]`
45+
return ''
46+
}
47+
if (['first-child', 'last-child', 'only-child'].includes(css)) {
48+
return css.split('-')[0]
49+
}
1950
return css
2051
}

src/transformCss.ts

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import {
2121
} from './utils'
2222
import { wrapperVueTemplate } from './wrapperVueTemplate'
2323

24-
const combineReg = /([.#\w\-]+)([.#]\w+)/ // xx.xx
24+
const combineReg = /([.#]?[\w\-]+)((?:[.#]\w+)+)/ // xx.xx
2525

2626
const addReg = /([.#\w\-]+)\s*\+\s*([.#\w]+)/ // xx + xx
27-
const tailReg = /:([\w\-]+)/ // :after
27+
const tailReg = /:?:(.+)/ // :after
2828
const tagReg = /\[([\w\-]*)[='" ]*([\w-]*)['" ]*\]/ // [class="xxx"]
2929
const emptyClass = /[,\w>.#\-+:[\]="'\s()]+\{\}\n/g
3030

@@ -366,6 +366,33 @@ function sort(data: any[]) {
366366
return result
367367
}
368368

369+
function matchCombine(
370+
props: any[],
371+
combineClass: string[],
372+
combineId: string[],
373+
) {
374+
const classPassed = combineClass.length
375+
? props.some(
376+
(prop: any) =>
377+
prop.name === 'class'
378+
&& combineClass.every((c) => {
379+
const className = prop.value.content?.split(' ').filter(Boolean)
380+
return className?.some((item: string) => item.includes(c))
381+
}),
382+
)
383+
: true
384+
const idPassed = combineId.length
385+
? props.some(
386+
(prop: any) =>
387+
prop.name === 'id'
388+
&& combineId.every((i) => {
389+
const idName = prop.value.content?.split(' ').filter(Boolean)
390+
return idName?.some((item: string) => item.includes(i))
391+
}),
392+
)
393+
: true
394+
return classPassed && idPassed
395+
}
369396
export function astFindTag(
370397
ast: any,
371398
tag = '',
@@ -390,6 +417,26 @@ export function astFindTag(
390417
: tag.startsWith('#')
391418
? 'id'
392419
: ''
420+
// combine 可能包含多个 .xxx#xxx.### , 需要一个个一个的去匹配
421+
const combineClass: string[] = []
422+
const combineId: string[] = []
423+
if (combine) {
424+
combine
425+
.split('.')
426+
.filter(Boolean)
427+
.forEach((item) => {
428+
if (item.includes('#')) {
429+
const classNames = item.replace(/#([^.#]+)/g, (_, id) => {
430+
combineId.push(id)
431+
return ''
432+
})
433+
combineClass.push(...classNames.split('.').filter(Boolean))
434+
}
435+
else {
436+
combineClass.push(item)
437+
}
438+
})
439+
}
393440
const combineSelector = combine
394441
? combine.startsWith('.')
395442
? 'class'
@@ -417,11 +464,9 @@ export function astFindTag(
417464
.includes(tagMatch && tagMatch[2] ? tagMatch[2] : tag.slice(1))),
418465
)
419466
&& (combine === undefined
420-
|| ast.props.some(
421-
(prop: any) =>
422-
prop.name === combineSelector
423-
&& prop.value.content?.includes(combine.slice(1)),
424-
))
467+
|| (ast.props
468+
&& ast.props.length
469+
&& matchCombine(ast.props, combineClass, combineId)))
425470
&& (add === undefined
426471
|| siblings.some(
427472
(sib: any) =>
@@ -443,23 +488,19 @@ export function astFindTag(
443488
&& (combine === undefined
444489
|| (ast.props
445490
&& ast.props.length
446-
&& ast.props.some(
447-
(prop: any) =>
448-
prop.name === combineSelector
449-
&& (tagMatch || prop.value.content?.includes(combine.slice(1))),
450-
)))
451-
&& (add === undefined
452-
|| siblings.some(
453-
(sib: any) =>
454-
sib !== ast
455-
&& sib.props
456-
&& sib.props.length
457-
&& sib.props.some(
458-
(prop: any) =>
459-
prop.name === addSelector
460-
&& prop.value.content?.includes(add.slice(1)),
461-
),
462-
))
491+
&& matchCombine(ast.props, combineClass, combineId)))
492+
&& (add === undefined
493+
|| siblings.some(
494+
(sib: any) =>
495+
sib !== ast
496+
&& sib.props
497+
&& sib.props.length
498+
&& sib.props.some(
499+
(prop: any) =>
500+
prop.name === addSelector
501+
&& prop.value.content?.includes(add.slice(1)),
502+
),
503+
))
463504
) {
464505
result.push(ast)
465506
}
@@ -524,8 +565,6 @@ async function resolveConflictClass(
524565
}
525566
}
526567

527-
const f = after.replace(/\[([^\]]+)\]/g, (all, v) =>
528-
all.replace(v, joinWithUnderLine(v)))
529568
// 默认全部都输出到class中
530569
const returnValue
531570
= isJsx || after.replace(/[\w\-]+=("{1})(.*?)\1/g, '').includes('[')
@@ -534,17 +573,14 @@ async function resolveConflictClass(
534573
all.replace(v, joinWithUnderLine(v)))
535574
.replace(/-(rgba?([^)]+))/g, '-[$1]')
536575
.replace(
537-
/([\w\-]+)=(['"]{1})(.*?)\2/g,
538-
(all, prefix, _, content) => {
576+
/([\w\-]+(?:-\[[^\]]*\])?)=(['"]{1})(.*?)\2/g,
577+
(_all, prefix, _, content) => {
539578
// 拆分 content 中的空格,但是要忽略 ( ) [] 中的空格, 然后用 prefix 连接
540-
const splitContent = content
579+
const splitContent: string[] = content
541580
.split(/(?<!\[[^\]]*)\s+/)
542581
.filter(Boolean)
543-
const newContent = splitContent
544-
.map(item => `${prefix}-${item}`)
545-
.join(` `)
546582

547-
return newContent
583+
return splitContent.map(item => `${prefix}-${item}`).join(` `)
548584
},
549585
)
550586
: after

test/__snapshots__/transformCode.test.ts.snap

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ exports[`demo3 vue test > demo2.vue 1`] = `
687687
<div class="add-node-btn-box w-240px inline-flex shrink-0 relative before-content-[''] before-absolute before-top-0 before-left-0 before-right-0 before-bottom-0 before--z-1 before-m-auto before-w-2px before-h-[100%] before-bg-[#cacaca]">
688688
<div class="add-node-btn select-none w-240px px-0 pt-20px pb-32px flex justify-center shrink-0 grow-1">
689689
<div class="add-node-popover-body flex">
690-
<a class="add-node-popover-item approver mr-10px cursor-pointer text-center flex-1 text-[#191f25]!" @click="addType(1)">
690+
<a class="add-node-popover-item approver mr-10px cursor-pointer text-center flex-1 text-[#191f25]! hover .item-wrapper-bg-[#3296fa] item-wrapper-shadow-[0_10px_20px_0_rgba(50,150,250,0.4)] hover .iconfont-text-[#fff] active .item-wrapper-shadow-[none] item-wrapper-bg-[#eaeaea] active .iconfont-text-inherit" @click="addType(1)">
691691
<div class="item-wrapper select-none inline-block w-80px h-80px mb-5px bg-[#fff] border-1px border-solid border-[#e2e2e2] border-rd-[50%] transition-all transition-duration-0.3s transition-ease-[cubic-bezier(0.645,0.045,0.355,1)] text-[#ff943e]">
692692
<span class="iconfont text-35px lh-80px"></span>
693693
</div>
@@ -764,23 +764,17 @@ export default defineComponent({
764764
765765
exports[`nth vue test > nth.vue 1`] = `
766766
"<script setup lang="ts"></script>
767-
768767
<template>
769-
<div class="bg-red w-[100%] lh-20px">
770-
<div class="red">
768+
<div class="bg-red w-[100%] lh-20px" id="test">
769+
<div class="red yellow green nth-last-[1]-text-yellow">
771770
n1
772771
</div>
773772
<div class="red">
774773
n2
775774
</div>
776775
</div>
777776
</template>
778-
779-
<style scoped>
780-
.red:nth-child(2) {
781-
color: yellow;
782-
}
783-
</style>
777+
<style scoped></style>
784778
"
785779
`;
786780
@@ -1803,9 +1797,9 @@ watch(
18031797
<div class="add-node-btn-box w-240px inline-flex shrink-0 relative before-content-[''] before-absolute before-top-0 before-left-0 before-right-0 before-bottom-0 before--z-1 before-m-auto before-w-2px before-h-[100%] before-bg-[#cacaca]">
18041798
<div class="add-node-btn select-none w-240px px-0 pt-20px pb-32px flex justify-center shrink-0 grow-1">
18051799
<div class="add-node-popover-body flex">
1806-
<a class="group add-node-popover-item approver mr-10px cursor-pointer text-center flex-1 text-[#191f25]!" @click="addType(1)">
1807-
<div class="group item-wrapper select-none inline-block w-80px h-80px mb-5px bg-[#fff] border-1px border-solid border-[#e2e2e2] border-rd-[50%] transition-all transition-duration-0.3s transition-ease-[cubic-bezier(0.645,0.045,0.355,1)] text-[#ff943e] group-hover-bg-[#3296fa] group-hover-shadow-[0_10px_20px_0_rgba(50,150,250,0.4)]">
1808-
<span class="iconfont text-35px lh-80px group-hover-text-[#fff]"></span>
1800+
<a class="add-node-popover-item approver mr-10px cursor-pointer text-center flex-1 text-[#191f25]! hover .item-wrapper-bg-[#3296fa] item-wrapper-shadow-[0_10px_20px_0_rgba(50,150,250,0.4)] hover .iconfont-text-[#fff] active .item-wrapper-shadow-[none] item-wrapper-bg-[#eaeaea] active .iconfont-text-inherit" @click="addType(1)">
1801+
<div class="item-wrapper select-none inline-block w-80px h-80px mb-5px bg-[#fff] border-1px border-solid border-[#e2e2e2] border-rd-[50%] transition-all transition-duration-0.3s transition-ease-[cubic-bezier(0.645,0.045,0.355,1)] text-[#ff943e]">
1802+
<span class="iconfont text-35px lh-80px"></span>
18091803
</div>
18101804
<p>审批人</p>
18111805
</a>
@@ -1916,23 +1910,17 @@ export default defineComponent({
19161910
----- nth.vue -------
19171911
19181912
<script setup lang="ts"></script>
1919-
19201913
<template>
1921-
<div class="bg-red w-[100%] lh-20px">
1922-
<div class="red">
1914+
<div class="bg-red w-[100%] lh-20px" id="test">
1915+
<div class="red yellow green nth-last-[1]-text-yellow">
19231916
n1
19241917
</div>
19251918
<div class="red">
19261919
n2
19271920
</div>
19281921
</div>
19291922
</template>
1930-
1931-
<style scoped>
1932-
.red:nth-child(2) {
1933-
color: yellow;
1934-
}
1935-
</style>
1923+
<style scoped></style>
19361924
",
19371925
"
19381926

test/demo/nth.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script setup lang="ts"></script>
22

33
<template>
4-
<div style="background: red; width: 100%; line-height: 20px">
5-
<div class="red">
4+
<div id="test" style="background: red; width: 100%; line-height: 20px">
5+
<div class="red yellow green">
66
n1
77
</div>
88
<div class="red">
@@ -12,7 +12,7 @@
1212
</template>
1313

1414
<style scoped>
15-
.red:nth-child(2) {
15+
#test div.red.yellow.green:nth-last-child(1) {
1616
color: yellow;
1717
}
1818
</style>

0 commit comments

Comments
 (0)