Skip to content

Commit ee404ef

Browse files
committed
feat: 支持伪类first-child\last-child\nth-child
1 parent 05feb5b commit ee404ef

File tree

7 files changed

+138
-20
lines changed

7 files changed

+138
-20
lines changed

README.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function parse(
8787
| top | Length | ✔️ |
8888
| bottom | Length ||
8989
| right | Length ||
90-
| z-zndex | Number | ✔️ |
90+
| z-index | Number | ✔️ |
9191
| bottom | Length | ✔️ |
9292
| margin | Length \ Length Length \ Length Length Length \ Length Length Length Length | ✔️ |
9393
| margin-top | Length | ✔️ |
@@ -104,7 +104,7 @@ export function parse(
104104
| min-height | Length | ✔️ |
105105
| min-width | Length | ✔️ |
106106
| max-height | Length | ✔️ |
107-
| max-eidth | Length | ✔️ |
107+
| max-width | Length | ✔️ |
108108
| background | | ✔️ |
109109
| background-color | Color | ✔️ |
110110
| background-image | "src('xxx')", "linear-gradient(xxx)" 支持图片资源和线性渐变 | ✔️ |
@@ -222,17 +222,15 @@ export function parse(
222222

223223
- 支持**before、after**
224224

225-
| 选择器 | 示例 | 示例说明 | 支持情况 | 备注 |
226-
| ----------------- | ------------------------ | ----------------------------------------------------------------- | :------: | :------: |
227-
| :before | .intro:before | 在每个 class="intro" 元素之前插入内容 | ✔️ | |
228-
| :after | .intro:after | 在每个 class="intro" 元素之后插入内容 | ✔️ | |
229-
| :nth-child() | .intro:nth-child(2) | 选择 class="intro" 元素是其父级的第二个子元素 || 后续支持 |
230-
| :nth-last-child() | .intro:nth-last-child(2) | 选择 class="intro" 元素是其父级的第二个子元素, 从最后一个子项计数 || 后续支持 |
231-
| :first-child | .intro:first-child | 选择 class="intro" 元素是其父级的第一个子级 || 后续支持 |
232-
| :last-child | .intro:last-child | 选择 class="intro" 元素是其父级的最后一个子级 || 后续支持 |
233-
| :root | :root | 选择文档的根元素 || 后续支持 |
234-
| :checked | input:checked | 选择每个选中的输入元素 || |
235-
| ... | | 其他 || |
225+
| 选择器 | 示例 | 示例说明 | 支持情况 | 备注 |
226+
| ------------ | ------------------- | --------------------------------------------- | :------: | :--: |
227+
| :before | .intro:before | 在每个 class="intro" 元素之前插入内容 | ✔️ | |
228+
| :after | .intro:after | 在每个 class="intro" 元素之后插入内容 | ✔️ | |
229+
| :nth-child() | .intro:nth-child(2) | 选择 class="intro" 元素是其父级的第二个子元素 | ✔️ | |
230+
| :first-child | .intro:first-child | 选择 class="intro" 元素是其父级的第一个子级 | ✔️ | |
231+
| :last-child | .intro:last-child | 选择 class="intro" 元素是其父级的最后一个子级 | ✔️ | |
232+
| :checked | input:checked | 选择每个选中的输入元素 || |
233+
| ... | | 其他 || |
236234

237235
## CSS 变量
238236

__test__/selector.spec.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,46 @@ test('Harmony selector combine .item{} .item{} important!', t => {
205205
t.snapshot(code)
206206
})
207207

208+
test('Harmony selector .item:before .item:after', t => {
209+
const { code } = parse(normal, [`
210+
.item {
211+
height: 100px;
212+
}
213+
.item::before {
214+
height: 100px;
215+
width: 30px;
216+
}
217+
.item::after {
218+
height: 100px;
219+
width: 30px;
220+
}
221+
`], {
222+
platformString: 'Harmony'
223+
})
224+
t.snapshot(code)
225+
})
226+
227+
228+
test('Harmony selector .item:first-child .item:last-child .item.nth-child(2n-1)', t => {
229+
const { code } = parse(normal, [`
230+
.item {
231+
height: 100px;
232+
}
233+
.item:first-child {
234+
height: 100px;
235+
width: 30px;
236+
}
237+
.item:last-child {
238+
height: 100px;
239+
width: 30px;
240+
}
241+
.item:nth-child(2n-1) {
242+
height: 100px;
243+
width: 30px;
244+
}
245+
`], {
246+
platformString: 'Harmony'
247+
})
248+
t.snapshot(code)
249+
})
208250

__test__/selector.spec.mjs.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,67 @@ Generated by [AVA](https://avajs.dev).
337337
};␊
338338
export default Index;␊
339339
`
340+
341+
## Harmony selector .item:before .item:after
342+
343+
> Snapshot 1
344+
345+
`import { View } from '@tarojs/components';␊
346+
import { calcStaticStyle, convertNumber2VP, __combine_nesting_style__, __var_fn, globalCss } from "@tarojs/runtime";␊
347+
let __inner_style_data__;␊
348+
function __inner_style__() {␊
349+
if (__inner_style_data__) return __inner_style_data__;␊
350+
__inner_style_data__ = {␊
351+
"item": {␊
352+
height: convertNumber2VP(100),␊
353+
["::after"]: {␊
354+
height: convertNumber2VP(100),␊
355+
width: convertNumber2VP(30)␊
356+
},␊
357+
["::before"]: {␊
358+
height: convertNumber2VP(100),␊
359+
width: convertNumber2VP(30)␊
360+
}␊
361+
}␊
362+
};␊
363+
return __inner_style_data__;␊
364+
}␊
365+
const Index = ()=>{␊
366+
return __combine_nesting_style__(<View __hmStyle={calcStaticStyle(__inner_style__(), 'index', null)} className='index'/>, null);␊
367+
};␊
368+
export default Index;␊
369+
`
370+
371+
## Harmony selector .item:first-child .item:last-child .item.nth-child(2n-1)
372+
373+
> Snapshot 1
374+
375+
`import { View } from '@tarojs/components';␊
376+
import { calcStaticStyle, convertNumber2VP, __combine_nesting_style__, __var_fn, globalCss } from "@tarojs/runtime";␊
377+
let __inner_style_data__;␊
378+
function __inner_style__() {␊
379+
if (__inner_style_data__) return __inner_style_data__;␊
380+
__inner_style_data__ = {␊
381+
"item": {␊
382+
height: convertNumber2VP(100),␊
383+
["::first-child"]: {␊
384+
height: convertNumber2VP(100),␊
385+
width: convertNumber2VP(30)␊
386+
},␊
387+
["::last-child"]: {␊
388+
height: convertNumber2VP(100),␊
389+
width: convertNumber2VP(30)␊
390+
},␊
391+
["::nth-child(2n-1)"]: {␊
392+
height: convertNumber2VP(100),␊
393+
width: convertNumber2VP(30)␊
394+
}␊
395+
}␊
396+
};␊
397+
return __inner_style_data__;␊
398+
}␊
399+
const Index = ()=>{␊
400+
return __combine_nesting_style__(<View __hmStyle={calcStaticStyle(__inner_style__(), 'index', null)} className='index'/>, null);␊
401+
};␊
402+
export default Index;␊
403+
`

__test__/selector.spec.mjs.snap

123 Bytes
Binary file not shown.

src/constants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub const NESTINT_STYLE_DATA: &'static str = "__nesting_style_data__";
99
// pub const CALC_DYMAMIC_STYLE: &'static str = "calcDynamicStyle";
1010
pub const CALC_STATIC_STYLE: &'static str = "calcStaticStyle";
1111
pub const CSS_VAR_FN: &'static str = "__var_fn";
12-
12+
pub static SUPPORT_PSEUDO_KEYS: [&'static str; 5] = [":before", ":after", ":first-child", ":last-child", ":nth-child"];
1313

1414

1515
pub const RN_CONVERT_STYLE_PX_FN: &'static str = "scalePx2dp";

src/style_parser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{rc::Rc, cell::RefCell, convert::Infallible, collections::HashMap, hash
22

33
use lightningcss::{stylesheet::{StyleSheet, ParserOptions, PrinterOptions}, visitor::{Visit, Visitor, VisitTypes}, visit_types, rules::CssRule, properties::Property, declaration::DeclarationBlock, traits::ToCss};
44

5-
use crate::{document::JSXDocument, style_propetries::{style_value_type::StyleValueType, unit::Platform}, utils::to_camel_case, visitor::SpanKey};
5+
use crate::{constants::SUPPORT_PSEUDO_KEYS, document::JSXDocument, style_propetries::{style_value_type::StyleValueType, unit::Platform}, utils::to_camel_case, visitor::SpanKey};
66

77
use super::parse_style_properties::parse_style_properties;
88

@@ -142,7 +142,7 @@ impl<'i> StyleParser<'i> {
142142
// 用于查询的选择器
143143
let mut element_selector = selector.clone();
144144
// 判断是否伪类(暂时支持鸿蒙)
145-
if (selector.contains(":before") || selector.contains(":after")) && self.platform == Platform::Harmony {
145+
if (SUPPORT_PSEUDO_KEYS.into_iter().any(|s| selector.contains(s))) && self.platform == Platform::Harmony {
146146
let _selectors = selector.split(":").collect::<Vec<&str>>();
147147
pesudo_selector = _selectors[1].parse::<String>().ok();
148148
// 伪类需要把 : 之后的选择器去掉,只保留 : 之前的选择器,用于查询所属的element

src/visitor.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use swc_core::{
2121
use swc_core::ecma::ast::*;
2222

2323
use crate::{
24-
constants::{CALC_STATIC_STYLE, COMBINE_NESTING_STYLE, CONVERT_STYLE_PX_FN, CSS_VAR_FN, HM_STYLE, INNER_STYLE, INNER_STYLE_DATA, NESTING_STYLE, NESTINT_STYLE_DATA, RN_CONVERT_STYLE_PX_FN, RN_CONVERT_STYLE_VU_FN}, parse_style_properties::parse_style_properties, scraper::Element, style_parser::StyleValue, style_propetries::{style_value_type::StyleValueType, traits::ToStyleValue, unit::{Platform, PropertyTuple}}, utils::{
24+
constants::{CALC_STATIC_STYLE, COMBINE_NESTING_STYLE, CONVERT_STYLE_PX_FN, CSS_VAR_FN, HM_STYLE, INNER_STYLE, INNER_STYLE_DATA, NESTING_STYLE, NESTINT_STYLE_DATA, RN_CONVERT_STYLE_PX_FN, RN_CONVERT_STYLE_VU_FN, SUPPORT_PSEUDO_KEYS}, parse_style_properties::parse_style_properties, scraper::Element, style_parser::StyleValue, style_propetries::{style_value_type::StyleValueType, traits::ToStyleValue, unit::{Platform, PropertyTuple}}, utils::{
2525
create_qualname, get_callee_attributes, is_starts_with_uppercase, prefix_style_key, recursion_jsx_member, split_selector, to_camel_case, to_kebab_case, TSelector
2626
}
2727
};
@@ -576,7 +576,7 @@ impl VisitMut for ModuleMutVisitor {
576576
let mut insert_key = key.to_string();
577577
let mut insert_value = vec![];
578578

579-
if (key.contains(":after") || key.contains(":before")) && self.platform == Platform::Harmony {
579+
if (SUPPORT_PSEUDO_KEYS.into_iter().any(|s| key.contains(s))) && self.platform == Platform::Harmony {
580580
let mut pesudo_key = String::new();
581581
let key_arr = key.split(":").collect::<Vec<&str>>();
582582
if key_arr.len() == 2 {
@@ -1298,6 +1298,7 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
12981298

12991299
fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
13001300
let mut has_style = false;
1301+
let mut should_remove_style = false;
13011302

13021303
if self.check_is_jsx_callee(n) {
13031304

@@ -1326,6 +1327,9 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
13261327
let value = self.process_attribute_lit_value(lit, has_dynamic_class);
13271328
if let Some(value) = value {
13281329
static_styles = parse_style_values(value, self.platform.clone());
1330+
if static_styles.len() > 0 {
1331+
should_remove_style = true;
1332+
}
13291333
}
13301334
}
13311335
_ => {
@@ -1334,6 +1338,9 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
13341338
let (static_props, dynamic_props) = self.process_attribute_expr_value(expr, has_dynamic_class);
13351339
static_styles = static_props;
13361340
dynamic_styles = dynamic_props;
1341+
if static_styles.len() > 0 || dynamic_styles.len() > 0 {
1342+
should_remove_style = true;
1343+
}
13371344
}
13381345
};
13391346
}
@@ -1499,7 +1506,7 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
14991506
}
15001507
} else {
15011508
// 移除原本的style
1502-
if has_style {
1509+
if has_style && should_remove_style {
15031510
if let Some(attr) = n.args.get_mut(1) {
15041511
if let Expr::Object(object) = &mut *attr.expr {
15051512
let mut index = 0;
@@ -1529,6 +1536,7 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
15291536

15301537
fn visit_mut_jsx_element(&mut self, n: &mut JSXElement) {
15311538
let mut has_style = false;
1539+
let mut should_remove_style = false;
15321540
let span_key = SpanKey(n.span);
15331541

15341542
if let Some(_) = self.jsx_record.borrow_mut().get(&span_key) {
@@ -1558,6 +1566,9 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
15581566
let value = self.process_attribute_lit_value(lit, has_dynamic_class);
15591567
if let Some(value) = value {
15601568
static_styles = parse_style_values(value, self.platform.clone());
1569+
if static_styles.len() > 0 {
1570+
should_remove_style = true;
1571+
}
15611572
}
15621573
}
15631574
JSXAttrValue::JSXExprContainer(expr_container) => {
@@ -1567,6 +1578,9 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
15671578
let (static_props, dynamic_props) = self.process_attribute_expr_value(expr, has_dynamic_class);
15681579
static_styles = static_props;
15691580
dynamic_styles = dynamic_props;
1581+
if static_styles.len() > 0 || dynamic_styles.len() > 0 {
1582+
should_remove_style = true;
1583+
}
15701584
}
15711585
_ => {
15721586
has_style = false;
@@ -1714,7 +1728,7 @@ impl<'i> VisitMut for JSXMutVisitor<'i> {
17141728
}
17151729
} else {
17161730
// 移除原本的style,从属性上去掉
1717-
if has_style {
1731+
if has_style && should_remove_style {
17181732
let attrs = n.opening.attrs.iter().filter(|attr| {
17191733
if let JSXAttrOrSpread::JSXAttr(attr) = attr {
17201734
if let JSXAttrName::Ident(ident) = &attr.name {

0 commit comments

Comments
 (0)