Move a child element from shadow dom to light dom on document.ready - web-component

I defined a custom-element using web componenet:
<universal-form>
<slot name="inner-input">
</universal-form>
This custom-element itself is placed inside another shadow dom:
<my-dashboard>
#shadow-root
<div id="shadow-container">
<universal-form>
...
<input type="datepicker" id="to-be-exposed" slot="inner-input"/>
</universal-form>
</div>
...
</my-dashboard>
** note: for some of you might suggest putting the <universal-form> inside a slot of the <my-dashboard>, instead of placing it directly into the <my-dashboard> shadow-root, this route is ruled out because of some other implementation requirements.
Now I'd like to be able to style the input#to-be-exposed from the webpage's css file, so it needs to be accessible from the light dom when the document is rendered ready.
What is some best practice to achieve this?

An option would be to drop the shadowDom form the dashboard element in this case as an element that has a shadow attached doesn't have a light dom visible anymore.
The element won't mind that, things that are leaking in are exactly what you want, namely style from the parent window.
Implementation is simple, instead appending elements to the attached shadow append them directly in to the element.
Example of a shadowless element :
class MyElement extends HTMLElement {
constructor(){
super();
this.innerHTML = `elements here`;
}
}

Related

Headless UI Transition.child errors to "Did you forget to passthrough the `ref` to the actual DOM node"

I'm building a sidebar with the Transition and Dialog Headless UI components.
Transition docs
When I break out the code that's passed between <Transition.Child> to it's own component. I get this error:
Unhandled Runtime Error
Error: Did you forget to passthrough the `ref` to the actual DOM node?
Call Stack
eval
node_modules/#headlessui/react/dist/components/transitions/transition.js (1:3632)
Failing code:
<Transition.Child as={Fragment}>
<Cart
cancelButtonReference={cancelButtonReference}
setCartOpen={setCartOpen}
checkoutUrl={checkoutUrl}
removeCartItem={removeCartItem}
clearCart={clearCart}
cartLoading={cartLoading}
incrementCartItem={incrementCartItem}
decrementCartItem={decrementCartItem}
cartTotal={cartTotal}
cart={cart}
/>
</Transition.Child>
Working code:
<Transition.Child as={Fragment}>
<div>
...
</div>
</Transition.Child>
I understand the error I believe, which is that when I break out the code to it's own component Transition.Child wants me to pass a ref so that React knows that it should render a component and not a fragment.
If I remove as={Fragment}, which makes the Transition default to a div the error goes away, but then I get an unneeded div..
What ref do I need to pass? This is what I don't get.
You don't need to pass a ref, but the component needs to accept one and set it on the actual element.
The div element will accept the ref, which is why that method works.
Try creating the Cart component using React.forwardRef and set the ref on the div.
const Cart = React.forwardRef((props, forwardedRef) => {
return (
<div ref={forwardedRef}>
...
</div>
)
})
I ran into the same issue, and just found a solution—which you basically already said, too.
Through some testing, as you pointed out, it works when surrounding the component with the <div>…</div> tags.
Although I don't really understand it, it's clear that Headless UI's <Transition> tag wants to pass a reference to it's immediate child. With normal HTML tags, like <div>, it does it automatically. When using a component, it can't do it in the same way.
Solution #1
(Broken*)
I'm sure there's a more "proper" solution to this, but I found that—as we don't want <Transition> to render any HTML tags—you can just surround your component with another React fragment:
<Transition.Child as={Fragment}>
<> {/* <— Our new Fragment */}
<Cart
cancelButtonReference={cancelButtonReference}
setCartOpen={setCartOpen}
checkoutUrl={checkoutUrl}
removeCartItem={removeCartItem}
clearCart={clearCart}
cartLoading={cartLoading}
incrementCartItem={incrementCartItem}
decrementCartItem={decrementCartItem}
cartTotal={cartTotal}
cart={cart}
/>
</>
</Transition.Child>
This was working for me for a bit, but then just stopped working.*
Solution #2
(This also appears to not function correctly*)
As a secondary option, if you don't mind having another element rendered on the DOM, you can set the <Transition> to render as={'div'} (this is the default, so you don't actually have to define the prop), and then set the CSS display to contents:
<Transition.Child style={{display: 'contents'}}> {/* <— display set to contents */}
<Cart
cancelButtonReference={cancelButtonReference}
setCartOpen={setCartOpen}
checkoutUrl={checkoutUrl}
removeCartItem={removeCartItem}
clearCart={clearCart}
cartLoading={cartLoading}
incrementCartItem={incrementCartItem}
decrementCartItem={decrementCartItem}
cartTotal={cartTotal}
cart={cart}
/>
</Transition.Child>
If you're not familiar with display: contents, Manuel Rego give this description:
display: contents makes that the div doesn’t generate any box, so its background, border and padding are not rendered. However the inherited properties like color and font have effect on the child (span element) as expected.
Source
It seems that this technically fixes the ref issue, but because of the way display: contents works, all other applied CSS doesn't render.*
As I just discovered, neither of these works, I am looking into other solutions.

How to apply CSS from parent component?

There is a React component using Emotion called OtherComponent:
OtherComponent:
...
return <div css={otherComponentStyles}>
<div className='something'>
</div>
</div>
And another component called MainComponent that uses OtherComponent:
MainComponent:
...
return <OtherComponent css={mainComponentStyles} ... />
What happens in this situation is that OtherComponent properly uses otherComponentStyles. But it ignores mainComponentStyles.
But what I would like to do is to apply style to OtherComponent from the level of MainComponent.
I know i can wrap OtherComponent into a div, ad set css=... to the div. But it is a nasty fix of the problem.
Hence the question: how to apply CSS with Emotion from parent component aka MainComponent?
You are not applying those styles to any html tag, it's not <OtherComponent> which is rendering, it's the <div> which is rendering to the page, so you must apply styles to a valid html tag.

How can I style a sub element of a Fluent UI component in React?

I am trying to style an HTML element inside the component from the Fluent UI React library.
What I want to do is put the "On" / "Off" text to the left of the toggle rather than on the left. When I look at my "compiled" code I can see that the component is translated into:
<div>
<label></label>
<div id="target-me">
<button>
<span></span>
</button>
<label></label>
</div>
</div>
I want to add an inline-flex to the target-me div and set flex-flow property to row-reverse in order to get the button element to the right of the label element. The problem is, I can't manage to target the "target-me" div in my code.
How can I achieve this without rewriting a custom component ?
Thanks!
Ok, well I found the answer to my own question so here it is:
<Toggle styles={{ container: { flexFlow: "row-reverse" } }} />
Essentially you can target different parts of the component (root, container, label..) by using the styles property. Use VS Code's Intellisense to find out what elements you can target inside the component and then just give it some regular CSS-in-JS that you want.

Use variables to update internal CSS inside an angular component?

I would like to modify quite a large amount of styles on a page through a customisable panel. When a user clicks an option, the content on the page will completely change based on whatever was clicked.
This cannot be a scenario where a class is appended to a parent element and use CSS/LESS to adjust accordingly. For this scenario (for requirement reasons) the CSS needs to be internal on the angular component HTML.
Is it possible to have a value in the component TS like this:
myNewColour: "red"
That can then be used in an internal style sheet that's inside my angular component.html like this?:
<style>
.myContainer { background: myNewColour }
</style>
<!-- HTML Content -->
<div class="myContainer"> Stuff </div>
Any help with this would be appreciated! :)
"Internal in the HTML template" is called inline style ;) Apart from that, you can use ngStyle like so
<tag [ngStyle]="{'background': myNewColour}"></tag>
EDIT if it makes your code too long, what you can do is simply
let customStyle = {
'background': this.myNewColour
};
And in your tag, simply
<tag [ngStyle]="customStyle"></tag>

Custom elements and accessibility

I'd like to implement a listbox widget using the current web components specs. Moreover, the resulting listbox should conform to the ARIA standard. Instantiating the listbox widget should be as simple as:
<x-listbox>
<x-option>Option 1</x-option>
<x-option>Option 2</x-option>
</x-listbox>
For purposes of cleanliness and encapsulation, everything else should be rendered in shadow dom. To implement this widget, two custom elements, <x-listbox> and <x-option> are registered. The top-level element of the shadow dom of <x-listbox> is a <div> that carries the role=listbox and the aria-activedescendent attributes for accessibility (I don't want these attributes on the <x-listbox> element because they are implementation details.)
In order for aria-activedescendent to work, one needs ids on the option elements. Putting ids directly on the <x-option> elements won't work out of two reasons: Firstly, it would pollute the id namespace of the document that uses the listbox widget. Secondly and even more importantly, ids do not work across shadow boundaries (which is one purpose of the shadow dom), so the ids of the options have to live in the same shadow dom as the <div> with the aria-activedescendent attribute.
A solution for this would be to surround each <x-option> that is rendered as content inside the shadow dom of <x-listbox> with another <div> (belonging to that shadow dom), on which an id can be put.
My question is: Is this the right way to go and how to implement this using the custom element and shadow dom web apis?
Your probably should better implement this by creating an select element (using JavaScript). This should ensure screen readers recognize this correctly as an input for selecting a value/values from a list.
Add an select element like this below your <x-listbox> element:
<select class="only-screenreader">
<option>Option 1</option>
<option>Option 2</option>
</select>
Then add aria-hidden="true" to your custom <x-listbox> element.
Finally apply CSS to make the screenreader select element invisible.
.only-screenreader {
position:absolute;
left:-10000px;
top:auto;
width:1px;
height:1px;
overflow:hidden;
}
That's my approach but maybe there's a better one.
In the markup provided, x-option is in the light DOM, not the shadow DOM, so it can be referred to by id. To avoid polluting the id namespace, I generate a random id, which is set when the component loads but can be replaced. This way I can refer to the element by id whether or not the component user has set an id on it.
Wrapping each option in a div seems unnecessary and likely to cause issues. Also, if the options are in a <slot />, it's simply not possible.

Resources