diff --git a/packages/fcl-css/src/fpcssresolver.pas b/packages/fcl-css/src/fpcssresolver.pas index 4600f9725b..3937dfb3f1 100644 --- a/packages/fcl-css/src/fpcssresolver.pas +++ b/packages/fcl-css/src/fpcssresolver.pas @@ -135,14 +135,14 @@ const CSSPseudoID_FirstOfType = CSSPseudoID_OnlyChild+1; // :first-of-type CSSPseudoID_LastOfType = CSSPseudoID_FirstOfType+1; // :last-of-type CSSPseudoID_OnlyOfType = CSSPseudoID_LastOfType+1; // :only-of-type - CSSCallID_Not = CSSPseudoID_OnlyOfType+1; // :nth-child - CSSCallID_Is = CSSCallID_Not+1; // :nth-child - CSSCallID_Where = CSSCallID_Is+1; // :nth-child - CSSCallID_Has = CSSCallID_Where+1; // :nth-child - CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child - CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-child - CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-child - CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-child + CSSCallID_Not = CSSPseudoID_OnlyOfType+1; // :not() + CSSCallID_Is = CSSCallID_Not+1; // :is() + CSSCallID_Where = CSSCallID_Is+1; // :where() + CSSCallID_Has = CSSCallID_Where+1; // :has() + CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child(n) + CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-last-child(n) + CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-of-type(n) + CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-last-of-type(n) CSSLastPseudoID = CSSCallID_NthLastOfType; const @@ -201,8 +201,7 @@ type function GetCSSPreviousOfType: ICSSNode; function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean; function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString; - function HasCSSPseudo(const AttrID: TCSSNumericalID): boolean; - function GetCSSPseudo(const AttrID: TCSSNumericalID): TCSSString; + function HasCSSPseudoClass(const AttrID: TCSSNumericalID): boolean; function GetCSSEmpty: boolean; function GetCSSDepth: integer; procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement); @@ -213,7 +212,7 @@ type TCSSNumericalIDKind = ( nikType, nikAttribute, - nikPseudoAttribute + nikPseudoClass ); TCSSNumericalIDKinds = set of TCSSNumericalIDKind; @@ -221,7 +220,7 @@ const CSSNumericalIDKindNames: array[TCSSNumericalIDKind] of TCSSString = ( 'Type', 'Attribute', - 'PseudoAttribute' + 'PseudoClass' ); type @@ -732,7 +731,7 @@ begin if OnlySpecifity then exit(CSSSpecifityClass); Result:=CSSSpecifityNoMatch; - PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoAttribute); + PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoClass); case PseudoID of CSSIDNone: LogWarning(croErrorOnUnknownName in Options,20220911205605,'Unknown CSS selector pseudo attribute name "'+aPseudoClass.Name+'"',aPseudoClass); @@ -763,7 +762,7 @@ begin and (TestNode.GetCSSPreviousOfType=nil) then Result:=CSSSpecifityClass; else - if TestNode.GetCSSPseudo(PseudoID)<>'' then + if TestNode.HasCSSPseudoClass(PseudoID) then Result:=CSSSpecifityClass; end; end; @@ -1763,7 +1762,7 @@ begin 'class': Result:=CSSAttributeID_Class; 'all': Result:=CSSAttributeID_All; end; - nikPseudoAttribute: + nikPseudoClass: begin aName:=lowercase(aName); // pseudo attributes are ASCII case insensitive case aName of diff --git a/packages/fcl-css/tests/tccssresolver.pp b/packages/fcl-css/tests/tccssresolver.pp index 1326ba8842..198f0ee71b 100644 --- a/packages/fcl-css/tests/tccssresolver.pp +++ b/packages/fcl-css/tests/tccssresolver.pp @@ -47,6 +47,7 @@ const ); DemoAttrIDBase = 100; + DemoPseudoClassIDBase = 100; type TDemoPseudoClass = ( @@ -55,6 +56,13 @@ type ); TDemoPseudoClasses = set of TDemoPseudoClass; +const + DemoPseudoClassNames: array[TDemoPseudoClass] of string = ( + // case sensitive! + ':active', + ':hover' + ); + type { TDemoNode } @@ -63,7 +71,9 @@ type private class var FAttributeInitialValues: array[TDemoNodeAttribute] of string; private + FActive: boolean; FAttributeValues: array[TDemoNodeAttribute] of string; + FHover: boolean; FNodes: TFPObjectList; // list of TDemoNode FCSSClasses: TStrings; FParent: TDemoNode; @@ -72,7 +82,9 @@ type function GetAttribute(AIndex: TDemoNodeAttribute): string; function GetNodeCount: integer; function GetNodes(Index: integer): TDemoNode; + procedure SetActive(const AValue: boolean); procedure SetAttribute(AIndex: TDemoNodeAttribute; const AValue: string); + procedure SetHover(const AValue: boolean); procedure SetParent(const AValue: TDemoNode); procedure SetStyleElements(const AValue: TCSSElement); procedure SetStyle(const AValue: string); @@ -104,8 +116,7 @@ type function GetCSSAttributeClass: TCSSString; virtual; function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean; virtual; function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual; - function HasCSSPseudo(const {%H-}AttrID: TCSSNumericalID): boolean; virtual; - function GetCSSPseudo(const {%H-}AttrID: TCSSNumericalID): TCSSString; virtual; + function HasCSSPseudoClass(const {%H-}AttrID: TCSSNumericalID): boolean; virtual; function GetCSSEmpty: boolean; virtual; function GetCSSDepth: integer; virtual; property Parent: TDemoNode read FParent write SetParent; @@ -123,6 +134,10 @@ type property Display: string index naDisplay read GetAttribute write SetAttribute; property Color: string index naColor read GetAttribute write SetAttribute; property Attribute[Attr: TDemoNodeAttribute]: string read GetAttribute write SetAttribute; + // CSS pseudo classes + property Active: boolean read FActive write SetActive; + property Hover: boolean read FHover write SetHover; + function HasPseudoClass(PseudoClass: TDemoPseudoClass): boolean; end; TDemoNodeClass = class of TDemoNode; @@ -208,9 +223,9 @@ type // Test list spaces "div, button ,span {}" procedure Test_Selector_Id; procedure Test_Selector_Class; - procedure Test_Selector_ClassClass; // ToDo and combinator - procedure Test_Selector_ClassSpaceClass; // ToDo descendant combinator - procedure Test_Selector_TypeCommaType; // or combinator + procedure Test_Selector_ClassClass; // AND combinator + procedure Test_Selector_ClassSpaceClass; // Descendant combinator + procedure Test_Selector_TypeCommaType; // OR combinator procedure Test_Selector_ClassGTClass; // child combinator procedure Test_Selector_TypePlusType; // adjacent sibling combinator procedure Test_Selector_TypeTildeType; // general sibling combinator @@ -224,7 +239,7 @@ type procedure Test_Selector_AttributeContainsSubstring; // ToDo: "all" - // pseudo attributes + // pseudo classes procedure Test_Selector_Root; procedure Test_Selector_Empty; procedure Test_Selector_FirstChild; @@ -243,8 +258,12 @@ type procedure Test_Selector_Where; // ToDo: div:has(>img) // ToDo: div:has(+img) + // ToDo: :dir() // ToDo: :lang() + // custom pseudo classes + procedure Test_Selector_Hover; + // inline style procedure Test_InlineStyle; @@ -1232,6 +1251,39 @@ begin AssertEquals('Div2.Left','2px',Div2.Left); end; +procedure TTestCSSResolver.Test_Selector_Hover; +var + Div1, Div11: TDemoDiv; + Button1: TDemoButton; +begin + Doc.Root:=TDemoNode.Create(nil); + + Div1:=TDemoDiv.Create(Doc); + Div1.Parent:=Doc.Root; + Div1.Hover:=true; + + Button1:=TDemoButton.Create(Doc); + Button1.Parent:=Div1; + Button1.Hover:=true; + + Div11:=TDemoDiv.Create(Doc); + Div11.Parent:=Div1; + + Doc.Style:=LinesToStr([ + ':hover { left: 1px; }', + 'button:hover { top: 2px; }', + '']); + Doc.ApplyStyle; + AssertEquals('Root.Left','',Doc.Root.Left); + AssertEquals('Root.Top','',Doc.Root.Top); + AssertEquals('Div1.Left','1px',Div1.Left); + AssertEquals('Div1.Top','',Div1.Top); + AssertEquals('Button1.Left','1px',Button1.Left); + AssertEquals('Button1.Top','2px',Button1.Top); + AssertEquals('Div11.Left','',Div11.Left); + AssertEquals('Div11.Top','',Div11.Top); +end; + procedure TTestCSSResolver.Test_InlineStyle; var Div1: TDemoDiv; @@ -1356,14 +1408,17 @@ end; constructor TDemoDocument.Create(AOwner: TComponent); var Attr: TDemoNodeAttribute; - TypeIDs, AttributeIDs: TCSSNumericalIDs; + TypeIDs, AttributeIDs, PseudoClassIDs: TCSSNumericalIDs; NumKind: TCSSNumericalIDKind; AttrID: TCSSNumericalID; + PseudoClass: TDemoPseudoClass; begin inherited Create(AOwner); for NumKind in TCSSNumericalIDKind do FNumericalIDs[NumKind]:=TCSSNumericalIDs.Create(NumKind); + + // register all css types TypeIDs:=FNumericalIDs[nikType]; TypeIDs['*']:=CSSTypeID_Universal; if TypeIDs['*']<>CSSTypeID_Universal then @@ -1373,22 +1428,38 @@ begin TypeIDs[TDemoDiv.CSSTypeName]:=TDemoDiv.CSSTypeID; TypeIDs[TDemoButton.CSSTypeName]:=TDemoButton.CSSTypeID; + // register all css attribute AttributeIDs:=FNumericalIDs[nikAttribute]; AttributeIDs['all']:=CSSAttributeID_All; + // add basic element attributes AttrID:=DemoAttrIDBase; for Attr in TDemoNodeAttribute do begin AttributeIDs[DemoAttributeNames[Attr]]:=AttrID; inc(AttrID); end; + // add button caption attribute TDemoButton.CSSCaptionID:=AttrID; AttributeIDs['caption']:=AttrID; inc(AttrID); + // register css pseudo attributes + PseudoClassIDs:=FNumericalIDs[nikPseudoClass]; + AttrID:=DemoPseudoClassIDBase; + for PseudoClass in TDemoPseudoClass do + begin + PseudoClassIDs[DemoPseudoClassNames[PseudoClass]]:=AttrID; + inc(AttrID); + end; + if PseudoClassIDs[DemoPseudoClassNames[pcHover]]<>DemoPseudoClassIDBase+ord(pcHover) then + raise Exception.Create('20231008232201'); + + // create the css resolver FCSSResolver:=TCSSResolver.Create(nil); for NumKind in TCSSNumericalIDKind do CSSResolver.NumericalIDs[NumKind]:=FNumericalIDs[NumKind]; + // create a demo root node Root:=TDemoNode.Create(Self); Root.Name:='Root'; end; @@ -1453,6 +1524,12 @@ begin FAttributeValues[AIndex]:=AValue; end; +procedure TDemoNode.SetHover(const AValue: boolean); +begin + if FHover=AValue then Exit; + FHover:=AValue; +end; + procedure TDemoNode.SetParent(const AValue: TDemoNode); begin if FParent=AValue then Exit; @@ -1471,6 +1548,12 @@ begin end; end; +procedure TDemoNode.SetActive(const AValue: boolean); +begin + if FActive=AValue then Exit; + FActive:=AValue; +end; + procedure TDemoNode.SetStyleElements(const AValue: TCSSElement); begin if FStyleElements=AValue then Exit; @@ -1701,16 +1784,12 @@ begin Result:=Attribute[Attr]; end; -function TDemoNode.HasCSSPseudo(const AttrID: TCSSNumericalID - ): boolean; +function TDemoNode.HasCSSPseudoClass(const AttrID: TCSSNumericalID): boolean; begin - Result:=false; -end; - -function TDemoNode.GetCSSPseudo(const AttrID: TCSSNumericalID - ): TCSSString; -begin - Result:=''; + if (AttrID>=DemoPseudoClassIDBase) and (AttrID<=DemoPseudoClassIDBase+ord(High(TDemoPseudoClass))) then + Result:=HasPseudoClass(TDemoPseudoClass(AttrID-DemoPseudoClassIDBase)) + else + Result:=false; end; function TDemoNode.GetCSSEmpty: boolean; @@ -1731,6 +1810,14 @@ begin end; end; +function TDemoNode.HasPseudoClass(PseudoClass: TDemoPseudoClass): boolean; +begin + case PseudoClass of + pcActive: Result:=Active; + pcHover: Result:=Hover; + end; +end; + function TDemoNode.GetCSSTypeName: TCSSString; begin Result:=CSSTypeName; diff --git a/packages/fcl-css/tests/testcss.lpi b/packages/fcl-css/tests/testcss.lpi index 5ef264d31a..f988ba558f 100644 --- a/packages/fcl-css/tests/testcss.lpi +++ b/packages/fcl-css/tests/testcss.lpi @@ -45,9 +45,6 @@ - - -