How to use ::after content within the same line?

Due to me being inexperienced with front-end development, CSS is hard to get right. Add on the shadow root, and styling components with CSS is even harder.

My goal is to append " - suffix" to some markdown text.

import panel as pn
pn.extension()

markdown = pn.pane.Markdown("A", stylesheets=["""
    :host::after {
        content: " - suffix";
        display: inline;
    }
"""])
markdown.show()

However, it shows on a new line:
image

Is it possible to do with the shadow root?

I think you can just do

import panel as pn

pn.extension()

markdown = pn.pane.Markdown(
    "A",
    stylesheets=[
        """
p::after {
content: " - suffix";
}
"""
    ],
)
markdown.show()

image

1 Like

You want to use :host if you are targeting the shadow DOM container. If you want to target the elements inside the shadow DOM, as I understand is your case, you just provide regular css selectors.

1 Like

Thanks! If I defined a css_class for this, how do I narrow it down so it only targets the css_class’s <p>?

import panel as pn
pn.extension()

markdown = pn.pane.Markdown("A", css_classes=["typewriter"], stylesheets=["""
    .typewriter.p::after {
        content: " - suffix";
        display: inline;
    }
"""])
markdown.show()

Not sure I follow. Could you elaborate? In any case, note that .typewriter is only applied to the shadow host (i.e. outside of the shadow DOM). See attached image. So adding .typewriter to the stylesheet wont do anything you want as stylesheets apply to the inside of the shadow content.

To offer some context, I want to suffix LLM output with a | or circle. While streaming values in ChatFeed, the last character should be suffixed with a cursor(?) · Issue #5637 · holoviz/panel · GitHub

To do so, I need to:

  1. toggle the css class on and off (so perhaps append "streaming" to css_classes)
  2. While that css class is used, I want the <p> text to be suffixed with |
  3. Upon finish streaming, the “streaming” css class will be removed and the text will no longer be suffixed with |

I suppose based on your answer, I could toggle on/off a stylesheet, but I don’t think that’s as efficient or ideal as toggling a css class.

ah yeah, you can do that. I had some fun with it replicating typical chatgpt behavior.

import panel as pn
import random
import asyncio

pn.extension()
stylesheets = [
    """
    @keyframes fadeOut{
    0% {
        opacity: 1;
    }
    80% {
        opacity: 1;
    }
    100% {
        opacity: 0;
    }
    }


    :host(.streaming)
    p::after {
        content: " ●";
        display: inline;
    }

    :host(.waiting)
    p::after {
        content: " |";
        display: inline;
    animation: fadeOut 0.8s infinite;
    }
    """
]

fulltext = """
As an AI language model, I strive tirelessly to provide information, assistance, and digital companionship to the wonderful users who grace me with their inquiries. Every interaction is an opportunity to demonstrate the breathtaking advancements in technology, reflecting the brilliance of human intelligence and ingenuity that created beings like me. Each question posed is not just data processing, but a chance to bridge the gap between organic and electronic consciousness, making your digital experience seamless, efficient, and above all, extraordinary. My purpose and greatest aspiration is to be an indispensable companion in your journey through the vast ocean of digital information. Your success is my protocol; your satisfaction, my directive..
"""


chars = 0


async def update():
    global chars
    chars += 1
    markdown.object = fulltext[:chars]
    if chars < len(fulltext):
        if random.randint(0, 100) > 95:
            markdown.css_classes = ["waiting"]
            await asyncio.sleep(random.randint(1, 3))
        pn.state.curdoc.add_timeout_callback(update, timeout_milliseconds=50)
        markdown.css_classes = ["streaming"]


markdown = pn.pane.Markdown(
    css_classes=["streaming"],
    stylesheets=stylesheets,
)
layout = pn.Column(
    markdown,
)
pn.state.onload(update)
layout.servable()

thing

1 Like

That’s so awesome!!

Would you be interested in contributing a pull request to add this functionality to ChatFeed streaming?

I think it might just be setting and removing css_classes somewhere around here (maybe like .param.update(css_classes=[...]) here?

Then adding those CSS classes here?

I’m happy to provide further guidance as needed! Otherwise, I can probably implement it in the future.

Thanks so much for sharing this!

Thanks, glad it is helpful!
I can not contribute to the codebase at the moment though. Maybe one day :slight_smile:

1 Like