|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971 |
- title: Custom Forms with Web Components and "ElementInternals"
- url: https://dev.to/stuffbreaker/custom-forms-with-web-components-and-elementinternals-4jaj
- hash_url: 2cadf792810f64540605c86a1431cb6b
- archive_date: 2024-02-23
- og_image: https://media.dev.to/cdn-cgi/image/width=1000,height=500,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xlfoy8ah43wdacnfwqr.png
- description: The ElementInternals API allows us to create form-associated custom elements which we can use to make our own supercharged form fields.
- favicon: https://media.dev.to/cdn-cgi/image/width=192,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png
- language: en_US
-
- <p>With the release of Safari 16.4 in March 2023, web components reached a milestone in their capability to interact with the <code><form></code> element using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals"><code>ElementInternals</code> API</a>. Prior to this, input elements located within a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_shadow_DOM">Shadow DOM</a> were not discoverable by forms, which means they would not validate on form submission and their data would not be included in the <code>FormData</code> object. The <code>ElementInterals</code> API allows us to create Form-Associated Custom Elements (FACE for short). Our custom elements can now behave like native input elements and take advantage of form APIs like <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation">form constraint validation</a>.</p>
-
- <p>I wanted to explore the capabilities of the API so we could implement it in our component library at work, but as I began working on it, I couldn’t always see a clear path forward, so I decided to write this article for anyone else running into the same problems. I will also use TypeScript in the examples to help define the APIs.</p>
-
- <h2>
- <a name="getting-started" href="#getting-started">
- </a>
- Getting Started
- </h2>
-
- <p>If you already have components set up or if you are not following along on your own, please free to jump to the “Associating Custom Elements with Forms” section below. There are CodePens available to help you skip ahead. If not, <a href="https://codepen.io/break-stuff/pen/mdQpRqx?editors=1010">here is a CodePen</a> you can get started with.</p>
-
- <h3>
- <a name="setting-up-the-component" href="#setting-up-the-component">
- </a>
- Setting Up the Component
- </h3>
-
- <p>For the purposes of demonstrating the <code>ElementInternals</code> API, our component setup will be <em>very</em> simple with only two attributes initially - <code>value</code> and <code>required</code>. This example will be using “vanilla” web components, so your component setup will likely differ if you are using a library or framework to build your components. Be sure to follow those best practices when setting up your components. The good news is that implementing the <code>ElementInternals</code> API seems to be fairly consistent regardless of the tools you are using.</p>
-
- <h3>
- <a name="add-input" href="#add-input">
- </a>
- Add Input
- </h3>
-
- <p>The first thing we are going to do is use the component’s <code>connectedCallback</code> lifecycle hook to add a shadow root to our component and insert an input and label in it.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">customElements</span><span class="p">.</span><span class="nx">define</span><span class="p">(</span><span class="dl">'</span><span class="s1">my-input</span><span class="dl">'</span><span class="p">,</span> <span class="kd">class</span> <span class="kd">extends</span> <span class="nx">HTMLElement</span> <span class="p">{</span>
- <span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">attachShadow</span><span class="p">({</span> <span class="na">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">open</span><span class="dl">'</span> <span class="p">}).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`
- <label>
- My Input
- <input type="text" />
- </label>`</span><span class="p">;</span>
- <span class="p">}</span>
- <span class="p">});</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Let’s grab a reference to our input element. We can do that by creating a property at the root of our class (I am choosing to prefix this with a <code>$</code> to distinguish properties referencing HTML elements from other properties).<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">private</span> <span class="nx">$input</span><span class="p">:</span> <span class="nx">HTMLInputElement</span><span class="p">;</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>In our <code>connectedCallback</code> method, we can query the shadow DOM contents for our input element.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h3>
- <a name="add-attributes" href="#add-attributes">
- </a>
- Add Attributes
- </h3>
-
- <p>Let’s add <code>value</code> and <code>required</code> attributes so we can set them on our custom element’s tag and pass them to our internal element (<code><my-input value=”abc” required></my-input</code>). We will do this by adding the <code>observedAttributes</code> property and returning an array of the attribute names.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">static</span> <span class="kd">get</span> <span class="nx">observedAttributes</span><span class="p">()</span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">[</span><span class="dl">"</span><span class="s2">required</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="p">];</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Now we need to update the input properties when the attributes change using the <code>attributeChangedCallback</code> lifecycle hook, but we will have a timing issue. The <code>attributeChangedCallback</code> method will run before our internal input has had a chance to render and for our query selector to assign it to our <code>$input</code> variable. To get around this, we will create a component variable to capture the attributes and values and we will update our internal input when it is ready.</p>
-
- <p>First, let’s add a private property called <code>_attrs</code> to our component and set the value to an empty object.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">private</span> <span class="nx">_attrs</span> <span class="o">=</span> <span class="p">{};</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>In our <code>attributeChangeCallback</code> method, let’s assign any attribute changes to that object where <code>name</code> is the attribute being changed and <code>next</code> is the new value.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">attributeChangedCallback</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">prev</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_attrs</span><span class="p">[</span><span class="nx">name</span><span class="p">]</span> <span class="o">=</span> <span class="nx">next</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Now let’s create a private method that will use our <code>_attrs</code> values to update our input element.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">private</span> <span class="nx">setProps</span><span class="p">()</span> <span class="p">{</span>
- <span class="c1">// prevent any errors in case the input isn't set</span>
- <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">return</span><span class="p">;</span>
- <span class="p">}</span>
-
- <span class="c1">// loop over the properties and apply them to the input</span>
- <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">prop</span> <span class="k">in</span> <span class="k">this</span><span class="p">.</span><span class="nx">_attrs</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">switch</span> <span class="p">(</span><span class="nx">prop</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">case</span> <span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="p">:</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_attrs</span><span class="p">[</span><span class="nx">prop</span><span class="p">];</span>
- <span class="k">break</span><span class="p">;</span>
- <span class="k">case</span> <span class="dl">"</span><span class="s2">required</span><span class="dl">"</span><span class="p">:</span>
- <span class="kd">const</span> <span class="nx">required</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_attrs</span><span class="p">[</span><span class="nx">prop</span><span class="p">];</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">toggleAttribute</span><span class="p">(</span>
- <span class="dl">"</span><span class="s2">required</span><span class="dl">"</span><span class="p">,</span>
- <span class="nx">required</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">true</span><span class="dl">"</span> <span class="o">||</span> <span class="nx">required</span> <span class="o">===</span> <span class="dl">""</span>
- <span class="p">);</span>
- <span class="k">break</span><span class="p">;</span>
- <span class="p">}</span>
- <span class="p">}</span>
-
- <span class="c1">// reset the attributes to prevent unwanted changes later</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_attrs</span> <span class="o">=</span> <span class="p">{};</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>We can now add this to the <code>connectedCallback</code> method to update our input element with any changes that happened before it was rendered.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span> <span class="o">=</span> <span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">);</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">setProps</span><span class="p">();</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>We can also add this to the <code>attributeChangedCallback</code> method so any attribute changes that occur after the <code>connectedCallback</code> method gets called are applied to the input element.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">attributeChangedCallback</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">prev</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_attrs</span><span class="p">[</span><span class="nx">name</span><span class="p">]</span> <span class="o">=</span> <span class="nx">next</span><span class="p">;</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">setProps</span><span class="p">();</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Now we should be able to add attributes to our element tag and have them pass down to the internal input element being rendered.</p>
-
- <p>
- </p>
-
- <h2>
- <a name="associating-custom-elements-with-forms" href="#associating-custom-elements-with-forms">
- </a>
- Associating Custom Elements with Forms
- </h2>
-
- <p>Associating your custom element is surprisingly straightforward and can be done in 2 steps:</p>
-
- <ol>
- <li><p>set the <code>formAssociated</code> static property to <code>true</code></p></li>
- <li><p>Expose the <code>ElementInternals</code> API by calling <code>this._internals = this.attachInternals();</code> in the component’s constructor.<br>
- </p></li>
- </ol>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">static</span> <span class="nx">formAssociated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
- <span class="k">private</span> <span class="nx">_internals</span><span class="p">:</span> <span class="nx">ElementInternals</span><span class="p">;</span>
-
- <span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span>
- <span class="k">super</span><span class="p">();</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">attachInternals</span><span class="p">();</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>With those two changes, our custom element can now see the parent form! As a quick test try adding <code>console.log(this._internals.form);</code> to the <code>connectedCallback</code> method and you should see the parent logged in the console.</p>
-
- <h3>
- <a name="using-labels" href="#using-labels">
- </a>
- Using Labels
- </h3>
-
- <p>By making this a form-associated custom element, the browser now sees it as an input element which means we can move the label out of our component.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`<input type="text" />`</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>We can label our custom input element like a standard input element. Let’s use a <code><label></code> and reference it using an <code>id</code> on the custom element and a <code>for</code> attribute on the label.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight html"><code><span class="nt"><form</span> <span class="na">id=</span><span class="s">"my-form"</span><span class="nt">></span>
- <span class="nt"><label</span> <span class="na">for=</span><span class="s">"input"</span><span class="nt">></span>My Input<span class="nt"></label></span>
- <span class="nt"><my-input</span> <span class="na">id=</span><span class="s">"input"</span><span class="nt">></my-input></span>
- <span class="nt"></form></span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <blockquote>
- <p><strong><em>NOTE:</em></strong> There is an accessibility bug in <a href="https://bugs.webkit.org/show_bug.cgi?id=259124">Safari</a> and <a href="https://github.com/nvaccess/nvda/issues/15118">NVDA</a> where labels are not properly associated to Form-Associated Custom Elements for screen readers. They are read fine with VoiceOver in Chromium browsers (Chrome, Edger, Brave, etc.) and Firefox on Mac and MS Narrator and JAWS on Windows. As a workaround, you can continue including your labels within your elements.</p>
- </blockquote>
-
- <p>Let’s also update our shadow root configuration to delegate focus. This will allow the input to be focused when the label is clicked like with native input elements.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="kd">const</span> <span class="nx">shadowRoot</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">attachShadow</span><span class="p">({</span> <span class="na">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">open</span><span class="dl">'</span><span class="p">,</span> <span class="na">delegatesFocus</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <blockquote>
- <p>“If <code>delegatesFocus</code> is <code>true</code>, when a non-focusable part of the shadow DOM is clicked, or <code>.focus()</code> is called on the host element, the first focusable part is given focus, and the shadow host is given any available <code>:focus</code> styling.” - MDN</p>
- </blockquote>
-
- <h3>
- <a name="exposing-validation" href="#exposing-validation">
- </a>
- Exposing Validation
- </h3>
-
- <p>Now that the form is associated with our custom element, we can begin by exposing some of the core validation behavior we would expect with an input element like <code>checkValidity</code>, <code>reportValidity</code>, <code>validity</code>, and <code>validationMessage</code>.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">public</span> <span class="nx">checkValidity</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
- <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">checkValidity</span><span class="p">();</span>
- <span class="p">}</span>
-
- <span class="k">public</span> <span class="nx">reportValidity</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
- <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">reportValidity</span><span class="p">();</span>
- <span class="p">}</span>
-
- <span class="k">public</span> <span class="kd">get</span> <span class="nx">validity</span><span class="p">():</span> <span class="nx">ValidityState</span> <span class="p">{</span>
- <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">validity</span><span class="p">;</span>
- <span class="p">}</span>
-
- <span class="k">public</span> <span class="kd">get</span> <span class="nx">validationMessage</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
- <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">validationMessage</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h3>
- <a name="controlling-validation" href="#controlling-validation">
- </a>
- Controlling Validation
- </h3>
-
- <p>The <code>ElementIntenrals</code> API gives us access to the <code>setValidity</code> method, which we can use to communicate to the form the validity status of our element.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">setValidity</span><span class="p">(</span><span class="nx">flags</span><span class="p">:</span> <span class="nx">ValidityStateFlags</span><span class="p">,</span> <span class="nx">message</span><span class="p">?:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">anchor</span><span class="p">?:</span> <span class="nx">HTMLElement</span><span class="p">)</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>As you can see, the <code>message</code> and <code>anchor</code> attributes are optional. If you want to reset the validation, you can pass an empty object (<code>{}</code>) as the flags parameter.</p>
-
- <h4>
- <a name="flags" href="#flags">
- </a>
- Flags
- </h4>
-
- <p>The <code>flags</code> interface is almost identical to the <code>ValidityState</code> object you get when you call <code>input.validity</code>, but each of the properties is optional and can be set (where an input’s <code>validity</code> is a read-only property). Here is an example of the interface as well as some examples of when these would be set with native HTML input elements.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="kr">interface</span> <span class="nx">ValidityStateFlags</span> <span class="p">{</span>
- <span class="cm">/** `true` if the element is required, but has no value */</span>
- <span class="nl">valueMissing</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value is not in the required syntax (when the "type" is "email" or "URL") */</span>
- <span class="nl">typeMismatch</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value does not match the specified pattern */</span>
- <span class="nl">patternMismatch</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value exceeds the specified `maxlength` */</span>
- <span class="nl">tooLong</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value fails to meet the specified `minlength` */</span>
- <span class="nl">tooShort</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value is less than the minimum specified by the `min` attribute */</span>
- <span class="nl">rangeUnderflow</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value is greater than the maximum specified by the `max` attribute */</span>
- <span class="nl">rangeOverflow</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the value does not fit the rules determined by the `step` attribute (that is, it's not evenly divisible by the step value) */</span>
- <span class="nl">stepMismatch</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the user has provided input that the browser is unable to convert */</span>
- <span class="nl">badInput</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="cm">/** `true` if the element's custom validity message has been set to a non-empty string by calling the element's `setCustomValidity()` method */</span>
- <span class="nl">customError</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h4>
- <a name="message" href="#message">
- </a>
- Message
- </h4>
-
- <p>The <code>message</code> parameter is how we can provide the element with a custom error message whenever one of these validation parameters is validated.</p>
-
- <h4>
- <a name="anchor" href="#anchor">
- </a>
- Anchor
- </h4>
-
- <p>The <code>anchor</code> parameter is the element which we want to associate the error message with.</p>
-
- <h2>
- <a name="adding-validation" href="#adding-validation">
- </a>
- Adding Validation
- </h2>
-
- <p>Now that we can control how validation is set in our component, let’s add the functionality to make our input <code>required</code>.</p>
-
- <h3>
- <a name="initialize-validation" href="#initialize-validation">
- </a>
- Initialize Validation
- </h3>
-
- <p>Now let’s initialize the validation. Because our input’s <code>ValidityState</code> has essentially the same interface as our <code>ValidityStateFlags</code> we can use the input’s initial state to set the <code>ElementInterals</code> state. Right after our input selector in the <code>connectedCallback</code> method, let’s call the <code>setValidity</code> method based on our input.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span> <span class="o">=</span> <span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">);</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">setValidity</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">validity</span><span class="p">,</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">validationMessage</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Here we use the internal input element’s <code>ValidityState</code>, but you can also pass in a subset of the <code>ValidityStateFlags</code> and a custom error message as well.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">setValidity</span><span class="p">(</span>
- <span class="p">{</span>
- <span class="na">valueMissing</span><span class="p">:</span> <span class="kc">true</span>
- <span class="p">},</span>
- <span class="dl">'</span><span class="s1">Please fill out this required field</span><span class="dl">'</span><span class="p">,</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span>
- <span class="p">);</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h3>
- <a name="testing-validation" href="#testing-validation">
- </a>
- Testing Validation
- </h3>
-
- <p>Everything should be wired up, so let’s test it out. Let’s update the HTML to add a submit button and a <code>required</code> attribute to our custom element.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight html"><code><span class="nt"><form</span> <span class="na">id=</span><span class="s">"my-form"</span><span class="nt">></span>
- <span class="nt"><label</span> <span class="na">for=</span><span class="s">"input"</span><span class="nt">></span>My Input<span class="nt"></label></span>
- <span class="nt"><my-input</span> <span class="na">id=</span><span class="s">"input"</span> <span class="na">required</span><span class="nt">></my-input></span>
- <span class="nt"><button></span>Submit<span class="nt"></button></span>
- <span class="nt"></form></span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>When you click the “Submit” button, you should see the browser validation message for a required field. If we select our form and call <code>form.checkValidity()</code>, it should return <code>false</code>.</p>
-
- <h3>
- <a name="updating-validation-using-raw-setvalidity-endraw-" href="#updating-validation-using-raw-setvalidity-endraw-">
- </a>
- Updating Validation Using <code>setValidity</code>
- </h3>
-
- <p>That validation state will remain as it is until we update it. If you enter text in the input and click “Submit”, you will still see an error message and <code>form.checkValidity()</code> will still return <code>false</code>.</p>
-
- <p>For this demonstration, we can set up a simple update whenever the user inputs content in the field. To do that, we will add an event listener to our <code>connectedCallback</code> method after we have selected our input element.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="k">this</span><span class="p">.</span><span class="nx">handleInput</span><span class="p">());</span>
- <span class="p">}</span>
-
- <span class="k">private</span> <span class="nx">handleInput</span><span class="p">()</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">setValidity</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">validity</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">validationMessage</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>
- </p>
-
- <h2>
- <a name="updating-form-values-using-raw-setformvalue-endraw-" href="#updating-form-values-using-raw-setformvalue-endraw-">
- </a>
- Updating Form Values Using <code>setFormValue</code>
- </h2>
-
- <p>Using the <code>setFormValue</code> on the <code>ElementInternals</code> API, we can now update our form whenever the value changes in our custom element. This allows developers to easily get form values using the <code>FormData</code> API.</p>
-
- <p>Let’s set the initial value when the component loads in the <code>connectedCallback</code> method.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">setFormValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Now, let’s update the value any time the <code>input</code> event fires by adding the update to our event listener.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">private</span> <span class="nx">handleInput</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_internals</span><span class="p">.</span><span class="nx">setFormValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h3>
- <a name="testing-form-data" href="#testing-form-data">
- </a>
- Testing Form Data
- </h3>
-
- <p>To test our values, let’s update our component to include a <code>name</code> attribute for the form to identify it.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight html"><code><span class="nt"><form</span> <span class="na">id=</span><span class="s">"my-form"</span><span class="nt">></span>
- <span class="nt"><label</span> <span class="na">for=</span><span class="s">"input"</span><span class="nt">></span>My Input<span class="nt"></label></span>
- <span class="nt"><my-input</span> <span class="na">id=</span><span class="s">"input"</span> <span class="na">name=</span><span class="s">"myInput"</span> <span class="na">required</span><span class="nt">></my-input></span>
- <span class="nt"><button></span>Submit<span class="nt"></button></span>
- <span class="nt"></form></span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>We can now test to see if our value is being bound to the form by adding an event listener to the form’s <code>submit</code> event and grabbing the form data.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="kd">const</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">my-form</span><span class="dl">"</span><span class="p">);</span>
- <span class="nx">form</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">submit</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
- <span class="kd">const</span> <span class="nx">formData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">);</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`My Input Value - '</span><span class="p">${</span><span class="nx">formData</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">myInput</span><span class="dl">'</span><span class="p">)}</span><span class="s2">'`</span><span class="p">);</span>
- <span class="p">});</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Type a value into the input and click the “Submit” button. You should see your value logged in the console.</p>
-
- <p>
- </p>
-
- <h2>
- <a name="elementinternals-lifecycle-hooks" href="#elementinternals-lifecycle-hooks">
- </a>
- ElementInternals Lifecycle Hooks
- </h2>
-
- <p>The <code>ElementInternals</code> API provides us with some additional lifecycle hooks that are important for controlling interactions with the browser and other elements. It is important to note that these are optional and should be used only when necessary.</p>
-
- <h3>
- <a name="-raw-formassociatedcallbackform-htmlformelement-endraw-" href="#-raw-formassociatedcallbackform-htmlformelement-endraw-">
- </a>
- <code>formAssociatedCallback(form: HTMLFormElement)</code>
- </h3>
-
- <p>This is called as soon as the element is associated with a form. We don’t really have a need for this right now so we won’t implement this right now.</p>
-
- <h3>
- <a name="-raw-formdisabledcallbackdisabled-boolean-endraw-" href="#-raw-formdisabledcallbackdisabled-boolean-endraw-">
- </a>
- <code>formDisabledCallback(disabled: boolean)</code>
- </h3>
-
- <p>This is called whenever the element or parent <code><fieldset></code> element are disabled. We can use this to help manage the disabled state of our internal element. We will add the callback method to the class and update our internal input element when it changes.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">formDisabledCallback</span><span class="p">(</span><span class="nx">disabled</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">disabled</span> <span class="o">=</span> <span class="nx">disabled</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h3>
- <a name="-raw-formresetcallback-endraw-" href="#-raw-formresetcallback-endraw-">
- </a>
- <code>formResetCallback()</code>
- </h3>
-
- <p>This gives us the ability to control our element’s behavior when a user resets a form. In our case, we will keep it simple and reset the input value to whatever the initial value was when the component was loaded. We will create a private property called <code>_defaultValue</code>, set it in the <code>connectedCallback</code> method, and then use the <code>formResetCallback</code> callback method to reset the value if the form is reset.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="k">private</span> <span class="nx">_defaultValue</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
-
- <span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="p">...</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">_defaultValue</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
- <span class="p">}</span>
-
- <span class="nx">formResetCallback</span><span class="p">()</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_defaultValue</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>Let’s update our form to include a reset button and add an initial value to the input. Now we can change the value and press the “Reset” button and it will revert back to the original value.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="p"><</span><span class="nt">form</span> <span class="na">id</span><span class="p">=</span><span class="s">"my-form"</span><span class="p">></span>
- <span class="p"><</span><span class="nt">label</span> <span class="na">for</span><span class="p">=</span><span class="s">"input"</span><span class="p">></span>My Input<span class="p"></</span><span class="nt">label</span><span class="p">></span>
- <span class="p"><</span><span class="nt">my</span><span class="err">-</span><span class="na">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"input"</span> <span class="na">name</span><span class="p">=</span><span class="s">"myInput"</span> <span class="na">value</span><span class="p">=</span><span class="s">"test"</span> <span class="na">required</span><span class="p">></</span><span class="nt">my</span><span class="err">-</span><span class="na">input</span><span class="p">></span>
- <span class="p"><</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"reset"</span><span class="p">></span>Reset<span class="p"></</span><span class="nt">button</span><span class="p">></span>
- <span class="p"><</span><span class="nt">button</span><span class="p">></span>Submit<span class="p"></</span><span class="nt">button</span><span class="p">></span>
- <span class="p"></</span><span class="nt">form</span><span class="p">></span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <h3>
- <a name="-raw-formstaterestorecallbackstate-mode-endraw-" href="#-raw-formstaterestorecallbackstate-mode-endraw-">
- </a>
- <strong><code>formStateRestoreCallback(state, mode)</code></strong>
- </h3>
-
- <p>This method callback gives the developer control over what happens when the browser completes form elements. The <code>state</code> property provides the value that is set using the <code>setFormValue</code> and the <code>mode</code> has two possible values - <code>restore</code> and <code>autocomplete</code>. The <code>restore</code> value is set when a user navigates away from a form and back to it again allowing them to continue where they left off. The <code>autocomplete</code> value is used when a browser’s input-assist tries to autocomplete the form. The downside to the <code>autocomplete</code> feature is that according to <a href="https://web.dev/more-capable-form-controls/#restoring-form-state">this article</a>, it is not supported yet. In that case, we can use a simple implementation to restore the input to the saved value.<br>
- </p>
-
- <div class="highlight js-code-highlight">
- <pre class="highlight tsx"><code><span class="nx">formStateRestoreCallback</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">mode</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">this</span><span class="p">.</span><span class="nx">$input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">state</span><span class="p">;</span>
- <span class="p">}</span>
- </code></pre>
- <div class="highlight__panel js-actions-panel">
- <div class="highlight__panel-action js-fullscreen-code-action">
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode</title>
- <path d="M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"></path>
- </svg>
-
- <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Exit fullscreen mode</title>
- <path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"></path>
- </svg>
-
- </div>
- </div>
- </div>
-
-
-
- <p>
- </p>
-
- <h2>
- <a name="conclusion" href="#conclusion">
- </a>
- Conclusion
- </h2>
-
- <p>As you can see, this only scratches the surface of the potential of what these new APIs can do. We only implemented two attributes out of the many that are available in an <code>input</code> element and things get even more interesting when you introduce other form elements like <code>select</code>, <code>textarea</code>, and <code>button</code>. Hopefully, this gives you a solid start with creating form-associated custom elements. Happy coding!</p>
|