I'm creating a custom component with StencilJS and I have to made some changes of the outline when the user is using the keyboard or the mouse to navigate into the component.

My component is using ShadowDOM and I want to access an HTML tag attribute from the CSS.

The tag's attributes are generated with what-input (https://github.com/ten1seven/what-input) to detect keybord and mouse events.

I've tried using CSS Selectors like [data-whatintent=keyboard] and html[data-whatintent=keyboard] but it didn't worked.

This is my HTML tag from which I want to access data-whatintent attribute:

<html dir="ltr" lang="en" data-whatinput="keyboard" data-whatintent="mouse">

  <my-custom-component></my-custom-component>

</html>

And this is my CSS:

[data-whatintent=keyboard] *:focus {
  outline: solid 2px #1A79C6;
}

I want that my CSS within the ShadowDOM can use the data-whatintent attribute's value to set styles on my component so the outline is like I want.

2 Answers

2
G. Tranter On Best Solutions

Supersharp's answer is correct, however it's not StencilJS code and also host-context support is flakey (doesn't work in Firefox and probably IE11).

You can 'transfer' the attribute to the host element, and then use the selector from inside the host component style:

TSX:

private intent: String;

componentWillLoad() {
    this.intent = document.querySelector('html').getAttribute('data-whatintent');
}

hostData() {
    return {
        'data-whatintent': this.intent
    };
}

SCSS:

:host([data-whatintent="keyboard"]) *:focus {
    outline: solid 2px #1A79C6;
}

If the data-whatintent attribute changes dynamically, make it a property of the component, and have the listener function update your component. You can optionally use the property to add/remove classes to the host for styling, although you could also continue to use the attribute selector.

TSX:

@Prop({ mutable: true, reflectToAtrr: true }) dataWhatintent: String;

componentWillLoad() {
    this.dataWhatintent = document.querySelector('html').getAttribute('data-whatintent');
}

hostData() {
    return {
        class: { 
            'data-intent-keyboard': this.dataWhatintent === 'keyboard' 
        }
    };
}

SCSS:

:host(.data-intent-keyboard) *:focus {
    outline: solid 2px #1A79C6;
}

Document's keyboard and mouse event handler:

function intentHandler(event: Event) {
    const intent = event instanceof KeyboardEvent ? 'keyboard' : 'mouse';
    document.querySelectorAll('my-custom-component').forEach(
        el => el.setAttribute('data-whatintent', intent)
    );
}
2
Supersharp On

You sould use :host-context() to apply a CSS style in a Shadow DOM depending on the context where the Custom Element is used.

customElements.define( 'my-custom-component', class extends HTMLElement {
    constructor() {
        super()
        this.attachShadow( { mode: 'open' } )
            .innerHTML = `
              <style>
                :host-context( [data-whatinput=keyboard] ) *:focus {
                   outline: solid 2px #1A79C6;
                }
              </style>
              <input value="Hello">`
    }
} )         
           
<html dir="ltr" lang="en" data-whatinput="keyboard" data-whatintent="mouse">

  <my-custom-component></my-custom-component>

</html>