diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberHostContext.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberHostContext.java
new file mode 100644
index 00000000..fc0df538
--- /dev/null
+++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberHostContext.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014-2022 Philip Helger (www.helger.com)
+ * philip[at]helger[dot]com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.helger.css.decl;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import com.helger.commons.ValueEnforcer;
+import com.helger.commons.annotation.Nonempty;
+import com.helger.commons.hashcode.HashCodeGenerator;
+import com.helger.commons.string.ToStringGenerator;
+import com.helger.css.CSSSourceLocation;
+import com.helger.css.ECSSVersion;
+import com.helger.css.ICSSSourceLocationAware;
+import com.helger.css.ICSSVersionAware;
+import com.helger.css.ICSSWriterSettings;
+
+/**
+ * Represents a single, simple CSS selector as used for the ":host-context()" CSS pseudo
+ * class function.
+ * Note: this class was completely redesigned for version 3.7.4
+ *
+ * @author Philip Helger
+ */
+@NotThreadSafe
+public class CSSSelectorMemberHostContext implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware
+{
+ private final CSSSelector m_aSelector;
+ private CSSSourceLocation m_aSourceLocation;
+
+ public CSSSelectorMemberHostContext (@Nonnull final CSSSelector aSimpleSelector)
+ {
+ ValueEnforcer.notNull (aSimpleSelector, "SimpleSelector");
+ m_aSelector = aSimpleSelector;
+ }
+
+ @Nonnull
+ public final CSSSelector getSelector ()
+ {
+ return m_aSelector;
+ }
+
+ @Nonnull
+ @Nonempty
+ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel)
+ {
+ aSettings.checkVersionRequirements (this);
+
+ final StringBuilder aSB = new StringBuilder (":host-context(");
+ aSB.append (m_aSelector.getAsCSSString (aSettings, 0));
+ return aSB.append (')').toString ();
+ }
+
+ @Nonnull
+ public ECSSVersion getMinimumCSSVersion ()
+ {
+ return ECSSVersion.CSS30;
+ }
+
+ @Nullable
+ public final CSSSourceLocation getSourceLocation ()
+ {
+ return m_aSourceLocation;
+ }
+
+ public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation)
+ {
+ m_aSourceLocation = aSourceLocation;
+ }
+
+ @Override
+ public boolean equals (final Object o)
+ {
+ if (o == this)
+ return true;
+ if (o == null || !getClass ().equals (o.getClass ()))
+ return false;
+ final CSSSelectorMemberHostContext rhs = (CSSSelectorMemberHostContext) o;
+ return m_aSelector.equals (rhs.m_aSelector);
+ }
+
+ @Override
+ public int hashCode ()
+ {
+ return new HashCodeGenerator (this).append (m_aSelector).getHashCode ();
+ }
+
+ @Override
+ public String toString ()
+ {
+ return new ToStringGenerator (null).append ("Selector", m_aSelector)
+ .appendIfNotNull ("SourceLocation", m_aSourceLocation)
+ .getToString ();
+ }
+}
diff --git a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java
index f7450df1..d2d64485 100644
--- a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java
+++ b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java
@@ -285,6 +285,18 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode)
return ret;
}
+ if (ECSSNodeType.HOSTCONTEXT.isNode (aChildNode, m_eVersion))
+ {
+ final CSSSelector aSelector = new CSSSelector ();
+ final int nChildChildCount = aChildNode.jjtGetNumChildren ();
+ for (int j = 0; j < nChildChildCount; ++j)
+ aSelector.addMember (_createSelectorMember (aChildNode.jjtGetChild (j)));
+ final CSSSelectorMemberHostContext ret = new CSSSelectorMemberHostContext (aSelector);
+ if (m_bUseSourceLocation)
+ ret.setSourceLocation (aNode.getSourceLocation ());
+ return ret;
+ }
+
if (ECSSNodeType.SLOTTED.isNode (aChildNode, m_eVersion))
{
final CSSSelector aSelector = new CSSSelector ();
diff --git a/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java b/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java
index c6518dd6..1daf19c8 100644
--- a/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java
+++ b/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java
@@ -58,6 +58,7 @@ public enum ECSSNodeType
CLASS (ParserCSS30TreeConstants.JJTCLASS),
PSEUDO (ParserCSS30TreeConstants.JJTPSEUDO),
HOST (ParserCSS30TreeConstants.JJTHOST),
+ HOSTCONTEXT (ParserCSS30TreeConstants.JJTHOSTCONTEXT),
SLOTTED (ParserCSS30TreeConstants.JJTSLOTTED),
NEGATION (ParserCSS30TreeConstants.JJTNEGATION),
ATTRIB (ParserCSS30TreeConstants.JJTATTRIB),
diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt
index 9e308004..ab6e9ea0 100644
--- a/ph-css/src/main/jjtree/ParserCSS30.jjt
+++ b/ph-css/src/main/jjtree/ParserCSS30.jjt
@@ -302,6 +302,7 @@ TOKEN :
| "-" "-calc(" >
| < FUNCTION_NOT: ":not(" >
| < FUNCTION_HOST: "host(" >
+| < FUNCTION_HOSTCONTEXT: "host-context(" >
| < FUNCTION_SLOTTED: "slotted(" >
| < FUNCTION_NTH: "nth-child("
| "nth-last-child("
@@ -1020,6 +1021,14 @@ void pseudoHost () #host : {}
)*
}
+void pseudoHostContext () #hostcontext : {}
+{
+ ( )*
+ ( simpleSelectorSequence ()
+ ( )*
+ )*
+}
+
void pseudoSlotted () #slotted : {}
{
( )*
@@ -1039,6 +1048,9 @@ void pseudo() : {}
| { jjtThis.appendText (token.image); }
pseudoHost()
// do not append because of expression!
+ | { jjtThis.appendText (token.image); }
+ pseudoHostContext()
+ // do not append because of expression!
| { jjtThis.appendText (token.image); }
pseudoSlotted()
// do not append because of expression!
diff --git a/ph-css/src/test/resources/testfiles/css30/good/issue80.css b/ph-css/src/test/resources/testfiles/css30/good/issue80.css
new file mode 100644
index 00000000..bbfbe3e5
--- /dev/null
+++ b/ph-css/src/test/resources/testfiles/css30/good/issue80.css
@@ -0,0 +1,23 @@
+/* Selects a shadow root host, only if it is
+ a descendant of the selector argument given */
+:host-context(h1) {
+ font-weight: bold;
+}
+
+:host-context(.main .article) {
+ font-weight: bold;
+}
+
+/* Changes paragraph text color from black to white when
+ a .dark-theme class is applied to the document body */
+p {
+ color: #000;
+}
+
+:host-context(body.dark-theme) p {
+ color: #fff;
+}
+
+:host-context([dir="rtl"]) {
+ float: right !important;
+}