# custom element

[browser](../browser) ⟩ [web components]() ⟩ [implement](implement) ⟩ custom elements

{% hint style="success" %}
associate a JavaScript [<mark style="color:yellow;">**class**</mark>](../js/val/class) with an [<mark style="color:yellow;">**HTML tag**</mark>](../html/element) name.
{% endhint %}

{% hint style="danger" %}
:star: custom element name 中間一定要有："<mark style="color:blue;">**`-`**</mark>" :exclamation:

* 例如：<mark style="color:blue;">`<vstack->`</mark> 或 <mark style="color:blue;">`<v-stack>`</mark> :white\_check\_mark:
* 但 "<mark style="color:blue;">`-`</mark>" 不能放前面：<mark style="color:blue;">`<-vstack>`</mark> :x:
{% endhint %}

{% tabs %}
{% tab title="⭐️ 重點" %}
{% hint style="success" %}
define new custom elements with [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/Window/customElements).
{% endhint %}

```javascript
// define new custom element
customElements.define(
  'web-component',                    // ⭐️ HTML tag name, must have "-"❗️ 
  class extends HTMLElement { ... }   // ⭐️ must extend `HTMLElement`❗️ 
);
```

{% hint style="warning" %}
**custom elements** render as <mark style="color:yellow;">**inline**</mark> elements <mark style="color:yellow;">**by default**</mark>.
{% endhint %}
{% endtab %}

{% tab title="🔴 主題" %}
* [lifecycle](custom-element/lifecycle "mention")
* [getter-setter](custom-element/getter-setter "mention") - make element's <mark style="color:yellow;">**attributes**</mark> available as <mark style="color:yellow;">**properties**</mark>.
* [Element Types](custom-element/types)
* [Element Info](custom-element/info)
* [Observing Attributes](custom-element/observed-attributes)
* [Custom Element Upgrade](../examples/less-than-time-formatted-greater-than#custom-element-upgrade)
* [Element-defined Content](custom-element/content)
{% endtab %}

{% tab title="📘 手冊" %}
* Window ⟩ [.customElements](https://developer.mozilla.org/en-US/docs/Web/API/Window/customElements) ([CustomElementRegistry](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry))
* [CustomElementRegistry](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry) ⟩ [.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)
* ​​📘 [Using custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) ⭐️
* ​​📘 [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
* Google ⟩ [Custom Elements](https://developers.google.com/web/fundamentals/web-components/customelements) ⭐️⭐️⭐️
* HTML Spec ⟩ [valid custom element name](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)
{% endtab %}

{% tab title="📗 參考" %}
* [ ] JavaScript: The Definitive Guide (15.6 Web Components)
* [ ] can I use [custom elements](https://caniuse.com/custom-elementsv1)?
{% endtab %}

{% tab title="💈範例" %}
:floppy\_disk: replit: [\<inline-circle>](https://replit.com/@pegasusroe/lessinline-circlegreater#index.html)

{% embed url="https://codepen.io/lochiwei/pen/wvmoxRP" %}

📁 HTML

```html
    <p> 
        The document has one marble: 
        <inline-circle size="3em" color="red"></inline-circle>. <br>
            
        The HTML parser instantiates two more marbles:
        <inline-circle size="2em" color="blue"></inline-circle>
        <inline-circle color="green"></inline-circle>.

        <hr>
            
        How many marbles does the document contain now? 
    </p>
```

📁 JS

```javascript
// define custom element: 
customElements.define("inline-circle", class extends HTMLElement {

    // 🔸 when <inline-circle> inserted into document.
    connectedCallback() {        
        
        // styles for circles    
        // ⭐️ demo only: don't do this in real apps.    
        this.style.display = "inline-block";    
        this.style.borderRadius = "50%";     
        this.style.border = "solid black 1px";    
        this.style.transform = "translateY(10%)";         
        
        // default size    
        if (!this.size) this.size = "0.8em";
    }
    
    // 🔸 observed attributesstatic 
    get observedAttributes() { return ["size", "color"]; }
    
    // 🔸 when one of the attributes listed above changes.
    attributeChangedCallback(name, oldValue, newValue) {    
        switch (name) {                    
            case "size":            
                this.style.width = newValue;             
                this.style.height = newValue;             
                break;        
            case "color":            
                this.style.backgroundColor = newValue;             
                break;    
        }
    }
    
    // 🔸 getter/setter for element's attributes.
    get size() { return this.getAttribute("size"); } 
    set size(size) { this.setAttribute("size", size); } 
    get color() { return this.getAttribute("color"); } 
    set color(color) { this.setAttribute("color", color); }
});
```
{% endtab %}
{% endtabs %}

## Valid Custom Element Name

{% hint style="info" %}
* 開頭：\[a-z] ⭐️
* 接著：\[0-9], \[a-z], "**`-`**", "**`.`**", "**`_`**" (optional)
* 中間一定要有："**`-`**" ⭐️
* 接著：\[0-9], \[a-z], "**`-`**", "**`.`**", "**`_`**" (optional)
{% endhint %}

{% hint style="info" %}
\<vstack-> 或 \<i-vstack> ❓(這兩個都是合法的名字)
{% endhint %}

```javascript
PotentialCustomElementName ::= 
    [a-z] (PCENChar)* '-' (PCENChar)*
    
PCENChar ::=
    "-" | "." | [0-9] | "_" | [a-z] | #xB7 | 
    [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x37D] | [#x37F-#x1FFF] | 
    [#x200C-#x200D] | [#x203F-#x2040] | [#x2070-#x218F] | 
    [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | 
    [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
```

## Register New Tag Name <a href="#register" id="register"></a>

We can create our tags by using the [CustomElementRegistry](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry) object (`window.customElements`).

{% hint style="danger" %}
* **custom element names** must contain a **hyphen**:exclamation: \
  📘 customElements.[define](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define#parameters)()
* Custom elements **cannot** be **self-closing** because HTML only allows [a few elements](https://html.spec.whatwg.org/multipage/syntax.html#void-elements) to be self-closing. Always write a closing tag\
  &#x20;(`<custom-element></custom-element>`)\
  📘 Google ⟩ [Custom Elements](https://developers.google.com/web/fundamentals/web-components/customelements#jsapi)
{% endhint %}

```javascript
// ⭐️ custom tag <my-component>
// ⭐️ custom element names must contain a "hyphen".
customElements.define('my-component', MyComponent);
```

## Supporting Class <a href="#class" id="class"></a>

To create a custom element, we need to tell the browser several details about it: **how to show it**, **what to do** when the element is **added** or **removed** to page, etc.

```javascript
class MyComponent extends HTMLElement {

  constructor() {
    super();
    // ...
  }

  // triggered when added to the document
  connectedCallback() {}

  // triggered when removed from the document
  disconnectedCallback() {}

  // attribute names to monitor for changes
  static get observedAttributes() {
    return [ /* ... */ ];
  }

  // triggered when one of attributes listed above is modified.
  // doesn’t trigger for unlisted attributes (for performance reasons).
  attributeChangedCallback(name, oldValue, newValue) {}

  // called when moved to a new document
  // happens in document.adoptNode, very rarely used
  adoptedCallback() {}

  // other methods and properties ...
}
```
