# Active Element

[Web Components](https://lochiwei.gitbook.io/web/component) ⟩ [Shadow DOM](https://lochiwei.gitbook.io/web/component/shadow-dom) ⟩ [Events](https://lochiwei.gitbook.io/web/component/shadow-dom/events) ⟩

{% tabs %}
{% tab title="📗 參考" %}

* Google ⟩ Web Component ⟩ [Handling focus](https://developers.google.com/web/fundamentals/web-components/shadowdom#focus)
  {% endtab %}

{% tab title="📘 手冊" %}

{% endtab %}

{% tab title="👉 相關" %}

* [\<my-log> ❤️](https://lochiwei.gitbook.io/web/component/examples/less-than-my-log-greater-than)
  {% endtab %}
  {% endtabs %}

events that are fired **inside shadow DOM** are adjusted to look like they come from the **hosting element**.

```markup
<x-focus>
  #shadow-root
    <input type="text" placeholder="input inside shadow dom">
```

## Active Element in Shadow DOM <a href="#in-shadow-dom" id="in-shadow-dom"></a>

```javascript
// ⭐️ if you click <input> inside a shadow root,
//    the `focus` event will look like it came from <x-focus>,
//    not the <input>❗️
document.activeElement                            // ⭐️ <x-focus>

// ⭐️ only works with {mode: open}.
document.activeElement.shadowRoot.activeElement   // ⭐️ <input> 
```

## Active Element down Multiple Levels of Shadow DOM <a href="#multiple-levels-of-shadow-dom" id="multiple-levels-of-shadow-dom"></a>

If there are **multiple levels** of **shadow DOM** at play (say a custom element within another custom element), you need to **recursively** drill into the shadow roots to find the **activeElement**:

```javascript
function deepActiveElement() {
  // top-level active element
  let a = document.activeElement;
  // recursively drill into shadow roots to find activeElement
  while (a && a.shadowRoot && a.shadowRoot.activeElement) {
    a = a.shadowRoot.activeElement;
  }
  // return the real active element
  return a;
}
```

## {delegatesFocus: true} <a href="#delegates-focus" id="delegates-focus"></a>

{% hint style="info" %}

* If you **click** a node **inside shadow DOM** and the node is **not** a **focusable** area, the **first focusable area** becomes focused.
* When a node inside shadow DOM gains focus, **:focus** applies to the **host** in addition to the **focused element**.
  {% endhint %}

💾 [replit](https://replit.com/@pegasusroe/delegatesFocus#script.js)

{% tabs %}
{% tab title="main.js" %}

```javascript
// outer DOM events (⭐️ 收不到 `focus` 訊號❓)
document.body.addEventListener('focus', e => {
    let tag = document.activeElement.nodeName;
    console.log(`Active element (outer DOM): ${tag}`);
})

// main
function log(msg){
    const div = document.querySelector('my-log');
    div.log(msg);
}
```

{% endtab %}

{% tab title="x-focus" %}

```javascript
// register <x-focus>
customElements.define('x-focus', class extends HTMLElement {

    constructor() {

        // always call super() first in the constructor.
        super(); 

        /*
            ⭐️ {delegatesFocus: true} option
            --------------------------------
            • If you click a node inside shadow DOM and the node is not a  
            focusable area, the first focusable area becomes focused.

            • When a node inside shadow DOM gains focus, :focus applies to 
            the host in addition to the focused element.
        */

        const delegatesFocus = this.hasAttribute('delegates-focus');

        const root = this.attachShadow({
            mode: 'open', 
            delegatesFocus: delegatesFocus        // ⭐️ delegates focus
        });

        root.innerHTML = `
            <link rel="stylesheet" href="x-focus-styles.css" />
            <div>${delegatesFocus ? '' : 'Not '}Clickable text</div>
            <input type="text" placeholder="input inside shadow dom">
        `;

        // ⭐️ `focus` event inside shadow DOM:
        this.addEventListener('focus', function(e) {
            let tag = root.activeElement.nodeName;
            console.log(`Active element (inside shadow dom): ${tag}`);
        });

    }// end: constructor()
});
```

{% endtab %}

{% tab title="html" %}

```javascript
<x-focus delegates-focus></x-focus>
<x-focus></x-focus>

<my-log></my-log>
```

{% endtab %}

{% tab title="css (outer)" %}

```css
:focus {
    outline: 2px solid red;
}
```

{% endtab %}

{% tab title="css (inner)" %}

```javascript
:host {
    display: flex;
    gap: 8px;
    border: 1px dotted black;
    padding: 16px;
}

:focus {
    outline: 2px solid blue;
}
```

{% endtab %}

{% tab title="codepen" %}
{% embed url="<https://codepen.io/lochiwei/pen/MWoqKZo>" %}

{% endtab %}

{% tab title="👉 相關" %}

* [\<my-log> ❤️](https://lochiwei.gitbook.io/web/component/examples/less-than-my-log-greater-than)
  {% endtab %}
  {% endtabs %}
