1use crate::corety::{AzString, OptionString};
4use crate::props::property::CssProperty;
5
6#[repr(C)]
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
10pub struct PseudoStateFlags {
11 pub hover: bool,
12 pub active: bool,
13 pub focused: bool,
14 pub disabled: bool,
15 pub checked: bool,
16 pub focus_within: bool,
17 pub visited: bool,
18}
19
20impl PseudoStateFlags {
21 pub fn has_state(&self, state: PseudoStateType) -> bool {
23 match state {
24 PseudoStateType::Normal => true,
25 PseudoStateType::Hover => self.hover,
26 PseudoStateType::Active => self.active,
27 PseudoStateType::Focus => self.focused,
28 PseudoStateType::Disabled => self.disabled,
29 PseudoStateType::Checked => self.checked,
30 PseudoStateType::FocusWithin => self.focus_within,
31 PseudoStateType::Visited => self.visited,
32 }
33 }
34}
35
36#[repr(C, u8)]
39#[derive(Debug, Clone, PartialEq)]
40pub enum DynamicSelector {
41 Os(OsCondition) = 0,
43 OsVersion(OsVersionCondition) = 1,
45 Media(MediaType) = 2,
47 ViewportWidth(MinMaxRange) = 3,
49 ViewportHeight(MinMaxRange) = 4,
51 ContainerWidth(MinMaxRange) = 5,
53 ContainerHeight(MinMaxRange) = 6,
55 ContainerName(AzString) = 7,
57 Theme(ThemeCondition) = 8,
59 AspectRatio(MinMaxRange) = 9,
61 Orientation(OrientationType) = 10,
63 PrefersReducedMotion(BoolCondition) = 11,
65 PrefersHighContrast(BoolCondition) = 12,
67 PseudoState(PseudoStateType) = 13,
69 Language(LanguageCondition) = 14,
72}
73
74impl_vec!(
75 DynamicSelector,
76 DynamicSelectorVec,
77 DynamicSelectorVecDestructor,
78 DynamicSelectorVecDestructorType
79);
80impl_vec_clone!(
81 DynamicSelector,
82 DynamicSelectorVec,
83 DynamicSelectorVecDestructor
84);
85impl_vec_debug!(DynamicSelector, DynamicSelectorVec);
86impl_vec_partialeq!(DynamicSelector, DynamicSelectorVec);
87
88#[repr(C)]
90#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
91pub struct MinMaxRange {
92 pub min: f32,
94 pub max: f32,
96}
97
98impl MinMaxRange {
99 pub const fn new(min: Option<f32>, max: Option<f32>) -> Self {
100 Self {
101 min: if let Some(m) = min { m } else { f32::NAN },
102 max: if let Some(m) = max { m } else { f32::NAN },
103 }
104 }
105
106 pub fn min(&self) -> Option<f32> {
107 if self.min.is_nan() {
108 None
109 } else {
110 Some(self.min)
111 }
112 }
113
114 pub fn max(&self) -> Option<f32> {
115 if self.max.is_nan() {
116 None
117 } else {
118 Some(self.max)
119 }
120 }
121
122 pub fn matches(&self, value: f32) -> bool {
123 let min_ok = self.min.is_nan() || value >= self.min;
124 let max_ok = self.max.is_nan() || value <= self.max;
125 min_ok && max_ok
126 }
127}
128
129#[repr(C)]
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub enum BoolCondition {
133 False,
134 True,
135}
136
137impl From<bool> for BoolCondition {
138 fn from(b: bool) -> Self {
139 if b {
140 Self::True
141 } else {
142 Self::False
143 }
144 }
145}
146
147impl From<BoolCondition> for bool {
148 fn from(b: BoolCondition) -> Self {
149 matches!(b, BoolCondition::True)
150 }
151}
152
153#[repr(C)]
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
155pub enum OsCondition {
156 Any,
157 Apple, MacOS,
159 IOS,
160 Linux,
161 Windows,
162 Android,
163 Web, }
165
166impl OsCondition {
167 pub fn from_system_platform(platform: &crate::system::Platform) -> Self {
169 use crate::system::Platform;
170 match platform {
171 Platform::Windows => OsCondition::Windows,
172 Platform::MacOs => OsCondition::MacOS,
173 Platform::Linux(_) => OsCondition::Linux,
174 Platform::Android => OsCondition::Android,
175 Platform::Ios => OsCondition::IOS,
176 Platform::Unknown => OsCondition::Any,
177 }
178 }
179}
180
181#[repr(C, u8)]
182#[derive(Debug, Clone, PartialEq, Eq, Hash)]
183pub enum OsVersionCondition {
184 Exact(AzString),
186 Min(AzString),
188 Max(AzString),
190 DesktopEnvironment(LinuxDesktopEnv),
192}
193
194#[repr(C)]
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196pub enum LinuxDesktopEnv {
197 Gnome,
198 KDE,
199 XFCE,
200 Unity,
201 Cinnamon,
202 MATE,
203 Other,
204}
205
206impl LinuxDesktopEnv {
207 pub fn from_system_desktop_env(de: &crate::system::DesktopEnvironment) -> Self {
209 use crate::system::DesktopEnvironment;
210 match de {
211 DesktopEnvironment::Gnome => LinuxDesktopEnv::Gnome,
212 DesktopEnvironment::Kde => LinuxDesktopEnv::KDE,
213 DesktopEnvironment::Other(_) => LinuxDesktopEnv::Other,
214 }
215 }
216}
217
218#[repr(C)]
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
220pub enum MediaType {
221 Screen,
222 Print,
223 All,
224}
225
226#[repr(C, u8)]
227#[derive(Debug, Clone, PartialEq, Eq, Hash)]
228pub enum ThemeCondition {
229 Light,
230 Dark,
231 Custom(AzString),
232 SystemPreferred,
234}
235
236impl ThemeCondition {
237 pub fn from_system_theme(theme: crate::system::Theme) -> Self {
239 use crate::system::Theme;
240 match theme {
241 Theme::Light => ThemeCondition::Light,
242 Theme::Dark => ThemeCondition::Dark,
243 }
244 }
245}
246
247#[repr(C)]
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
249pub enum OrientationType {
250 Portrait,
251 Landscape,
252}
253
254#[repr(C, u8)]
257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
258pub enum LanguageCondition {
259 Exact(AzString),
261 Prefix(AzString),
263}
264
265impl LanguageCondition {
266 pub fn matches(&self, language: &str) -> bool {
268 match self {
269 LanguageCondition::Exact(lang) => language.eq_ignore_ascii_case(lang.as_str()),
270 LanguageCondition::Prefix(prefix) => {
271 let prefix_str = prefix.as_str();
272 if language.len() < prefix_str.len() {
273 return false;
274 }
275 let lang_prefix = &language[..prefix_str.len()];
277 if !lang_prefix.eq_ignore_ascii_case(prefix_str) {
278 return false;
279 }
280 language.len() == prefix_str.len()
282 || language.as_bytes().get(prefix_str.len()) == Some(&b'-')
283 }
284 }
285 }
286}
287
288#[repr(C)]
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
290pub enum PseudoStateType {
291 Normal,
293 Hover,
295 Active,
297 Focus,
299 Disabled,
301 Checked,
303 FocusWithin,
305 Visited,
307}
308
309impl_option!(
310 LinuxDesktopEnv,
311 OptionLinuxDesktopEnv,
312 [Debug, Clone, Copy, PartialEq, Eq, Hash]
313);
314
315#[repr(C)]
317#[derive(Debug, Clone)]
318pub struct DynamicSelectorContext {
319 pub os: OsCondition,
321 pub os_version: AzString,
322 pub desktop_env: OptionLinuxDesktopEnv,
323
324 pub theme: ThemeCondition,
326
327 pub media_type: MediaType,
329 pub viewport_width: f32,
330 pub viewport_height: f32,
331
332 pub container_width: f32,
335 pub container_height: f32,
336 pub container_name: OptionString,
337
338 pub prefers_reduced_motion: BoolCondition,
340 pub prefers_high_contrast: BoolCondition,
341
342 pub orientation: OrientationType,
344
345 pub pseudo_state: PseudoStateFlags,
347
348 pub language: AzString,
350}
351
352impl Default for DynamicSelectorContext {
353 fn default() -> Self {
354 Self {
355 os: OsCondition::Any,
356 os_version: AzString::from_const_str("0.0"),
357 desktop_env: OptionLinuxDesktopEnv::None,
358 theme: ThemeCondition::Light,
359 media_type: MediaType::Screen,
360 viewport_width: 800.0,
361 viewport_height: 600.0,
362 container_width: f32::NAN,
363 container_height: f32::NAN,
364 container_name: OptionString::None,
365 prefers_reduced_motion: BoolCondition::False,
366 prefers_high_contrast: BoolCondition::False,
367 orientation: OrientationType::Landscape,
368 pseudo_state: PseudoStateFlags::default(),
369 language: AzString::from_const_str("en-US"),
370 }
371 }
372}
373
374impl DynamicSelectorContext {
375 pub fn from_system_style(system_style: &crate::system::SystemStyle) -> Self {
377 let os = OsCondition::from_system_platform(&system_style.platform);
378 let desktop_env = if let crate::system::Platform::Linux(de) = &system_style.platform {
379 OptionLinuxDesktopEnv::Some(LinuxDesktopEnv::from_system_desktop_env(de))
380 } else {
381 OptionLinuxDesktopEnv::None
382 };
383 let theme = ThemeCondition::from_system_theme(system_style.theme);
384
385 Self {
386 os,
387 os_version: AzString::from_const_str("0.0"), desktop_env,
389 theme,
390 media_type: MediaType::Screen,
391 viewport_width: 800.0, viewport_height: 600.0,
393 container_width: f32::NAN,
394 container_height: f32::NAN,
395 container_name: OptionString::None,
396 prefers_reduced_motion: BoolCondition::False, prefers_high_contrast: BoolCondition::False,
398 orientation: OrientationType::Landscape,
399 pseudo_state: PseudoStateFlags::default(),
400 language: system_style.language.clone(),
401 }
402 }
403
404 pub fn with_viewport(&self, width: f32, height: f32) -> Self {
406 let mut ctx = self.clone();
407 ctx.viewport_width = width;
408 ctx.viewport_height = height;
409 ctx.orientation = if width > height {
410 OrientationType::Landscape
411 } else {
412 OrientationType::Portrait
413 };
414 ctx
415 }
416
417 pub fn with_container(&self, width: f32, height: f32, name: Option<AzString>) -> Self {
419 let mut ctx = self.clone();
420 ctx.container_width = width;
421 ctx.container_height = height;
422 ctx.container_name = name.into();
423 ctx
424 }
425
426 pub fn with_pseudo_state(&self, state: PseudoStateFlags) -> Self {
428 let mut ctx = self.clone();
429 ctx.pseudo_state = state;
430 ctx
431 }
432
433 pub fn viewport_breakpoint_changed(&self, other: &Self, breakpoints: &[f32]) -> bool {
435 for bp in breakpoints {
436 let self_above = self.viewport_width >= *bp;
437 let other_above = other.viewport_width >= *bp;
438 if self_above != other_above {
439 return true;
440 }
441 }
442 false
443 }
444}
445
446impl DynamicSelector {
447 pub fn matches(&self, ctx: &DynamicSelectorContext) -> bool {
449 match self {
450 Self::Os(os) => Self::match_os(*os, ctx.os),
451 Self::OsVersion(ver) => Self::match_os_version(ver, &ctx.os_version, &ctx.desktop_env),
452 Self::Media(media) => *media == ctx.media_type || *media == MediaType::All,
453 Self::ViewportWidth(range) => range.matches(ctx.viewport_width),
454 Self::ViewportHeight(range) => range.matches(ctx.viewport_height),
455 Self::ContainerWidth(range) => {
456 !ctx.container_width.is_nan() && range.matches(ctx.container_width)
457 }
458 Self::ContainerHeight(range) => {
459 !ctx.container_height.is_nan() && range.matches(ctx.container_height)
460 }
461 Self::ContainerName(name) => ctx.container_name.as_ref().map_or(false, |n| n == name),
462 Self::Theme(theme) => Self::match_theme(theme, &ctx.theme),
463 Self::AspectRatio(range) => {
464 let ratio = ctx.viewport_width / ctx.viewport_height.max(1.0);
465 range.matches(ratio)
466 }
467 Self::Orientation(orient) => *orient == ctx.orientation,
468 Self::PrefersReducedMotion(pref) => {
469 bool::from(*pref) == bool::from(ctx.prefers_reduced_motion)
470 }
471 Self::PrefersHighContrast(pref) => {
472 bool::from(*pref) == bool::from(ctx.prefers_high_contrast)
473 }
474 Self::PseudoState(state) => Self::match_pseudo_state(*state, &ctx.pseudo_state),
475 Self::Language(lang_cond) => lang_cond.matches(ctx.language.as_str()),
476 }
477 }
478
479 fn match_os(condition: OsCondition, actual: OsCondition) -> bool {
480 match condition {
481 OsCondition::Any => true,
482 OsCondition::Apple => matches!(actual, OsCondition::MacOS | OsCondition::IOS),
483 _ => condition == actual,
484 }
485 }
486
487 fn match_os_version(
488 condition: &OsVersionCondition,
489 actual: &AzString,
490 desktop_env: &OptionLinuxDesktopEnv,
491 ) -> bool {
492 match condition {
493 OsVersionCondition::Exact(ver) => ver == actual,
494 OsVersionCondition::Min(ver) => Self::compare_version(actual, ver) >= 0,
495 OsVersionCondition::Max(ver) => Self::compare_version(actual, ver) <= 0,
496 OsVersionCondition::DesktopEnvironment(env) => {
497 desktop_env.as_ref().map_or(false, |de| de == env)
498 }
499 }
500 }
501
502 fn compare_version(a: &AzString, b: &AzString) -> i32 {
503 a.as_str().cmp(b.as_str()) as i32
506 }
507
508 fn match_theme(condition: &ThemeCondition, actual: &ThemeCondition) -> bool {
509 match (condition, actual) {
510 (ThemeCondition::SystemPreferred, _) => true,
511 _ => condition == actual,
512 }
513 }
514
515 fn match_pseudo_state(state: PseudoStateType, node_state: &PseudoStateFlags) -> bool {
516 match state {
517 PseudoStateType::Normal => true, PseudoStateType::Hover => node_state.hover,
519 PseudoStateType::Active => node_state.active,
520 PseudoStateType::Focus => node_state.focused,
521 PseudoStateType::Disabled => node_state.disabled,
522 PseudoStateType::Checked => node_state.checked,
523 PseudoStateType::FocusWithin => node_state.focus_within,
524 PseudoStateType::Visited => node_state.visited,
525 }
526 }
527}
528
529#[repr(C)]
539#[derive(Debug, Clone, PartialEq)]
540pub struct CssPropertyWithConditions {
541 pub property: CssProperty,
543 pub apply_if: DynamicSelectorVec,
546}
547
548impl Eq for CssPropertyWithConditions {}
549
550impl PartialOrd for CssPropertyWithConditions {
551 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
552 Some(self.cmp(other))
553 }
554}
555
556impl Ord for CssPropertyWithConditions {
557 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
558 self.apply_if
560 .as_slice()
561 .len()
562 .cmp(&other.apply_if.as_slice().len())
563 }
564}
565
566impl CssPropertyWithConditions {
567 pub const fn simple(property: CssProperty) -> Self {
569 Self {
570 property,
571 apply_if: DynamicSelectorVec::from_const_slice(&[]),
572 }
573 }
574
575 pub const fn with_single_condition(
577 property: CssProperty,
578 conditions: &'static [DynamicSelector],
579 ) -> Self {
580 Self {
581 property,
582 apply_if: DynamicSelectorVec::from_const_slice(conditions),
583 }
584 }
585
586 pub fn with_condition(property: CssProperty, condition: DynamicSelector) -> Self {
588 Self {
589 property,
590 apply_if: DynamicSelectorVec::from_vec(vec![condition]),
591 }
592 }
593
594 pub const fn with_conditions(property: CssProperty, conditions: DynamicSelectorVec) -> Self {
596 Self {
597 property,
598 apply_if: conditions,
599 }
600 }
601
602 pub const fn on_hover(property: CssProperty) -> Self {
604 Self::with_single_condition(
605 property,
606 &[DynamicSelector::PseudoState(PseudoStateType::Hover)],
607 )
608 }
609
610 pub const fn on_active(property: CssProperty) -> Self {
612 Self::with_single_condition(
613 property,
614 &[DynamicSelector::PseudoState(PseudoStateType::Active)],
615 )
616 }
617
618 pub const fn on_focus(property: CssProperty) -> Self {
620 Self::with_single_condition(
621 property,
622 &[DynamicSelector::PseudoState(PseudoStateType::Focus)],
623 )
624 }
625
626 pub const fn when_disabled(property: CssProperty) -> Self {
628 Self::with_single_condition(
629 property,
630 &[DynamicSelector::PseudoState(PseudoStateType::Disabled)],
631 )
632 }
633
634 pub fn on_os(property: CssProperty, os: OsCondition) -> Self {
636 Self::with_condition(property, DynamicSelector::Os(os))
637 }
638
639 pub const fn dark_theme(property: CssProperty) -> Self {
641 Self::with_single_condition(property, &[DynamicSelector::Theme(ThemeCondition::Dark)])
642 }
643
644 pub const fn light_theme(property: CssProperty) -> Self {
646 Self::with_single_condition(property, &[DynamicSelector::Theme(ThemeCondition::Light)])
647 }
648
649 pub const fn on_windows(property: CssProperty) -> Self {
651 Self::with_single_condition(property, &[DynamicSelector::Os(OsCondition::Windows)])
652 }
653
654 pub const fn on_macos(property: CssProperty) -> Self {
656 Self::with_single_condition(property, &[DynamicSelector::Os(OsCondition::MacOS)])
657 }
658
659 pub const fn on_linux(property: CssProperty) -> Self {
661 Self::with_single_condition(property, &[DynamicSelector::Os(OsCondition::Linux)])
662 }
663
664 pub fn matches(&self, ctx: &DynamicSelectorContext) -> bool {
666 if self.apply_if.as_slice().is_empty() {
668 return true;
669 }
670
671 self.apply_if
673 .as_slice()
674 .iter()
675 .all(|selector| selector.matches(ctx))
676 }
677
678 pub fn is_conditional(&self) -> bool {
680 !self.apply_if.as_slice().is_empty()
681 }
682
683 pub fn is_pseudo_state_only(&self) -> bool {
686 let conditions = self.apply_if.as_slice();
687 !conditions.is_empty()
688 && conditions
689 .iter()
690 .all(|c| matches!(c, DynamicSelector::PseudoState(_)))
691 }
692
693 pub fn is_layout_affecting(&self) -> bool {
696 true
699 }
700}
701
702impl_vec!(
703 CssPropertyWithConditions,
704 CssPropertyWithConditionsVec,
705 CssPropertyWithConditionsVecDestructor,
706 CssPropertyWithConditionsVecDestructorType
707);
708impl_vec_debug!(CssPropertyWithConditions, CssPropertyWithConditionsVec);
709impl_vec_partialeq!(CssPropertyWithConditions, CssPropertyWithConditionsVec);
710impl_vec_partialord!(CssPropertyWithConditions, CssPropertyWithConditionsVec);
711impl_vec_clone!(
712 CssPropertyWithConditions,
713 CssPropertyWithConditionsVec,
714 CssPropertyWithConditionsVecDestructor
715);
716
717impl Eq for CssPropertyWithConditionsVec {}
719
720impl Ord for CssPropertyWithConditionsVec {
721 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
722 self.as_slice().len().cmp(&other.as_slice().len())
723 }
724}
725
726impl core::hash::Hash for CssPropertyWithConditions {
727 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
728 self.property.hash(state);
729 self.apply_if.as_slice().len().hash(state);
731 }
732}
733
734impl core::hash::Hash for CssPropertyWithConditionsVec {
735 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
736 for item in self.as_slice() {
737 item.hash(state);
738 }
739 }
740}
741
742impl CssPropertyWithConditionsVec {
743 #[cfg(feature = "parser")]
745 pub fn parse_normal(style: &str) -> Self {
746 use crate::props::property::{
747 parse_combined_css_property, parse_css_property, CombinedCssPropertyType, CssKeyMap,
748 CssPropertyType,
749 };
750
751 let mut props = Vec::new();
752 let key_map = CssKeyMap::get();
753
754 for pair in style.split(';') {
756 let pair = pair.trim();
757 if pair.is_empty() {
758 continue;
759 }
760 if let Some((key, value)) = pair.split_once(':') {
761 let key = key.trim();
762 let value = value.trim();
763 if let Some(prop_type) = CssPropertyType::from_str(key, &key_map) {
765 if let Ok(prop) = parse_css_property(prop_type, value) {
766 props.push(CssPropertyWithConditions::simple(prop));
767 continue;
768 }
769 }
770 if let Some(combined_type) = CombinedCssPropertyType::from_str(key, &key_map) {
772 if let Ok(expanded_props) = parse_combined_css_property(combined_type, value) {
773 for prop in expanded_props {
774 props.push(CssPropertyWithConditions::simple(prop));
775 }
776 }
777 }
778 }
779 }
780
781 CssPropertyWithConditionsVec::from_vec(props)
782 }
783
784 #[cfg(feature = "parser")]
786 pub fn parse_hover(style: &str) -> Self {
787 use crate::props::property::{
788 parse_combined_css_property, parse_css_property, CombinedCssPropertyType, CssKeyMap,
789 CssPropertyType,
790 };
791
792 let mut props = Vec::new();
793 let key_map = CssKeyMap::get();
794
795 for pair in style.split(';') {
796 let pair = pair.trim();
797 if pair.is_empty() {
798 continue;
799 }
800 if let Some((key, value)) = pair.split_once(':') {
801 let key = key.trim();
802 let value = value.trim();
803 if let Some(prop_type) = CssPropertyType::from_str(key, &key_map) {
805 if let Ok(prop) = parse_css_property(prop_type, value) {
806 props.push(CssPropertyWithConditions::on_hover(prop));
807 continue;
808 }
809 }
810 if let Some(combined_type) = CombinedCssPropertyType::from_str(key, &key_map) {
812 if let Ok(expanded_props) = parse_combined_css_property(combined_type, value) {
813 for prop in expanded_props {
814 props.push(CssPropertyWithConditions::on_hover(prop));
815 }
816 }
817 }
818 }
819 }
820
821 CssPropertyWithConditionsVec::from_vec(props)
822 }
823
824 #[cfg(feature = "parser")]
826 pub fn parse_active(style: &str) -> Self {
827 use crate::props::property::{
828 parse_combined_css_property, parse_css_property, CombinedCssPropertyType, CssKeyMap,
829 CssPropertyType,
830 };
831
832 let mut props = Vec::new();
833 let key_map = CssKeyMap::get();
834
835 for pair in style.split(';') {
836 let pair = pair.trim();
837 if pair.is_empty() {
838 continue;
839 }
840 if let Some((key, value)) = pair.split_once(':') {
841 let key = key.trim();
842 let value = value.trim();
843 if let Some(prop_type) = CssPropertyType::from_str(key, &key_map) {
845 if let Ok(prop) = parse_css_property(prop_type, value) {
846 props.push(CssPropertyWithConditions::on_active(prop));
847 continue;
848 }
849 }
850 if let Some(combined_type) = CombinedCssPropertyType::from_str(key, &key_map) {
852 if let Ok(expanded_props) = parse_combined_css_property(combined_type, value) {
853 for prop in expanded_props {
854 props.push(CssPropertyWithConditions::on_active(prop));
855 }
856 }
857 }
858 }
859 }
860
861 CssPropertyWithConditionsVec::from_vec(props)
862 }
863
864 #[cfg(feature = "parser")]
866 pub fn parse_focus(style: &str) -> Self {
867 use crate::props::property::{
868 parse_combined_css_property, parse_css_property, CombinedCssPropertyType, CssKeyMap,
869 CssPropertyType,
870 };
871
872 let mut props = Vec::new();
873 let key_map = CssKeyMap::get();
874
875 for pair in style.split(';') {
876 let pair = pair.trim();
877 if pair.is_empty() {
878 continue;
879 }
880 if let Some((key, value)) = pair.split_once(':') {
881 let key = key.trim();
882 let value = value.trim();
883 if let Some(prop_type) = CssPropertyType::from_str(key, &key_map) {
885 if let Ok(prop) = parse_css_property(prop_type, value) {
886 props.push(CssPropertyWithConditions::on_focus(prop));
887 continue;
888 }
889 }
890 if let Some(combined_type) = CombinedCssPropertyType::from_str(key, &key_map) {
892 if let Ok(expanded_props) = parse_combined_css_property(combined_type, value) {
893 for prop in expanded_props {
894 props.push(CssPropertyWithConditions::on_focus(prop));
895 }
896 }
897 }
898 }
899 }
900
901 CssPropertyWithConditionsVec::from_vec(props)
902 }
903}