Browse Source

Links

master
David Larlet 2 months ago
parent
commit
340155024a
Signed by: David Larlet <david@larlet.fr> GPG Key ID: 3E2953A359E7E7BD

+ 1
- 1
cache.py View File

@@ -191,7 +191,7 @@ def fetch_metadata(title, url, description, language):
for image in data.get("images"):
if image_type == "body_image":
image_src = image.get("src")
if "favicon" not in image_src:
if image_src and "favicon" not in image_src:
og_image = image_src
break


+ 1082
- 0
cache/2024/2cadf792810f64540605c86a1431cb6b/index.html
File diff suppressed because it is too large
View File


+ 971
- 0
cache/2024/2cadf792810f64540605c86a1431cb6b/index.md View File

@@ -0,0 +1,971 @@
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>&lt;form&gt;</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">`
&lt;label&gt;
My Input
&lt;input type="text" /&gt;
&lt;/label&gt;`</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>&lt;my-input value=”abc” required&gt;&lt;/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">`&lt;input type="text" /&gt;`</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>&lt;label&gt;</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">&lt;form</span> <span class="na">id=</span><span class="s">"my-form"</span><span class="nt">&gt;</span>
<span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"input"</span><span class="nt">&gt;</span>My Input<span class="nt">&lt;/label&gt;</span>
<span class="nt">&lt;my-input</span> <span class="na">id=</span><span class="s">"input"</span><span class="nt">&gt;&lt;/my-input&gt;</span>
<span class="nt">&lt;/form&gt;</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">&lt;form</span> <span class="na">id=</span><span class="s">"my-form"</span><span class="nt">&gt;</span>
<span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"input"</span><span class="nt">&gt;</span>My Input<span class="nt">&lt;/label&gt;</span>
<span class="nt">&lt;my-input</span> <span class="na">id=</span><span class="s">"input"</span> <span class="na">required</span><span class="nt">&gt;&lt;/my-input&gt;</span>
<span class="nt">&lt;button&gt;</span>Submit<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/form&gt;</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">=&gt;</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">&lt;form</span> <span class="na">id=</span><span class="s">"my-form"</span><span class="nt">&gt;</span>
<span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"input"</span><span class="nt">&gt;</span>My Input<span class="nt">&lt;/label&gt;</span>
<span class="nt">&lt;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">&gt;&lt;/my-input&gt;</span>
<span class="nt">&lt;button&gt;</span>Submit<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/form&gt;</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">=&gt;</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>&lt;fieldset&gt;</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">&lt;</span><span class="nt">form</span> <span class="na">id</span><span class="p">=</span><span class="s">"my-form"</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="p">=</span><span class="s">"input"</span><span class="p">&gt;</span>My Input<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
<span class="p">&lt;</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">&gt;&lt;/</span><span class="nt">my</span><span class="err">-</span><span class="na">input</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"reset"</span><span class="p">&gt;</span>Reset<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">button</span><span class="p">&gt;</span>Submit<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</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>

+ 184
- 0
cache/2024/5da390fa8d93278cd62233d3915729e7/index.html
File diff suppressed because it is too large
View File


+ 17
- 0
cache/2024/5da390fa8d93278cd62233d3915729e7/index.md
File diff suppressed because it is too large
View File


+ 217
- 0
cache/2024/d6e0a9beec0623cc66cede135fc7acdc/index.html View File

@@ -0,0 +1,217 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Introducing Plausible Community Edition (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://plausible.io/blog/community-edition">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Introducing Plausible Community Edition</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://plausible.io/blog/community-edition" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-02-23
</p>
</nav>
<hr>
<p><strong>TL;DR</strong>: We’re introducing the “free as in beer”, self-hosted and AGPL-licensed Plausible Community Edition (CE). <a href="https://github.com/plausible/community-edition/">Installation instructions are here</a>.</p>
<p>In October 2020, we changed <a href="https://plausible.io/blog/open-source-licenses">from the MIT to the AGPL license</a> to prevent corporations from exploiting our code without giving back.</p>
<p>As mentioned at the time, we wanted a “don’t be evil” license. Here’s the quote of what we wrote more than three years ago on what we were trying to accomplish with the license change:</p>
<blockquote> <ul> <li>We want to prevent corporations from taking our code and using it as part of their closed-source proprietary products</li> <li>We want to prevent corporations from offering Plausible as a service without contributing to the open source project</li> <li>We want to prevent corporations from confusing people and making them think that the service they sell is in any shape or form approved by the original team</li> </ul> <p>And we don’t want this to in any way impact the actual use case Plausible is built for. A completely open source and self-hostable web analytics tool that helps people de-Googlify their sites and respect the privacy of their visitors.</p> </blockquote>
<p>That license change has helped us build <a href="https://plausible.io/blog/open-source-saas">a sustainable open source project</a> with 8 core team members and several paid external contributors at the time of writing. We don’t have any investors, so all this is solely funded by the fees our 12,000+ subscribers pay us.</p>
<p>As we’ve progressed in our journey, we’ve become aware that despite its advantages, the AGPL license doesn’t go far enough to protect us against all the threats we face. To better protect our project in the future, we’re changing some things about our self-hosted release. These changes don’t affect anyone subscribing to the Plausible Analytics managed hosting in the cloud. Everything stays the same there.</p>
<p>If you have a blog, a personal site or a business site, we want you to be able to self-host our code on your servers without needing to pay us any fees if that’s your preference. Nothing changes about this either. Our self-hosted code continues being AGPL-licensed, “free as in beer” and is the same code running in the production on our Plausible Analytics managed hosting in the cloud. </p>
<p>Here’s more on what’s being changed and why.</p>
<ol id="markdown-toc"> <li><a href="#threats-were-facing" id="markdown-toc-threats-were-facing">Threats we’re facing</a></li> <li><a href="#sticking-to-the-agpl-license" id="markdown-toc-sticking-to-the-agpl-license">Sticking to the AGPL license </a></li> <li><a href="#changes-were-making-to-our-self-hosted-release" id="markdown-toc-changes-were-making-to-our-self-hosted-release">Changes we’re making to our self-hosted release</a> <ol> <li> <li><a href="#a-stronger-moat-for-our-official-managed-hosting" id="markdown-toc-a-stronger-moat-for-our-official-managed-hosting">A stronger moat for our official managed hosting</a></li> <li><a href="#features-to-be-offered-on-top-of-plausible-ce" id="markdown-toc-features-to-be-offered-on-top-of-plausible-ce">Features to be offered on top of Plausible CE</a></li> </ol> </li> <li><a href="#ensuring-the-continuous-sustainability" id="markdown-toc-ensuring-the-continuous-sustainability">Ensuring the continuous sustainability</a></li> </ol>
<h2 id="threats-were-facing">Threats we’re facing</h2>
<p>We want to reduce the threat from businesses such as hosting companies and other resellers who try to commercialize popular open source projects. We’ve seen our self-hosted code being used in a way it was never intended for by corporations that don’t care about us or open source. Our license doesn’t prevent these corporations from using the Plausible Analytics name and logo either, as these are a part of our self-hosted code.</p>
<p>We face threats both from single-tenant and multi-tenant hosts and resellers. These entities compete against us using our work and brand without contributing back. For them it’s a business opportunity to commercialize open source software. They are even damaging our brand. We’ve had cases of these entities:</p>
<ul> <li>Confusing people and making them think that we approve of the service they run</li> <li>Reselling our self-hosted code while running Google Analytics and other surveillance capitalism scripts on their site</li> <li>Using Google and Amazon-owned servers despite promising that they’re running on <a href="https://plausible.io/blog/google-analytics-illegal">EU-owned infrastructure</a></li> <li>Having a bad user experience with under provisioned servers, slow loading time, frequent downtime and no support </li> <li>Running paid advertising on Google and paying referral fees for people to promote them. Both marketing tactics that <a href="https://plausible.io/blog/best-marketing-practices">we say no to ourselves</a></li> </ul>
<p>These use cases were never the intention behind our self-hosted release. We wanted to give site owners a way to de-Google their sites even if they didn’t want to or couldn’t use our managed service. Now, we’re facing situations where companies much larger than us with much bigger resources use our brand to compete against us and gain a business advantage.</p>
<p>It’s telling that these entities rarely decide to build their brand on top of our code. They prefer to use the brand we’ve built instead as it gives them a better and easier starting point. These actions not only threaten our sustainability but also mislead people who trust the Plausible Analytics name. We want to ensure that anyone using or considering our services knows precisely what they’re getting, without confusion or misrepresentation.</p>
<h2 id="sticking-to-the-agpl-license">Sticking to the AGPL license </h2>
<p>We’re real people who have rent to pay and mouths to feed. We make $300 per month from donations from our self-hosted users. It would take us more than ten years of donations to pay one month of salary for our small team. If we cannot capture the economic value of our work, the project will become unsustainable and die.</p>
<p>We believe this would not only hurt us as people but also all of the users of the product. Our self-hosted users directly benefit from the investments we can put into product development. All this is made possible and solely funded by the revenue we get from our managed hosting in the cloud.</p>
<p>We’ve learned that being open source and transparent has costs. Dealing with these threats is an incredibly taxing and time-consuming distraction for our small team. It’s something our proprietary competitors never even have to think about.</p>
<p>Several popular open source projects have shifted away from traditional models in the last few years because of similar threats that we’re facing. They’re adopting licenses such as the Business Source License (BSL) or the Server Side Public License (SSPL) as these licenses more directly address the concerns about resellers and hosting companies.</p>
<p>These licenses are <a href="https://opensource.org/licenses/">not approved nor recognized by the Open Source Initiative (OSI)</a>, which is why we’re not interested in switching to them. We want to stay open source to continue providing a transparent and privacy-friendly web analytics tool with a “free as in beer” self-hosted release.</p>
<h2 id="changes-were-making-to-our-self-hosted-release">Changes we’re making to our self-hosted release</h2>
<p>Here’s how we’ll proceed with our self-hosted release. These changes will go live with the next version of our self-hosted release (2.1.0). <a href="https://github.com/plausible/analytics/releases/tag/v2.1.0-rc.0">Release notes are here</a>.</p>
<p>Our self-hosted release will be renamed Plausible Community Edition (or Plausible CE). Plausible CE will also have a different logo from the Plausible Analytics managed hosting in the cloud. This is the main change you’ll see if you’re running our self-hosted code on your server. </p>
<p>Plausible CE continues to be AGPL-licensed and “free as in beer”. It continues to be the same code running in the production on our managed hosting. As we’ve done so far, we’ll keep maintaining, improving and adding features to the Plausible CE with new releases twice annually.</p>
<p>To further protect our brand from threats, we’ve registered trademarks for Plausible Analytics on both sides of the Atlantic. </p>
<h3 id="a-stronger-moat-for-our-official-managed-hosting">A stronger moat for our official managed hosting</h3>
<p>We’re excluding features we use to manage Plausible Analytics at scale, such as the sites API and customer relationship management system, from Plausible CE. As a self-hoster, you likely haven’t noticed these features, as they’ve been undocumented and are primarily helpful when providing web analytics as a service.</p>
<p>We’re also keeping some of the newly released business and enterprise features (<a href="https://plausible.io/docs/funnel-analysis">funnels</a> and <a href="https://plausible.io/docs/ecommerce-revenue-tracking">ecommerce revenue metrics</a> at the time of being) exclusive to the business plan subscribers on our Plausible Analytics managed hosting. </p>
<p>The code for all these features is still available to the public so it can be reviewed and inspected, but these features will not be a part of Plausible CE and they’ll have a different license.</p>
<p>This change also means that if you want to contribute to our codebase in the future as an external contributor, you’ll need to sign a Contributor License Agreement (CLA).</p>
<p>To provide a better service and experience to people who pay our bills, Plausible CE will continue to not have any premium customer support and is <a href="https://plausible.io/blog/building-open-source">community-supported only</a>.</p>
<p>This open core model is something that GitLab and other open source projects have championed and we believe it’s the best and most open source friendly approach for the situation we’re in.</p>
<h3 id="features-to-be-offered-on-top-of-plausible-ce">Features to be offered on top of Plausible CE</h3>
<p>The plan moving forward is to carefully consider which features to keep exclusive to our managed cloud hosting. These will predominantly be features that help us run our managed hosting at scale and enterprise features. We aim always to ensure a protective barrier around our cloud offering.</p>
<p>This doesn’t mean that all business and enterprise features won’t ever make it to the Plausible CE. Nothing from Plausible CE will be taken away in the future and we plan to periodically evaluate business and enterprise features to be added to Plausible CE. </p>
<p>For instance, we’ve already decided to release <a href="https://plausible.io/docs/custom-props/introduction">custom properties</a> (another business feature in the cloud) as part of the Plausible CE. We’ll update the release notes with any new features making their way to Plausible CE in the future.</p>
<h2 id="ensuring-the-continuous-sustainability">Ensuring the continuous sustainability</h2>
<p>These changes will affect corporations that take our code to compete directly against us as it will create a more substantial moat and better protection for our official managed hosting. It will get more challenging for them to misrepresent their association with us or use our brand and logo misleadingly. And they won’t be able to offer a fully featured Plausible Analytics product with all the enterprise features included either.</p>
<p>Many indie hackers that contact us about our experience of running an open source startup tell us that their main concern about open sourcing their code is the risk that large corporations will resell that code and take advantage of their project. It’s a valid concern. We hope that our new approach mitigates most potential downsides and still allows the upsides of releasing our code free to the world. </p>
<p>With the change of our self-hosted release name and logo, the registration of our trademarks and the exclusive availability of certain enterprise features to our managed cloud hosting, we aim to better ensure the sustainability of Plausible Analytics in the face of external threats in the future. Thank you for your support!</p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 10
- 0
cache/2024/d6e0a9beec0623cc66cede135fc7acdc/index.md
File diff suppressed because it is too large
View File


+ 203
- 0
cache/2024/ecae6fcce7e86066e432b5f38b2299ca/index.html View File

@@ -0,0 +1,203 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Everyone seems to forget why GNOME and GNOME 3 and Unity happened (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://liam-on-linux.dreamwidth.org/85359.html">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Everyone seems to forget why GNOME and GNOME 3 and Unity happened</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://liam-on-linux.dreamwidth.org/85359.html" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-02-23
</p>
</nav>
<hr>
<p>That is *what* it came from, yes, but not *why*.</p>
<p>The "why" part seems to be forgotten now: because Microsoft was threatening to sue all the Linux vendors shipping Windows 95-like desktops.</p>
<p>https://www.theregister.com/2006/11/20/microsoft_claims_linux_code</p>
<p>Microsoft invented the Win95 desktop from scratch. Its own previous Ones (e.g. Windows for Workgroups 3.11, Windows NT 3.51 and OS/2 1.x) looked nothing like it.</p>
<p>The task bar, the Start menu, the system tray, "My Computer", "Network Neighbourhood", all that: all original, *patented* Microsoft designs. There was nothing like it before. </p>
<p>(The closest was Acorn's RISC OS, with an "icon bar" that works very differently, on the Archimedes computer. A handful of those were imported to North America, and right after, NeXT "invented" the Dock, and then Microsoft invented the task bar which is quite a bit more sophisticated.</p>
<p>One source: the team that programmed it. Here's me moderating a panel discussion by most of the surviving members of Acorn's programming team, on video from a month ago:</p>
<p>https://www.youtube.com/watch?v=P_SDL0IwbCc</p>
<p>SUSE signed a patent-sharing deal:</p>
<p>https://www.theregister.com/2006/11/03/microsoft_novell_suse_linux/</p>
<p>Note: SUSE is the biggest German Linux company. (Source: I worked for them until last year.) KDE is a German project. SUSE developers did a lot of the work on KDE. </p>
<p>So, when SUSE signed up, KDE was safe.</p>
<p>Red Hat and Ubuntu refused to sign.</p>
<p>So, both needed *non* Windows like desktops, ASAP, without a Start menu, without a taskbar, without a window menu at top left and minimize/maximize/close at top right, and so on.</p>
<p>Red Hat is the main sponsor of GNOME development. (When KDE was first launched, Qt was not GPL, so Red Hat refused to bundle it or support it, and wrote its own environment instead.)</p>
<p>Ubuntu tried to get involved with the development of GNOME 3, and was rebuffed. So it went its own way with Unity instead: basically, a Mac OS X rip-off, only IMHO done better. Myself, I still use both Unity and macOS every day. They are like twins, and switching between them is very easy.</p>
<p>So both RH and Ubuntu switched to non-Windows-like desktops by default.</p>
<p>In the end MS did not sue anyone... but it got what it wanted: total chaos in the Linux desktop world.</p>
<p>Before the threats, almost everyone used GNOME 2. Even SUSE bundled GNOME because its corporate owner bought the main GNOME 3rd party developers, Ximian, and forcibly merged the company into SUSE:</p>
<p>https://www.theregister.com/2004/01/07/novell_marries_suse_to_ximian/</p>
<p>SUSE, Red Hat, Debian, Ubuntu, even Sun Solaris used GNOME 2. Everyone liked GNOME 2.</p>
<p>Then Microsoft rattled its sabre, and the FOSS UNIX world splintered in all directions.</p>
<p>RH uses GNOME 3. Ubuntu used Unity, alienated a lot of people who only knew how to use Windows-like desktops, and that made Mint a huge success. GNOME 2 got forked as MATE, and Mint adopted it, helping a lot. Mint also built its own fork of GNOME 3, Cinnamon. Formerly tiny niche desktops like Xfce and LXDE got a *huge* boost. Debian adopted GNOME 3 and systemd, annoying lots of its developers and causing the Devuan fork to happen.</p>
<p>Here's an analysis I wrote at the time:</p>
<p>https://www.theregister.com/2013/06/03/thank_microsoft_for_linux_desktop_fail/</p>
<p>Yes, Unity evolved out of the Ubuntu netbook desktop, but the reason _why_ it did is that Ubuntu was getting threatened.</p>
<p>(Xubuntu and Lubuntu and Kubuntu are not official and not the defaults, so they don't endanger it.)</p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 34
- 0
cache/2024/ecae6fcce7e86066e432b5f38b2299ca/index.md View File

@@ -0,0 +1,34 @@
title: Everyone seems to forget why GNOME and GNOME 3 and Unity happened
url: https://liam-on-linux.dreamwidth.org/85359.html
hash_url: ecae6fcce7e86066e432b5f38b2299ca
archive_date: 2024-02-23
og_image: https://www.dreamwidth.org/img/silk/identity/user.png
description: That is *what* it came from, yes, but not *why*.
favicon: https://liam-on-linux.dreamwidth.org/favicon.ico
language: en_US

<p>That is *what* it came from, yes, but not *why*.</p>
<p>The "why" part seems to be forgotten now: because Microsoft was threatening to sue all the Linux vendors shipping Windows 95-like desktops.</p>
<p>https://www.theregister.com/2006/11/20/microsoft_claims_linux_code</p>
<p>Microsoft invented the Win95 desktop from scratch. Its own previous Ones (e.g. Windows for Workgroups 3.11, Windows NT 3.51 and OS/2 1.x) looked nothing like it.</p>
<p>The task bar, the Start menu, the system tray, "My Computer", "Network Neighbourhood", all that: all original, *patented* Microsoft designs. There was nothing like it before. </p>
<p>(The closest was Acorn's RISC OS, with an "icon bar" that works very differently, on the Archimedes computer. A handful of those were imported to North America, and right after, NeXT "invented" the Dock, and then Microsoft invented the task bar which is quite a bit more sophisticated.</p>
<p>One source: the team that programmed it. Here's me moderating a panel discussion by most of the surviving members of Acorn's programming team, on video from a month ago:</p><p>https://www.youtube.com/watch?v=P_SDL0IwbCc</p>
<p>SUSE signed a patent-sharing deal:</p><p>https://www.theregister.com/2006/11/03/microsoft_novell_suse_linux/</p>
<p>Note: SUSE is the biggest German Linux company. (Source: I worked for them until last year.) KDE is a German project. SUSE developers did a lot of the work on KDE. </p>
<p>So, when SUSE signed up, KDE was safe.</p>
<p>Red Hat and Ubuntu refused to sign.</p>
<p>So, both needed *non* Windows like desktops, ASAP, without a Start menu, without a taskbar, without a window menu at top left and minimize/maximize/close at top right, and so on.</p>
<p>Red Hat is the main sponsor of GNOME development. (When KDE was first launched, Qt was not GPL, so Red Hat refused to bundle it or support it, and wrote its own environment instead.)</p>
<p>Ubuntu tried to get involved with the development of GNOME 3, and was rebuffed. So it went its own way with Unity instead: basically, a Mac OS X rip-off, only IMHO done better. Myself, I still use both Unity and macOS every day. They are like twins, and switching between them is very easy.</p>
<p>So both RH and Ubuntu switched to non-Windows-like desktops by default.</p>
<p>In the end MS did not sue anyone... but it got what it wanted: total chaos in the Linux desktop world.</p>
<p>Before the threats, almost everyone used GNOME 2. Even SUSE bundled GNOME because its corporate owner bought the main GNOME 3rd party developers, Ximian, and forcibly merged the company into SUSE:</p>
<p>https://www.theregister.com/2004/01/07/novell_marries_suse_to_ximian/</p>
<p>SUSE, Red Hat, Debian, Ubuntu, even Sun Solaris used GNOME 2. Everyone liked GNOME 2.</p>
<p>Then Microsoft rattled its sabre, and the FOSS UNIX world splintered in all directions.</p>
<p>RH uses GNOME 3. Ubuntu used Unity, alienated a lot of people who only knew how to use Windows-like desktops, and that made Mint a huge success. GNOME 2 got forked as MATE, and Mint adopted it, helping a lot. Mint also built its own fork of GNOME 3, Cinnamon. Formerly tiny niche desktops like Xfce and LXDE got a *huge* boost. Debian adopted GNOME 3 and systemd, annoying lots of its developers and causing the Devuan fork to happen.</p>
<p>Here's an analysis I wrote at the time:</p>
<p>https://www.theregister.com/2013/06/03/thank_microsoft_for_linux_desktop_fail/</p>
<p>Yes, Unity evolved out of the Ubuntu netbook desktop, but the reason _why_ it did is that Ubuntu was getting threatened.</p>
<p>(Xubuntu and Lubuntu and Kubuntu are not official and not the defaults, so they don't endanger it.)</p>

+ 8
- 0
cache/2024/index.html View File

@@ -158,6 +158,8 @@
<li><a href="/david/cache/2024/ffaf50bf5d5e4cf870a618b518ee5ba7/" title="Accès à l’article dans le cache local : Portable EPUBs">Portable EPUBs</a> (<a href="https://willcrichton.net/notes/portable-epubs/" title="Accès à l’article original distant : Portable EPUBs">original</a>)</li>
<li><a href="/david/cache/2024/ecae6fcce7e86066e432b5f38b2299ca/" title="Accès à l’article dans le cache local : Everyone seems to forget why GNOME and GNOME 3 and Unity happened">Everyone seems to forget why GNOME and GNOME 3 and Unity happened</a> (<a href="https://liam-on-linux.dreamwidth.org/85359.html" title="Accès à l’article original distant : Everyone seems to forget why GNOME and GNOME 3 and Unity happened">original</a>)</li>
<li><a href="/david/cache/2024/d75afc90a9d3c3b5a56b69446795fbb5/" title="Accès à l’article dans le cache local : plaisir d'ébauche">plaisir d'ébauche</a> (<a href="https://www.la-grange.net/2024/01/06/ebauche" title="Accès à l’article original distant : plaisir d'ébauche">original</a>)</li>
<li><a href="/david/cache/2024/c3272392d462da90874d32841e5caac8/" title="Accès à l’article dans le cache local : Where have all the websites gone?">Where have all the websites gone?</a> (<a href="https://www.fromjason.xyz/p/notebook/where-have-all-the-websites-gone/" title="Accès à l’article original distant : Where have all the websites gone?">original</a>)</li>
@@ -190,6 +192,8 @@
<li><a href="/david/cache/2024/40a1a0f90d7ac25a6150e6e27ac310d4/" title="Accès à l’article dans le cache local : D’un enfant à l’autre">D’un enfant à l’autre</a> (<a href="https://www.la-grange.net/2024/02/04/enfant" title="Accès à l’article original distant : D’un enfant à l’autre">original</a>)</li>
<li><a href="/david/cache/2024/5da390fa8d93278cd62233d3915729e7/" title="Accès à l’article dans le cache local : Generating Config driven Dynamic Forms using Web Components">Generating Config driven Dynamic Forms using Web Components</a> (<a href="https://codeburst.io/generating-config-driven-dynamic-forms-using-web-components-7c8d400f7f2e" title="Accès à l’article original distant : Generating Config driven Dynamic Forms using Web Components">original</a>)</li>
<li><a href="/david/cache/2024/71b177818657a209fc76f23c08c21d25/" title="Accès à l’article dans le cache local : Dude, you broke the future!">Dude, you broke the future!</a> (<a href="https://www.antipope.org/charlie/blog-static/2018/01/dude-you-broke-the-future.html" title="Accès à l’article original distant : Dude, you broke the future!">original</a>)</li>
<li><a href="/david/cache/2024/f4d2d42eba58062be910407690ae447c/" title="Accès à l’article dans le cache local : The Web Component Success Story">The Web Component Success Story</a> (<a href="https://jakelazaroff.com/words/the-web-component-success-story/" title="Accès à l’article original distant : The Web Component Success Story">original</a>)</li>
@@ -198,6 +202,8 @@
<li><a href="/david/cache/2024/ea2cfc9aa425a6967d2cacd9f96ceb9e/" title="Accès à l’article dans le cache local : Ask LukeW: New Ways into Web Content">Ask LukeW: New Ways into Web Content</a> (<a href="https://lukew.com/ff/entry.asp?2008" title="Accès à l’article original distant : Ask LukeW: New Ways into Web Content">original</a>)</li>
<li><a href="/david/cache/2024/2cadf792810f64540605c86a1431cb6b/" title="Accès à l’article dans le cache local : Custom Forms with Web Components and "ElementInternals"">Custom Forms with Web Components and "ElementInternals"</a> (<a href="https://dev.to/stuffbreaker/custom-forms-with-web-components-and-elementinternals-4jaj" title="Accès à l’article original distant : Custom Forms with Web Components and "ElementInternals"">original</a>)</li>
<li><a href="/david/cache/2024/4a56aa5497e68df0c5bb1d5331203219/" title="Accès à l’article dans le cache local : When “Everything” Becomes Too Much: The npm Package Chaos of 2024">When “Everything” Becomes Too Much: The npm Package Chaos of 2024</a> (<a href="https://socket.dev/blog/when-everything-becomes-too-much" title="Accès à l’article original distant : When “Everything” Becomes Too Much: The npm Package Chaos of 2024">original</a>)</li>
<li><a href="/david/cache/2024/d890da6c22300e52daec05e9c5888784/" title="Accès à l’article dans le cache local : The Fucks and How We Give Them (A Manifesto)">The Fucks and How We Give Them (A Manifesto)</a> (<a href="https://www.patrickrhone.net/the-fucks-and-how-we-give-them-a-manifesto/" title="Accès à l’article original distant : The Fucks and How We Give Them (A Manifesto)">original</a>)</li>
@@ -256,6 +262,8 @@
<li><a href="/david/cache/2024/e990536ed88823f047296ea25a6b7933/" title="Accès à l’article dans le cache local : Samsung caught faking zoom photos of the Moon">Samsung caught faking zoom photos of the Moon</a> (<a href="https://www.theverge.com/2023/3/13/23637401/samsung-fake-moon-photos-ai-galaxy-s21-s23-ultra" title="Accès à l’article original distant : Samsung caught faking zoom photos of the Moon">original</a>)</li>
<li><a href="/david/cache/2024/d6e0a9beec0623cc66cede135fc7acdc/" title="Accès à l’article dans le cache local : Introducing Plausible Community Edition">Introducing Plausible Community Edition</a> (<a href="https://plausible.io/blog/community-edition" title="Accès à l’article original distant : Introducing Plausible Community Edition">original</a>)</li>
<li><a href="/david/cache/2024/90e6434dbda21f9d18ad8fa53c822b47/" title="Accès à l’article dans le cache local : OSI Board Meeting Minutes, Wednesday, March 4, 2009">OSI Board Meeting Minutes, Wednesday, March 4, 2009</a> (<a href="https://opensource.org/meeting-minutes/minutes20090304/" title="Accès à l’article original distant : OSI Board Meeting Minutes, Wednesday, March 4, 2009">original</a>)</li>
<li><a href="/david/cache/2024/359df603dbf60e8476027b2eb26cb7ce/" title="Accès à l’article dans le cache local : uv: Python packaging in Rust">uv: Python packaging in Rust</a> (<a href="https://astral.sh/blog/uv" title="Accès à l’article original distant : uv: Python packaging in Rust">original</a>)</li>

Loading…
Cancel
Save