bkardell / half-light

Small library for experimenting with ways to tame Shadow DOM in CSS
Apache License 2.0
44 stars 1 forks source link

no-styles? half-shadow? #12

Open bkardell opened 9 months ago

bkardell commented 9 months ago

While you could argue that what half-light does this would be best as it's own { mode: 'open-styleable' } or OpenStyleableElement base class or something, I didn't do that intentionally, for reasons. I just don't think it is pragmatic. As the page author I am ultimately in control whether you want me to be or not... At least for open shadow roots, it is very easily possible to do what half-light is doing today via a little JavaScript so it seems hard to argue that this is a whole new mode.

What I want, as a page author is mainly to not have to figure that out, and to make that a little sweeter by doing it in/via CSS in a way lots of others can potentially understand. Establishing a kind of pattern or protocol for this which uses @layers and lets (but doesn't force) component authors to work with that advice more creatively seems good.

In discussions, I've mentioned that closed shadow roots are already a kind we can't cross, and at least currently half-light does nothing about.. To do so without an author's knowledge definitely does seem like it is changing something about the contract.

However, @sorvell hit upon the idea of allowing authors to explicitly say "No thank you, I would actively not like your layer", even with half-light. That... seems pretty reasonable to me actually. If we do this by simply looking for some well known property name then anyone can add a single line of code to their custom elements without having any kind of actual dependency on half-light, but signal that they really don't want that if someone is using it. Of course, they can't prevent people from doing it themselves with JS, but at least we could respect it. Is this a good idea? I think maybe we could just add support for a static property on your class that is like reject-style-layer - and if it's truthy then we don't give it styles.

It strikes me too though that the inverse might be true as well though. For example, there might be people developing a closed root who would feel pretty fine actually accepting a style layer. I might be one of them infact - I have some shorthand components: markdown, for example, which mostly not at all "secrets" - I'm pleased to accept advice for what an <h1> should look like, for example. That is probably something we could potentially explore with a new base class? It's a lot more like Nolan's original solution, but I think we can just develop a protocol around it so that it's still expressed the same way it is in half-light, but could be accepted. I think that would be a different JS though, maybe a superset?

lukewarlow commented 9 months ago

One issue with the static property approach is it doesn't work if you're using a shadow root on a built in element. An attribute would work though. Perhaps could check for both?

sashafirsov commented 9 months ago

@bkardell , how this correlate with react css modules concept? The css module is scoped by its name but can be associated with single component exclusively. Making the insulation scoping by css selector. I.e. on one side it is sharig the rules with page but safeguard own content with generated selector.

In Custom Element it would mean associating the scope with own (open) root I guess.

lukewarlow commented 9 months ago

@sashafirsov if I've understood correctly shadow Dom already handles this 1 way sharing of CSS we can adopt the page styles but the page doesn't adopt the shadow Dom styles.

There's also the @ scope CSS concept to handle more complicated scoping

lukewarlow commented 9 months ago

One other issue with this opt out approach is that it applies to the node itself not the sub-tree of that node. I think a way to opt-out the entire sub-tree could be useful.

sorvell commented 9 months ago

I hadn't fully realized that the library already actually supports this, probably to enough of an extent for now, by controlling the selector given to the @media rule: --crossroot(:not([reject-style-layer]). This is good because it can be biased for opt-in or opt-out and customized per element.

I do also agree with @lukewarlow and think tree based control would be valuable. I prototyped this by using a container style query, but that's only available in Chrome/Edge only for now.

There's an API contract between the element author and user, but the conditions can vary widely. Consider the following corporate scenario:

  1. A tools team authors a set of elements for app teams to use, and that team is also responsible for ensuring all company web properties meet government accessibility requirements. This isn't just the right thing to do, the company's government contract is at risk. This team wants to allow as little style customization as possible.
  2. An app team has a very specific scenario the tools team didn't consider and must customize the styling with their legacy CSS or they cannot use the provided elements. The tools team cannot support this use case explicitly but gives the OK for the app team to use half-light to do it.
  3. Some time later, the app team is ready to migrate away from their legacy CSS. They want to be very careful not to break their app during the migration, and it must be done piecemeal so it doesn't disrupt feature progress. They want to have very granular control over which elements do and do not use half-light.
bkardell commented 9 months ago

An attribute would work though.

But if it is for a custom element it has to be an attribute that the element class sets or else it's again the page controlling it which is, I think, the thing you're saying you don't want...Plus I don't think any element really does that, right? Like, I guess we can do whatever, but it seems unusual to sprout an attribute that will only have legit meaning (probably) for an instant.

I mean, you could still attach to the heading constructor but, it's a keen point that if you are doing that with a heading, it's probably not 'all headings' but specific instances.. So, I mean, it can be an instance property? Why not? You can set that any way you want.

bkardell commented 9 months ago

@sashafirsov Yeah, what @lukewarlow said.

lukewarlow commented 9 months ago

I mean, it can be an instance property? Why not? You can set that any way you want.

It's very possible I'm just forgetting that you can do this stuff to regular elements. Potentially it would work fine and then that's okay.

bkardell commented 9 months ago

I think a way to opt-out the entire sub-tree could be useful.

Hmmm. I see what you're saying, and I've been thinking about this since you mentioned it to me and, I'm not sure we should try to tackle that for 3 reasons.

  1. That is how we got to here with things being so hard importing and exporting in trees. I know why it is tempting to say you want that, but then you get there and after a long time you realize how many problems there are with it. So, I'm not sure I want to go down that rabbit hole given the other two bullets.

  2. that would be really complex to polyfill and I don't think it could hope to be perfomant.

  3. Conversely element.attachShadow({mode: "closed"}) should Just Work™ for that today and I can promise you that we can get that part standardized 😄

So like, the short version is it kind of exists already I guess?

bkardell commented 9 months ago

I prototyped this by using a container style query, but that's only available in Chrome/Edge only for now.

@sorvell oh... that's an intersting possibility I hadnt considered... can we see what it looks like? a codepen? Maybe even send a PR so we can see the code impact?

sorvell commented 9 months ago

can we see what it looks like? a codepen?

I experimented with it here.

Just wrapping what you already have with something like @container style(--appearance: crossroot) { ... } and only doing that if it's supported. Using @propetrty that could also be the default value so it's opt-out.