<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/rss/style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>
    Snippets | Stefan Judis Web Development
  </title>
  <subtitle>
    Useful snippets from the internet
  </subtitle>
  <link href="https://www.stefanjudis.com/feeds/snippets.xml" rel="self"/>
  <link href="https://www.stefanjudis.com/"/>
  <updated>
    2025-12-22T23:00:00+00:00
  </updated>
  <id>
    https://www.stefanjudis.com/
  </id>
  <author>
    <name>
      Stefan Judis
    </name>
    <email>
      stefanjudis@gmail.com
    </email>
  </author>
    
    <entry>
      <title>
        Feature detection of View Transition Types
      </title>
      <link href="https://www.stefanjudis.com/snippets/feature-detection-of-view-transition-types/"/>
      <published>2025-12-22T23:00:00+00:00</published>
      <updated>2025-12-22T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/feature-detection-of-view-transition-types/
      </id>
      <category term="snippet"></category>
        <category term="JavaScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>I've been working on the new Web Weekly website and implemented the fairly <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using_types" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FView_Transition_API%2FUsing_types')">new View Transition Types</a> to trigger different view transitions depending on specific actions. I discovered that the usual feature detection doesn't work. Let's dive in!</p>
<hr aria-hidden="true"><p>View Transitions support is still fairly new across browsers.</p>
<div class="highlightBox mdn margin-top-xl margin-bottom-xl">
        <div class="cornerBubble">
          <svg aria-hidden="true"><use xlink:href="/sprite.svg#icon-mdn"></use></svg>
        </div>
        <div class="highlightBox__header">MDN Compat Data (<a href="https://raw.githubusercontent.com/mdn/browser-compat-data/refs/heads/main/api/Document.json">source</a>)</div>
        <div class="highlightBox__body">
          <div class="highlightBox__overflow">
            <table class="highlightBox__compat">
              <caption>Browser support info for <a href="https://developer.mozilla.org/docs/Web/API/Document/startViewTransition">View Transitions</a> </caption>
              <thead>
                <tr>
                  <td>
                        <img src="/assets/browsers/chrome.webp"
                              srcset="/assets/browsers/chrome.webp, /assets/browsers/chrome@2.webp 2x"
                              width="48" height="51" alt="chrome">
                      </td><td>
                        <img src="/assets/browsers/chrome_android.webp"
                              srcset="/assets/browsers/chrome_android.webp, /assets/browsers/chrome_android@2.webp 2x"
                              width="48" height="51" alt="chrome_android">
                      </td><td>
                        <img src="/assets/browsers/edge.webp"
                              srcset="/assets/browsers/edge.webp, /assets/browsers/edge@2.webp 2x"
                              width="48" height="51" alt="edge">
                      </td><td>
                        <img src="/assets/browsers/firefox.webp"
                              srcset="/assets/browsers/firefox.webp, /assets/browsers/firefox@2.webp 2x"
                              width="48" height="51" alt="firefox">
                      </td><td>
                        <img src="/assets/browsers/firefox_android.webp"
                              srcset="/assets/browsers/firefox_android.webp, /assets/browsers/firefox_android@2.webp 2x"
                              width="48" height="51" alt="firefox_android">
                      </td><td>
                        <img src="/assets/browsers/safari.webp"
                              srcset="/assets/browsers/safari.webp, /assets/browsers/safari@2.webp 2x"
                              width="48" height="51" alt="safari">
                      </td><td>
                        <img src="/assets/browsers/safari_ios.webp"
                              srcset="/assets/browsers/safari_ios.webp, /assets/browsers/safari_ios@2.webp 2x"
                              width="48" height="51" alt="safari_ios">
                      </td><td>
                        <img src="/assets/browsers/samsunginternet_android.webp"
                              srcset="/assets/browsers/samsunginternet_android.webp, /assets/browsers/samsunginternet_android@2.webp 2x"
                              width="48" height="51" alt="samsunginternet_android">
                      </td><td>
                        <img src="/assets/browsers/webview_android.webp"
                              srcset="/assets/browsers/webview_android.webp, /assets/browsers/webview_android@2.webp 2x"
                              width="48" height="51" alt="webview_android">
                      </td>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>
                        <span class="highlightBox__pill success margin-top-s">
                        111
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        111
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        111
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        144
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        144
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        18
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        18
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        22.0
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        111
                        </span>
                      </td>
                </tr>
              </tbody>
            </table>
          </div>
<p></div>
</div></p>
<p>Of course, it depends on your browser support, but I will go with implementing feature detection when using View Transitions. To do so, it's usually fine to check for <code>document<wbr>.startViewTransition</code> being available.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token comment">// "standard" view transition feature detection</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// update the DOM</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>If you now want to use View Transition Types, the simple feature detection testing <code>document<wbr>.startViewTransition</code> won't work because <code>startViewTransition</code> can be used with a different syntax.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token comment">// only checking for `startViewTransition` won't work for</span>
<span class="token comment">// browsers not supporting view transition types</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// update the DOM</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">types</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"theme-switch"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Note that when you pass in the View Transition Types, <code>startViewTransition</code> isn't called with a callback function but with a configuration object containing an <code>update</code> function (the initial callback) and the desired <code>types</code>. Firefox, in my case now, does support <code>startViewTransition</code> but doesn't support the new View Transition object syntax.</p>
<p>It took me a while to figure it out, so here's how you can feature detect View Transition Types.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> supportsViewTransitions <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">;</span>
<span class="token comment">// feature detection for View Transition Types</span>
<span class="token keyword">const</span> supportsViewTransitionTypes <span class="token operator">=</span> <span class="token keyword">typeof</span> ViewTransitionTypeSet <span class="token operator">!==</span> <span class="token string">"undefined"</span><span class="token punctuation">;</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span>supportsViewTransitions <span class="token operator">&amp;&amp;</span> supportsViewTransitionTypes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// update the DOM</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">types</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"theme-switch"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And there we go! In this scenario, Firefox will not run any View Transitions because it doesn't support View Transition Types. All the other browsers are good to go then!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Feature detection of View Transition Types">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Three conditional border-radius CSS snippets
      </title>
      <link href="https://www.stefanjudis.com/snippets/three-conditional-border-radius-css-snippets/"/>
      <published>2025-11-16T23:00:00+00:00</published>
      <updated>2025-11-16T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/three-conditional-border-radius-css-snippets/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>You probably know the problem of rounded boxes. You give a box a nice <code>border-radius</code>, everything looks good and all, and poof, if the box hits the viewport edges it looks terrible. I'm just cleaning up some tabs and I'm 100% certain that I'll come back to these snippets in the future. Maybe they're useful to you, too!</p>
<p>Here's <a href="https://ishadeed.com/article/conditional-border-radius/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fishadeed.com%2Farticle%2Fconditional-border-radius%2F')">Ahmad sharing his conditional border-radius solution</a>:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>0px<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>100vw - 4px<span class="token punctuation">)</span> - 100%<span class="token punctuation">)</span> * 9999<span class="token punctuation">,</span> 8px<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>This snippet should definitely come with a code comment because I doubt many people understand what's going on there. I'm not sure I do, even...</p>
<hr aria-hidden="true"><p>Here's <a href="https://css-tip.com/conditional-border-radius/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fcss-tip.com%2Fconditional-border-radius%2F')">Temani's solution</a>:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.box</span> <span class="token punctuation">{</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">sign</span><span class="token punctuation">(</span>100cqi - 100%<span class="token punctuation">)</span>*2rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>This is a little better but yeah... Still not very readable.</p>
<hr aria-hidden="true"><p>And here's <a href="https://una.im/5-css-functions/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Funa.im%2F5-css-functions%2F')">Una's approach using the new CSS functions</a>:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* Conditionally apply a radius until you are (default: 4px, or specify second argument) from the edge of your screen */</span>
<span class="token atrule"><span class="token rule">@function</span> <span class="token function">--conditional-radius</span><span class="token punctuation">(</span>--radius<span class="token punctuation">,</span> <span class="token property">--edge-dist</span><span class="token punctuation">:</span> 4px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  <span class="token property">result</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>0px<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>100vw - <span class="token function">var</span><span class="token punctuation">(</span>--edge-dist<span class="token punctuation">)</span><span class="token punctuation">)</span> - 100%<span class="token punctuation">)</span> * 1e5<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--radius<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/* usage */</span>
<span class="token selector">.box</span> <span class="token punctuation">{</span>
  <span class="token comment">/*  1rem border radius, default (4px) distance  */</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">--conditional-radius</span><span class="token punctuation">(</span>1rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.box-2</span> <span class="token punctuation">{</span>
  <span class="token comment">/*  1rem border radius, right at the edge (0px distance)  */</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">--conditional-radius</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 0px<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>If you look closely, you might discover that it's basically Ahmad's approach above wrapped in a CSS function (<code>--conditional-radius</code>), and I think this makes this CSS so much better. Bring in the complicated CSS once, but hide all its complexity. Nice nice!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Three conditional border-radius CSS snippets">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Turn off macOS Liquid Glass via the CLI
      </title>
      <link href="https://www.stefanjudis.com/snippets/turn-off-macos-liquid-glass-via-the-cli/"/>
      <published>2025-10-25T22:00:00+00:00</published>
      <updated>2025-10-25T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/turn-off-macos-liquid-glass-via-the-cli/
      </id>
      <category term="snippet"></category>
        <category term="macOS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><strong>Loud and bold disclaimer: I haven't used the following CLI command because I'm still holding onto the old macOS version. I'm not ready to deal with the &quot;Liquid Glass&quot; situation. However, if I upgrade my OS, I want to find this snippet in my bookmarks (aka my blog).</strong></p>
<p>Here's <a href="https://tidbits.com/2025/10/09/how-to-turn-liquid-glass-into-a-solid-interface/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Ftidbits.com%2F2025%2F10%2F09%2Fhow-to-turn-liquid-glass-into-a-solid-interface%2F')">Adam sharing all sorts of tricks to make Apple's new Liquid Glass design language more accessible</a>. He claims that you can nuke it entirely with one CLI command (again, I didn't run it):</p>
<pre><code>defaults write -g com.apple.SwiftUI.DisableSolarium -bool YES
</code></pre>
<p>The article is definitely worth reading if you've had enough of all these translucent UI elements.</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Turn off macOS Liquid Glass via the CLI">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Query &quot;newly&quot; available baseline features in Node.js
      </title>
      <link href="https://www.stefanjudis.com/snippets/query-newly-available-baseline-features-in-node-js/"/>
      <published>2025-03-08T23:00:00+00:00</published>
      <updated>2025-03-08T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/query-newly-available-baseline-features-in-node-js/
      </id>
      <category term="snippet"></category>
        <category term="NodeJS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://web.dev/articles/web-platform-dashboard-baseline" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fweb.dev%2Farticles%2Fweb-platform-dashboard-baseline')">Jeremy's post describes the Web Platform Dashboard and its underlying API</a>. If you don't know what I'm talking about, here's <a href="https://webstatus.dev/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwebstatus.dev%2F')">the Web Platform Dashboard</a>. 👇</p>
<p>
      <div class="sqip-container margin-top-l margin-bottom-l">
        <figure class="sqip-image" style="
          --color1: rgb(239,150,60); --color2: rgb(158,87,82); --color3: rgb(252,230,109); --color4: rgb(164,175,180); --color5: rgb(60,60,67);
          --sqip-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMTY5Ij48cGF0aCBmaWxsPSIjM2MzYzQzIiBkPSJNMCAwaDMwMHYxNjlIMHoiLz48ZyBmaWxsLW9wYWNpdHk9Ii41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguNiAuNikgc2NhbGUoMS4xNzE4OCkiPjxjaXJjbGUgY3g9IjE0MSIgY3k9IjgwIiByPSIxNjUiIGZpbGw9IiNmZmYiLz48Y2lyY2xlIGN4PSIxNTEiIGN5PSI3OSIgcj0iMTc2IiBmaWxsPSIjZmZmIi8+PGNpcmNsZSBjeD0iMTE3IiBjeT0iNzIiIHI9IjE2OCIgZmlsbD0iI2ZmZiIvPjxlbGxpcHNlIGN4PSIxMjEiIGN5PSI4OSIgZmlsbD0iI2ZmZiIgcng9IjE3MiIgcnk9IjE0NCIvPjxjaXJjbGUgcj0iMSIgZmlsbD0iIzZlNmU2ZiIgdHJhbnNmb3JtPSJtYXRyaXgoLTMxLjk3MTU4IC0uMjc5MDEgLjAxMzggLTEuNTgxNTMgNjUuOSAyMS45KSIvPjxwYXRoIGZpbGw9IiMyYTJhMjkiIGQ9Ik0xMSA3aDM3djJIMTF6Ii8+PGNpcmNsZSBjeD0iMTYyIiByPSI2OCIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjM2IiBjeT0iMTA4IiByPSI4OCIgZmlsbD0iI2ZjZmZmZiIvPjwvZz48L3N2Zz4=');
        ">
          <a href="//images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png">
            <picture>
              <source type="image/avif"
                srcset="//images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=avif&fit=scale&q=75&w=300&h=170 300w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=avif&fit=scale&q=75&w=500&h=284 500w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=avif&fit=scale&q=75&w=700&h=398 700w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=avif&fit=scale&q=75&w=900&h=512 900w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=avif&fit=scale&q=75&w=1100&h=626 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <source type="image/webp"
                srcset="//images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=webp&fit=scale&q=75&w=300&h=170 300w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=webp&fit=scale&q=75&w=500&h=284 500w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=webp&fit=scale&q=75&w=700&h=398 700w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=webp&fit=scale&q=75&w=900&h=512 900w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=webp&fit=scale&q=75&w=1100&h=626 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <img width="1000" height="569"
                srcset="//images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=jpg&fit=scale&q=75&w=300&h=170 300w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=jpg&fit=scale&q=75&w=500&h=284 500w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=jpg&fit=scale&q=75&w=700&h=398 700w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=jpg&fit=scale&q=75&w=900&h=512 900w, //images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png?fm=jpg&fit=scale&q=75&w=1100&h=626 1100w"
                sizes="(max-width: 50em) 98vw, 700px"
                src="//images.ctfassets.net/f20lfrunubsq/3tHaxzdiGpRUVM6Df9HsCb/aec490d36f18cf57b555c71493b4fbc5/Screenshot_2025-03-09_at_11.11.51.png"
                alt="Web Platform features dashboard showing browser support and baseline information."
                loading="lazy"
                onload="this.classList.add('kf-fade-in')">
            </picture>
          </a>
        </figure>
      </div>
    </p>
<p>The Web Platform Dashboard is wonderful because you can query the data set for baseline status, dates, and specific features. If you're into web platform updates, this site is there to get lost. Luckily, everything's powered by an API, too.</p>
<p>Jeremy's post includes many example snippets to give you an idea of what to query.</p>
<p>I sat down, adjusted the examples, and here's my new script to access all &quot;newly&quot; available baseline features in my terminal via <code>node index<wbr>.mjs</code>.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> styleText <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"node:util"</span><span class="token punctuation">;</span>

<span class="token comment">// More info on: https://web.dev/articles/web-platform-dashboard-baseline</span>

<span class="token keyword">const</span> <span class="token constant">API_BASE_URL</span> <span class="token operator">=</span> <span class="token string">"https://api.webstatus.dev/v1/features"</span><span class="token punctuation">;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">queryWebStatusDashboard</span><span class="token punctuation">(</span><span class="token parameter">query<span class="token punctuation">,</span> featureData <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> token</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> urlBase <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">API_BASE_URL</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?q=</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
    <span class="token keyword">let</span> queryUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>urlBase<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>token<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      queryUrl <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&amp;page_token=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>queryUrl<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>
        <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Failed to query dashboard </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>queryUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>response<span class="token punctuation">.</span>statusText<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">,</span> metadata <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    featureData <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>featureData<span class="token punctuation">,</span> <span class="token operator">...</span>data<span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">"next_page_token"</span> <span class="token keyword">in</span> metadata<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> <span class="token punctuation">{</span> next_page_token <span class="token punctuation">}</span> <span class="token operator">=</span> metadata<span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">queryWebStatusDashboard</span><span class="token punctuation">(</span>query<span class="token punctuation">,</span> featureData<span class="token punctuation">,</span> next_page_token<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> featureData<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Failed to query dashboard: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token punctuation">.</span>message<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> <span class="token function-variable function">renderName</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">styleText</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"bold"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> text<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">renderLink</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">styleText</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"underline"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> text<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">getFormatedFeatures</span><span class="token punctuation">(</span><span class="token parameter">features</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> features
    <span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a<span class="token punctuation">.</span>feature_id<span class="token punctuation">.</span><span class="token function">localeCompare</span><span class="token punctuation">(</span>b<span class="token punctuation">.</span>feature_id<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>
      <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> name<span class="token punctuation">,</span> feature_id <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span>
        <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">renderName</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n  - </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">renderLink</span><span class="token punctuation">(</span>
          <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://webstatus.dev/features/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>feature_id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
        <span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
    <span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">"\n\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> newFeatures <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">queryWebStatusDashboard</span><span class="token punctuation">(</span><span class="token string">"baseline_status:newly"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> report <span class="token operator">=</span> <span class="token function">getFormatedFeatures</span><span class="token punctuation">(</span>newFeatures<span class="token punctuation">)</span><span class="token punctuation">;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>report<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Error in main:"</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
    process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>It fetches the data, paginates if needed, and applies basic formatting.</p>
<p>
      <div class="sqip-container margin-top-l margin-bottom-l">
        <figure class="sqip-image" style="
          --color1: rgb(168,180,76); --color2: rgb(175,92,101); --color3: rgb(228,113,183); --color4: rgb(189,190,190); --color5: rgb(93,49,64);
          --sqip-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMTY1Ij48cGF0aCBmaWxsPSIjNWQzMTQwIiBkPSJNMCAwaDMwMHYxNjVIMHoiLz48ZyBmaWxsLW9wYWNpdHk9Ii41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguNiAuNikgc2NhbGUoMS4xNzE4OCkiPjxjaXJjbGUgY3g9IjE3NyIgY3k9IjM2IiByPSIyMTYiIGZpbGw9IiMwMDBhMTciLz48Y2lyY2xlIHI9IjEiIGZpbGw9IiMwMDBmMWYiIHRyYW5zZm9ybT0ibWF0cml4KDY2LjYxODA5IDIwOS4xODU1NSAtNzkuMDYwNTggMjUuMTc3OTYgMTc3LjQgMjYuNSkiLz48ZWxsaXBzZSBjeD0iNTEiIGN5PSI3MyIgZmlsbD0iIzIyMzEzZCIgcng9IjEyMSIgcnk9IjY3Ii8+PHBhdGggZmlsbD0iIzc2Nzc3ZCIgZD0iTTE2IDUxaDE1OXY0SDE2eiIvPjxwYXRoIGZpbGw9IiM2ZjcwNzYiIGQ9Ik0xNSA4NGgxNjV2NEgxNXoiLz48cGF0aCBmaWxsPSIjODQ4NjhhIiBkPSJNMTkgMThoMTI0djNIMTl6Ii8+PGVsbGlwc2UgY3g9Ijg1IiBjeT0iMzYiIGZpbGw9IiM3NDc1N2IiIHJ4PSI4MCIgcnk9IjIiLz48ZWxsaXBzZSBjeD0iNyIgY3k9IjYzIiBmaWxsPSIjOTg5YmEyIiByeD0iNTciIHJ5PSIyIi8+PC9nPjwvc3ZnPg==');
        ">
          <a href="//images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png">
            <picture>
              <source type="image/avif"
                srcset="//images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=avif&fit=scale&q=75&w=300&h=165 300w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=avif&fit=scale&q=75&w=500&h=276 500w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=avif&fit=scale&q=75&w=700&h=387 700w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=avif&fit=scale&q=75&w=900&h=497 900w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=avif&fit=scale&q=75&w=1100&h=608 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <source type="image/webp"
                srcset="//images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=webp&fit=scale&q=75&w=300&h=165 300w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=webp&fit=scale&q=75&w=500&h=276 500w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=webp&fit=scale&q=75&w=700&h=387 700w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=webp&fit=scale&q=75&w=900&h=497 900w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=webp&fit=scale&q=75&w=1100&h=608 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <img width="1000" height="553"
                srcset="//images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=jpg&fit=scale&q=75&w=300&h=165 300w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=jpg&fit=scale&q=75&w=500&h=276 500w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=jpg&fit=scale&q=75&w=700&h=387 700w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=jpg&fit=scale&q=75&w=900&h=497 900w, //images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png?fm=jpg&fit=scale&q=75&w=1100&h=608 1100w"
                sizes="(max-width: 50em) 98vw, 700px"
                src="//images.ctfassets.net/f20lfrunubsq/69eRUI74KLhpSpw3h2xiNu/993468a5e3eb283991638cacd806a722/Screenshot_2025-03-09_at_11.17.16.png"
                alt="Terminal run showing the rendered result for newly available baseline entries."
                loading="lazy"
                onload="this.classList.add('kf-fade-in')">
            </picture>
          </a>
        </figure>
      </div>
    </p>
<p>This tiny script may be valuable for someone.</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Query &quot;newly&quot; available baseline features in Node.js">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Template Literal Types for validating files names
      </title>
      <link href="https://www.stefanjudis.com/snippets/template-literal-types-for-validating-files-names/"/>
      <published>2025-01-19T23:00:00+00:00</published>
      <updated>2025-01-19T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/template-literal-types-for-validating-files-names/
      </id>
      <category term="snippet"></category>
        <category term="TypeScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Here's <a href="https://macarthur.me/posts/template-literal-types/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fmacarthur.me%2Fposts%2Ftemplate-literal-types%2F')">Alex using one of my favorite TypeScript features</a> — <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwww.typescriptlang.org%2Fdocs%2Fhandbook%2F2%2Ftemplate-literal-types.html')">Template Literal Types</a> — to enforce file name conventions in TypeScript.</p>
<pre class="shiki dark-plus twoslash lsp" style="background-color: #1E1E1E; color: #D4D4D4"><div class="language-id">js</div><div class='code-container'><code><div class='line'><span style="color: #569CD6">type</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0"><data-lsp lsp='type ImageExtension = "jpg" | "jpeg" | "png" | "webp"' >ImageExtension</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">`png`</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">`jp</span><span style="color: #569CD6">${</span><span style="color: #CE9178">`e`</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">``</span><span style="color: #569CD6">}</span><span style="color: #CE9178">g`</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">`webp`</span><span style="color: #D4D4D4">;</span></div><div class='line'><span style="color: #569CD6">type</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0"><data-lsp lsp='type ImageFileName = `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >ImageFileName</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">`</span><span style="color: #569CD6">${</span><span style="color: #4EC9B0"><data-lsp lsp='type Lowercase&lt;S extends string> = intrinsic' >Lowercase</data-lsp></span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">&gt;</span><span style="color: #569CD6">}</span><span style="color: #CE9178">.</span><span style="color: #569CD6">${</span><span style="color: #4EC9B0"><data-lsp lsp='type ImageExtension = "jpg" | "jpeg" | "png" | "webp"' >ImageExtension</data-lsp></span><span style="color: #569CD6">}</span><span style="color: #CE9178">`</span><span style="color: #D4D4D4">;</span></div><div class='line'>&nbsp;</div><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-lsp lsp='const goodName1: `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >goodName1</data-lsp></span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0"><data-lsp lsp='type ImageFileName = `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >ImageFileName</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&apos;doggy1.jpeg&apos;</span><span style="color: #D4D4D4">;</span></div><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-lsp lsp='const goodName2: `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >goodName2</data-lsp></span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0"><data-lsp lsp='type ImageFileName = `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >ImageFileName</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&apos;doggy2.jpg&apos;</span><span style="color: #D4D4D4">;</span></div><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-err><data-lsp lsp='const badName: `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >badName</data-lsp></data-err></span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0"><data-lsp lsp='type ImageFileName = `${Lowercase&lt;string>}.jpg` | `${Lowercase&lt;string>}.jpeg` | `${Lowercase&lt;string>}.png` | `${Lowercase&lt;string>}.webp`' >ImageFileName</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&apos;KittyCat.webp&apos;</span><span style="color: #D4D4D4">;</span></div><span class="error"><span>Type '"KittyCat.webp"' is not assignable to type '`${Lowercase&lt;string&gt;}.jpg` | `${Lowercase&lt;string&gt;}.jpeg` | `${Lowercase&lt;string&gt;}.png` | `${Lowercase&lt;string&gt;}.webp`'.</span><span class="code">2322</span></span><span class="error-behind">Type '"KittyCat.webp"' is not assignable to type '`${Lowercase&lt;string&gt;}.jpg` | `${Lowercase&lt;string&gt;}.jpeg` | `${Lowercase&lt;string&gt;}.png` | `${Lowercase&lt;string&gt;}.webp`'.</span></code></div></pre>
<p>I just love this feature. Enforcing string patterns with TypeScript makes me feel like a TS pro. 😅</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Template Literal Types for validating files names">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Construct query strings from FormData
      </title>
      <link href="https://www.stefanjudis.com/snippets/formdata-to-url-search-params/"/>
      <published>2024-06-30T10:00:00+00:00</published>
      <updated>2024-06-30T10:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/formdata-to-url-search-params/
      </id>
      <category term="snippet"></category>
        <category term="TypeScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>I was wrangling some Remix TypeScript the other day and wanted to create a server action that receives submitted form data. This form data then should be used to build up a query string, make a request to a secured and hidden GET endpoint and return the result.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// what I wanted to do ...</span>
<span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">action</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>
  request<span class="token punctuation">,</span>
<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> formData <span class="token operator">=</span> <span class="token keyword">await</span> request<span class="token punctuation">.</span><span class="token function">formData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> queryString <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span>formData<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  
  <span class="token comment">// make another API call with the constructed query string</span>
  <span class="token keyword">const</span> resp <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://hidden-api.com?</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>queryString<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
  
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Easy peasy, that's what <code>URLSearchParams</code> is for, right? When you check MDN, you'll discover <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams#parameters" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FURLSearchParams%2FURLSearchParams%23parameters')">that passing <code>FormData</code> into the <code>URLSearchParams</code> constructor is valid</a>. Nice!</p>
<div class="highlightBox mdn margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-mdn"/>
            </svg>
          </div>
        

      
      <p>[The <code>URLSearchParams(options)</code> options object can be] a literal sequence of name-value string pairs, or any object — <strong>such as a FormData object</strong> — with an iterator that produces a sequence of string pairs.</p>
</div><p>Unfortunately, TypeScript isn't happy about passing a <code>FormData</code> type into the <code>URLSearchParams</code> constructor.</p>
<pre class="shiki dark-plus twoslash lsp" style="background-color: #1E1E1E; color: #D4D4D4"><div class="language-id">ts</div><div class='code-container'><code><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-lsp lsp='const formData: FormData' >formData</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA"><data-lsp lsp='var FormData: new (form?: HTMLFormElement | undefined, submitter?: HTMLElement | null | undefined) => FormData' >FormData</data-lsp></span><span style="color: #D4D4D4">();</span></div><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-lsp lsp='const queryString: string' >queryString</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA"><data-lsp lsp='var URLSearchParams: new (init?: string | string[][] | Record&lt;string, string> | URLSearchParams | undefined) => URLSearchParams' >URLSearchParams</data-lsp></span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE"><data-err><data-lsp lsp='const formData: FormData' >formData</data-lsp></data-err></span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA"><data-lsp lsp='(method) URLSearchParams.toString(): string' >toString</data-lsp></span><span style="color: #D4D4D4">();</span></div><span class="error"><span>Argument of type 'FormData' is not assignable to parameter of type 'string | string[][] | Record&lt;string, string&gt; | URLSearchParams | undefined'.
  Type 'FormData' is missing the following properties from type 'URLSearchParams': size, sort</span><span class="code">2345</span></span><span class="error-behind">Argument of type 'FormData' is not assignable to parameter of type 'string | string[][] | Record&lt;string, string&gt; | URLSearchParams | undefined'.
  Type 'FormData' is missing the following properties from type 'URLSearchParams': size, sort</span></code></div></pre>
<p>But why is <code>FormData</code> not allowed to be used with <code>URLSearchParams</code>? MDN says it should work, it's a common use case, what's up?</p>
<p>The problem is that <code>FormData</code> could also include non-string types such as a submitted <code>File</code>. As always, TypeScript is correct and rightfully complaining — the <code>FormData</code> object could hold data that <code>URLSearchParams</code> can't handle and this would lead to a nasty hard to find runtime exception.</p>
<p>So what's the solution?</p>
<p>I could now iterate over the form data keys and type guard them, but because I know there won't be a submitted file in my <code>FormData</code> quick type casting did the trick for me.</p>
<pre class="shiki dark-plus twoslash lsp" style="background-color: #1E1E1E; color: #D4D4D4"><div class="language-id">ts</div><div class='code-container'><code><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-lsp lsp='const formData: FormData' >formData</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA"><data-lsp lsp='var FormData: new (form?: HTMLFormElement | undefined, submitter?: HTMLElement | null | undefined) => FormData' >FormData</data-lsp></span><span style="color: #D4D4D4">();</span></div><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-lsp lsp='const searchParams: string' >searchParams</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA"><data-lsp lsp='var URLSearchParams: new (init?: string | string[][] | Record&lt;string, string> | URLSearchParams | undefined) => URLSearchParams' >URLSearchParams</data-lsp></span><span style="color: #D4D4D4">(</span></div><div class='line'><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE"><data-lsp lsp='const formData: FormData' >formData</data-lsp></span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">unknown</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0"><data-lsp lsp='type Record&lt;K extends string | number | symbol, T> = { [P in K]: T; }' >Record</data-lsp></span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">&gt;,</span></div><div class='line'><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA"><data-lsp lsp='(method) URLSearchParams.toString(): string' >toString</data-lsp></span><span style="color: #D4D4D4">();</span></div></code></div></pre>
<p>If you want to find more possible solutions, <a href="https://github.com/microsoft/TypeScript/issues/30584" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fmicrosoft%2FTypeScript%2Fissues%2F30584')">check this related GitHub issue</a>.</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Construct query strings from FormData">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Disable Angular&#39;s scroll position restoration for specific routes
      </title>
      <link href="https://www.stefanjudis.com/snippets/disable-angular-scroll-position-restoration/"/>
      <published>2024-04-26T22:00:00+00:00</published>
      <updated>2024-04-26T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/disable-angular-scroll-position-restoration/
      </id>
      <category term="snippet"></category>
        <category term="Angular"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>I've been doing Angular development lately, and as with any single-page app, the <em>&quot;Let's do everything in JS&quot;</em> approach breaks basic web functionality. Scroll handling is only one example.</p>
<div class="highlightBox info margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-info"/>
            </svg>
          </div>
        

      
      <p><strong>If you're looking for a way to alter URL query params without having Angular scroll to the top of the page</strong>, this post is for you.</p>
</div><p>But before we get to the problem, let's recognize what browsers are good at — doing web things. If your site relies on server-rendered HTML you get well-working scroll handling out of the box.</p>
<p>If you link to an anchor element (<code>/something#foo</code>), the browser scrolls to it. Do you hit the back button? The browser instantly shows the last page, thanks to the BF cache. And the page is even in the correct scroll position. Browsers are pretty good at handling websites. One might think they're built for this.</p>
<p>Sometimes (often?) SPA frameworks break these standard web features. And of course, then there's more JS added to fix and reimplement what broke.</p>
<p>Here's Angular's <code>RouterModule</code> configured to fix scroll handling.</p>
<pre class="language-javascript"><code class="language-javascript">@<span class="token function">NgModule</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">imports</span><span class="token operator">:</span> <span class="token punctuation">[</span>RouterModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span>routes<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token comment">// handle back and forward navigations</span>
    <span class="token literal-property property">scrollPositionRestoration</span><span class="token operator">:</span> <span class="token string">'enabled'</span><span class="token punctuation">,</span>
    <span class="token comment">// handle anchor links</span>
    <span class="token literal-property property">anchorScrolling</span><span class="token operator">:</span> <span class="token string">'enabled'</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token literal-property property">exports</span><span class="token operator">:</span> <span class="token punctuation">[</span>RouterModule<span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppRoutingModule</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
</code></pre>
<p><code>anchorScrolling</code> scrolls to anchors if they're available, and <code>scrollPositionRestoration</code> scrolls the previous page to its last position when hitting the back button. Ignoring the fact that there must be an array storing all navigation's scroll positions to reapply them, this works reasonably well. Forward navigations, on the other hand, are scrolled to the top.</p>
<p>But could there be situations  when you want to update the URL without scrolling around? You bet!</p>
<p>If you're building a shop search and want to apply filters or sorting (<code>?filter=shoes&amp;order=asc</code>), chances are high that you just want to update the URL and keep the current scroll position. Or, like in my case, maybe you want to open a modal or sidebar, and it should have a URL so you can link to it (<code>?sidebar=reviews</code>). If you're rendering UI in JS, there are plenty of cases in which you just want to update the URL.</p>
<p>So I thought I could slap an attribute onto a <code>[routerlink]</code> and call it a day.</p>
<pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- 
  ⚠️ Before you copy and paste this code ⚠️
  Note that I hoped for the `scrollPositionRestoration` 
  attribute but unfortunately it's not a thing.
--></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>
  <span class="token attr-name">[routerLink]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>[]<span class="token punctuation">"</span></span>
  <span class="token attr-name">[queryParams]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{sidebar: 'reviews', productId: productId}<span class="token punctuation">"</span></span>
  <span class="token attr-name">[scrollPositionRestoration]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>false<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  Show all reviews
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
</code></pre>
<p>But now it gets fun. Let me repeat the previous fact: <strong>forward navigations are scrolled to the top</strong>. Always? Yes always (unless you work around the core router behavior).</p>
<p><a href="https://github.com/angular/angular/blob/17.3.6/packages/router/src/router_scroller.ts#L83-L102" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fangular%2Fangular%2Fblob%2F17.3.6%2Fpackages%2Frouter%2Fsrc%2Frouter_scroller.ts%23L83-L102')">Here's the scroll logic from Angular <code>17<wbr>.3</code> core</a> if you're curious.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">private</span> <span class="token function">consumeScrollEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>transitions<span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">Scroll</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    
    <span class="token comment">// this part handles back navigations</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>position<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>scrollPositionRestoration <span class="token operator">===</span> <span class="token string">'top'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>viewportScroller<span class="token punctuation">.</span><span class="token function">scrollToPosition</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>scrollPositionRestoration <span class="token operator">===</span> <span class="token string">'enabled'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>viewportScroller<span class="token punctuation">.</span><span class="token function">scrollToPosition</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>position<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// imperative navigation "forward"</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>anchor <span class="token operator">&amp;&amp;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>anchorScrolling <span class="token operator">===</span> <span class="token string">'enabled'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>viewportScroller<span class="token punctuation">.</span><span class="token function">scrollToAnchor</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>anchor<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>options<span class="token punctuation">.</span>scrollPositionRestoration <span class="token operator">!==</span> <span class="token string">'disabled'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// You'll end up here if you have `scrollPositionRestoration`</span>
        <span class="token comment">// enabled and navigate forward</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>viewportScroller<span class="token punctuation">.</span><span class="token function">scrollToPosition</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Discovering this Angular core code took me a few hours already, but I can't be alone with this issue, can I? Of course not. Here's <a href="https://github.com/angular/angular/issues/26744" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fangular%2Fangular%2Fissues%2F26744')">the GitHub issue from 2018 asking for a way to temporarily disable <code>scrollPositionRestoration</code></a>.</p>
<p>The proposed framework solution is what I have hoped for: allow setting <code>scrollPositionRestoration</code> on each navigation. But no one seemed to have PR'ed, reacted or cared about the issue.</p>
<p>I tried a few solutions and here are the ones that worked for me:</p>
<ol>
<li><a href="https://github.com/angular/angular/issues/26744#issuecomment-550392777" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fangular%2Fangular%2Fissues%2F26744%23issuecomment-550392777')">Disable scroll position restoration entirely and roll your own scroll handling</a>. Ufff.</li>
<li><a href="https://github.com/angular/angular/issues/26744#issuecomment-550392777" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fangular%2Fangular%2Fissues%2F26744%23issuecomment-550392777')">Link to a fragment that doesn't exist and trick Angular into not scrolling</a>. Also, ufff.</li>
</ol>
<p>I was almost leaning into the ugly fragment hack but asked a colleague for a rubber duck session. And looking at the Angular core code, he came up with a dynamic JavaScript getter for the <code>scrollPositionRestoration</code> option.</p>
<pre class="language-javascript"><code class="language-javascript">RouterModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span>routes<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token keyword">get</span> <span class="token function">scrollPositionRestoration</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> params <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span>window<span class="token punctuation">.</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>params<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'sidebar'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token string">'disabled'</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token string">'enabled'</span> <span class="token keyword">as</span> <span class="token keyword">const</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">scrollOffset</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">164</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token literal-property property">anchorScrolling</span><span class="token operator">:</span> <span class="token string">'enabled'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>Whenever Angular updates the URL and checks if it should scroll around by accessing <code>this<wbr>.options<wbr>.scrollPositionRestoration</code>, the current URL is checked. If it includes a query param that shouldn't trigger scrolling (<code>sidebar</code>), <code>scrollPositionRestoration</code> returns <code>disabled</code>. Otherwise, it'll be <code>enabled</code>, and Angular will do its &quot;scroll magic&quot;.</p>
<p>Is this a perfect solution? I doubt it. Will there be edge cases where this approach leads to bugs? Most likely. Does it do the trick for me right now? Absolutely, because it's way better than rolling my own scroll handling or adding ugly URLs to the app.</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Disable Angular&#39;s scroll position restoration for specific routes">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Reveal an image with smart padding
      </title>
      <link href="https://www.stefanjudis.com/snippets/image-padding-trick/"/>
      <published>2024-04-19T22:00:00+00:00</published>
      <updated>2024-04-19T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/image-padding-trick/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://www.smashingmagazine.com/2024/04/sliding-3d-image-frames-css/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwww.smashingmagazine.com%2F2024%2F04%2Fsliding-3d-image-frames-css%2F')">Here's Temani Afif once again doing what he does best</a>: creating wild single-element tricks. What's on this time?</p>
<p>The preview below shows the article's result, which is two image elements.</p>
<p>
      <div class="sqip-container margin-top-l margin-bottom-l">
        <figure class="sqip-image" style="
          --color1: rgb(195,51,36); --color2: rgb(172,177,92); --color3: rgb(252,219,182); --color4: rgb(211,195,156); --color5: rgb(86,64,56);
          --sqip-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMjAxIj48cGF0aCBmaWxsPSIjNTY0MDM4IiBkPSJNMCAwaDMwMHYyMDFIMHoiLz48ZyBmaWxsLW9wYWNpdHk9Ii41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguNiAuNikgc2NhbGUoMS4xNzE4OCkiPjxjaXJjbGUgY3g9IjYzIiBjeT0iOTAiIHI9IjIxNSIgZmlsbD0iI2ZmZmZkMSIvPjxjaXJjbGUgcj0iMSIgZmlsbD0iIzU0MDAwMCIgdHJhbnNmb3JtPSJtYXRyaXgoLTM1LjM3NTUyIDcuNjQ4NDYgLTkuMjI3MiAtNDIuNjc3NSAxODQuOCAxMDguOCkiLz48ZWxsaXBzZSBjeD0iMTQiIGN5PSI2MiIgZmlsbD0iI2ZmZmZkNyIgcng9IjI1IiByeT0iMTcxIi8+PHBhdGggZmlsbD0iIzFlM2EwMCIgZD0ibTEwMi40IDE0OS42LTY2LTEuMiAxLjItNjQgNjYgMS4yeiIvPjxlbGxpcHNlIGN4PSIxMjciIGN5PSI4NiIgZmlsbD0iI2ZmZmZkOCIgcng9IjIyIiByeT0iMTcxIi8+PGNpcmNsZSByPSIxIiBmaWxsPSIjZmZmZmQ0IiB0cmFuc2Zvcm09InJvdGF0ZSgtNyA1NzEgLTIwNjEpIHNjYWxlKDQ2LjMxMDE5IDIwOC4wMTU0MikiLz48ZWxsaXBzZSBjeD0iMTc5IiBjeT0iNTIiIGZpbGw9IiNmZjFjMjgiIHJ4PSIzMiIgcnk9IjM0Ii8+PGVsbGlwc2UgY3g9IjEyNCIgY3k9IjE2NiIgZmlsbD0iI2ZmZjRjOCIgcng9IjI0MyIgcnk9IjE5Ii8+PC9nPjwvc3ZnPg==');
        ">
          <a href="//images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png">
            <picture>
              <source type="image/avif"
                srcset="//images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=avif&fit=scale&q=75&w=300&h=201 300w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=avif&fit=scale&q=75&w=500&h=336 500w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=avif&fit=scale&q=75&w=700&h=471 700w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=avif&fit=scale&q=75&w=900&h=605 900w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=avif&fit=scale&q=75&w=1100&h=740 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <source type="image/webp"
                srcset="//images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=webp&fit=scale&q=75&w=300&h=201 300w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=webp&fit=scale&q=75&w=500&h=336 500w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=webp&fit=scale&q=75&w=700&h=471 700w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=webp&fit=scale&q=75&w=900&h=605 900w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=webp&fit=scale&q=75&w=1100&h=740 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <img width="1000" height="673"
                srcset="//images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=jpg&fit=scale&q=75&w=300&h=201 300w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=jpg&fit=scale&q=75&w=500&h=336 500w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=jpg&fit=scale&q=75&w=700&h=471 700w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=jpg&fit=scale&q=75&w=900&h=605 900w, //images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png?fm=jpg&fit=scale&q=75&w=1100&h=740 1100w"
                sizes="(max-width: 50em) 98vw, 700px"
                src="//images.ctfassets.net/f20lfrunubsq/50wrgKA7rqz4PYKiZM8CEr/318e49eb9bf1669a3262ef0bd533469a/Screenshot_2024-04-20_at_17.17.16.png"
                alt="Two 3d boxes that reveal an image."
                loading="lazy"
                onload="this.classList.add('kf-fade-in')">
            </picture>
          </a>
        </figure>
      </div>
    </p>
<p>The <code>img</code> elements are displayed as 3D boxes and only reveal their image content when hovering. There are no <code>div</code> wrappers or anything — what you see above are two <code>img</code> elements.</p>
<p>Temani walks you through how to create this effect step by step; go check it out. It's a great read.</p>
<p>But one thing stood out in the article: you might think that the images hide and show with some wild CSS tricks, but the effect uses good ol' <code>padding-right</code>.</p>
<p>Here's a minimal example.</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>By applying full-width padding to the image, the actual image is &quot;pushed out&quot; of the container. The applied <code>object-fit: cover</code> then prevents the image from being squished. And as the cherry on top: if you change <code>object-position</code> from <code>left</code> to <code>right</code>, the revealing effect becomes a &quot;slide in&quot;. Go and try it above!</p>
<p>I don't know when and if I'll use this effect, but it's more than worth a snippet entry on this blog. Thank you, Temani!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Reveal an image with smart padding">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Timestamped Git yolo commits 
      </title>
      <link href="https://www.stefanjudis.com/snippets/timestamped-git-yolo-commits/"/>
      <published>2024-03-29T23:00:00+00:00</published>
      <updated>2024-03-29T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/timestamped-git-yolo-commits/
      </id>
      <category term="snippet"></category>
        <category term="git"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Kyle Shevlin blogged about his belief in detailed atomic commits at work. On the contrary, he changes entire projects with a single commit in side projects. I feel you, Kyle.</p>
<p>To improve the side hustle commit chaos, <a href="https://kyleshevlin.com/make-checkpoint/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fkyleshevlin.com%2Fmake-checkpoint%2F')">Kyle added a <code>make</code> command called <code>checkpoint</code></a> to at least occasionally add progress to version control.</p>
<p>I have the same YOLO commit traits and, of course, I love this approach!</p>
<p>But I don't use <code>make</code>, so I created a more general solution as a custom Git command.</p>
<div class="highlightBox bulp margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-bulp"/>
            </svg>
          </div>
        

      
      <p>Quick tip: <code>git-</code> prefixed shell commands available in your <code>$PATH</code> will become git commands automatically.</p>
</div><p>A <code>git-yolo</code> shell file now defines my new <code>git yolo</code> command that's based on Kyle's original script.</p>
<pre class="language-sh"><code class="language-sh"><span class="token shebang important">#!/usr/bin/env sh</span>

<span class="token builtin class-name">echo</span> <span class="token string">"🚀 You only live once, right?"</span>
<span class="token builtin class-name">echo</span> <span class="token string">""</span>
<span class="token function">git</span> <span class="token function">add</span> <span class="token parameter variable">-A</span>
<span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">"yoloing at <span class="token variable"><span class="token variable">$(</span><span class="token function">date</span> <span class="token string">'+%Y-%m-%dT%H:%M'</span><span class="token variable">)</span></span>"</span>
<span class="token function">git</span> push
<span class="token builtin class-name">echo</span> <span class="token string">""</span>
<span class="token builtin class-name">echo</span> <span class="token string">"⬆️  Yolo Checkpoint created and pushed to remote."</span>
</code></pre>
<p>As a result, whenever I don't feel like (or don't care about) maintaining a clean Git history — <code>git yolo</code> will do the job.</p>
<pre><code>$ git yolo
🚀 You only live once, right?

[main 23c80e0] yoloing at 2024-03-30T08:22
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 fo
...
...

⬆️  Yolo Checkpoint created and pushed to remote
</code></pre>
<p>The Git history will then include some &quot;yolo checkpoints&quot; — at least.</p>
<pre><code>$ git log 

commit 23c80e... (HEAD -&gt; main, origin/main, origin/HEAD)
Author: stefan judis &lt;stefanjudis@gmail.com&gt;
Date:   Sat Mar 30 08:22:07 2024 +0100

    yoloing at 2024-03-30T08:22

commit 53yd07... (HEAD -&gt; main, origin/main, origin/HEAD)
Author: stefan judis &lt;stefanjudis@gmail.com&gt;
Date:   Sat Mar 30 08:10:23 2024 +0100

    yoloing at 2024-03-30T08:10
</code></pre>
<p>Nice nice!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Timestamped Git yolo commits ">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to type fixed string value combinations with template literal types
      </title>
      <link href="https://www.stefanjudis.com/snippets/template-literal-types/"/>
      <published>2024-01-27T23:00:00+00:00</published>
      <updated>2024-01-27T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/template-literal-types/
      </id>
      <category term="snippet"></category>
        <category term="TypeScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Sometimes, I'm surprised about how smart TypeScript is. Let's assume you want to type your app's release version numbers. The format is <code>[releaseMonth]-[releaseYear]</code>. There won't be a new version every month or year.</p>
<p>How hard is it to define a type for this? It's not hard at all.</p>
<pre class="shiki dark-plus twoslash lsp" style="background-color: #1E1E1E; color: #D4D4D4"><div class="language-id">js</div><div class='code-container'><code><div class='line'><span style="color: #6A9955">// define release years</span></div><div class='line'><span style="color: #569CD6">type</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0"><data-lsp lsp='type releaseYear = "2022" | "2023" | "2024"' >releaseYear</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&apos;2022&apos;</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">&apos;2023&apos;</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">&apos;2024&apos;</span><span style="color: #D4D4D4">;</span></div><div class='line'><span style="color: #6A9955">// define release months</span></div><div class='line'><span style="color: #569CD6">type</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0"><data-lsp lsp='type releasteMonth = "03" | "07" | "11"' >releasteMonth</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&apos;03&apos;</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">&apos;07&apos;</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">&apos;11&apos;</span><span style="color: #D4D4D4">;</span></div><div class='line'><span style="color: #6A9955">// combine the two types in a template literal type</span></div><div class='line'><span style="color: #569CD6">type</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0"><data-lsp lsp='type versionNumber = "03.2022" | "03.2023" | "03.2024" | "07.2022" | "07.2023" | "07.2024" | "11.2022" | "11.2023" | "11.2024"' >versionNumber</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">`</span><span style="color: #569CD6">${</span><span style="color: #4EC9B0"><data-lsp lsp='type releasteMonth = "03" | "07" | "11"' >releasteMonth</data-lsp></span><span style="color: #569CD6">}</span><span style="color: #CE9178">.</span><span style="color: #569CD6">${</span><span style="color: #4EC9B0"><data-lsp lsp='type releaseYear = "2022" | "2023" | "2024"' >releaseYear</data-lsp></span><span style="color: #569CD6">}</span><span style="color: #CE9178">`</span><span style="color: #D4D4D4">;</span></div><div class='line'>&nbsp;</div><div class='line'><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF"><data-err><data-lsp lsp='const version: "03.2022" | "03.2023" | "03.2024" | "07.2022" | "07.2023" | "07.2024" | "11.2022" | "11.2023" | "11.2024"' >version</data-lsp></data-err></span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0"><data-lsp lsp='type versionNumber = "03.2022" | "03.2023" | "03.2024" | "07.2022" | "07.2023" | "07.2024" | "11.2022" | "11.2023" | "11.2024"' >versionNumber</data-lsp></span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&apos;04.2022&apos;</span><span style="color: #D4D4D4">;</span></div><span class="error"><span>Type '"04.2022"' is not assignable to type '"03.2022" | "03.2023" | "03.2024" | "07.2022" | "07.2023" | "07.2024" | "11.2022" | "11.2023" | "11.2024"'. Did you mean '"03.2022"'?</span><span class="code">2820</span></span><span class="error-behind">Type '"04.2022"' is not assignable to type '"03.2022" | "03.2023" | "03.2024" | "07.2022" | "07.2023" | "07.2024" | "11.2022" | "11.2023" | "11.2024"'. Did you mean '"03.2022"'?</span></code></div></pre>
<p>For these situations, TypeScript defines <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwww.typescriptlang.org%2Fdocs%2Fhandbook%2F2%2Ftemplate-literal-types.html')">Template Literal Types</a> based on good old template strings. Sweet!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to type fixed string value combinations with template literal types">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to turn off password managers for fields
      </title>
      <link href="https://www.stefanjudis.com/snippets/turn-off-password-managers/"/>
      <published>2024-01-23T23:00:00+00:00</published>
      <updated>2024-01-23T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/turn-off-password-managers/
      </id>
      <category term="snippet"></category>
        <category term="HTML"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Disclaimer: be aware that turning off password manager functionality is generally a bad idea. <strong>The general rule is that it should always be the user's choice when to autofill an input field.</strong></p>
<p><a href="https://www.w3.org/TR/WCAG22/#accessible-authentication-minimum" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwww.w3.org%2FTR%2FWCAG22%2F%23accessible-authentication-minimum')">The WCAG Accessible Authentication criterion</a> defines that filling forms with assistance like password managers must be possible. If you go against this rule, there must be a good reason. Ideally, this reason is evaluated with user testing.</p>
<p>A situation I can think of is a field that must have <code>name=&quot;name&quot;</code> for technical reasons but doesn't match the account user name. Constantly popping up completion dialogs can harm UX in this situation. There are probably more situations. But the rule stands: always evaluate if it's worth breaking password autofill functionality for all users.</p>
<p>Thanks to <a href="https://fedbysandrine.com/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Ffedbysandrine.com%2F')">Sandrine</a> and <a href="https://www.scottohara.me/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwww.scottohara.me%2F')">Scott</a> for providing feedback.</p>
<p>Let's get to it!</p>
<hr aria-hidden="true"><p><a href="https://flaviocopes.com/how-to-disable-1password-in-an-input-field/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fflaviocopes.com%2Fhow-to-disable-1password-in-an-input-field%2F')">Flavio shared how to tell 1Password</a> not to interact with an input element on <code>localhost</code>.</p>
<p>
      <div class="sqip-container margin-top-l margin-bottom-l">
        <figure class="sqip-image" style="
          --color1: rgb(236,151,84); --color2: rgb(158,98,79); --color3: rgb(227,215,151); --color4: rgb(222,227,236); --color5: rgb(36,36,52);
          --sqip-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMjQxIj48cGF0aCBmaWxsPSIjMjQyNDM0IiBkPSJNMCAwaDMwMHYyNDFIMHoiLz48ZyBmaWxsLW9wYWNpdHk9Ii41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguNiAuNikgc2NhbGUoMS4xNzE4OCkiPjxjaXJjbGUgY3g9IjgyIiBjeT0iMTI4IiByPSIyMjAiIGZpbGw9IiM5NDc0NzYiLz48Y2lyY2xlIHI9IjEiIGZpbGw9IiNmZmYiIHRyYW5zZm9ybT0ibWF0cml4KC03Ny4xNjM4NiAuMTM0NjggLS4wMTcyNyAtOS44OTYzMyAxMzQuMSAxMzkuOSkiLz48ZWxsaXBzZSBjeD0iMTI5IiBjeT0iNjgiIGZpbGw9IiMwZTE1MjIiIHJ4PSIxMTkiIHJ5PSI2MCIvPjxlbGxpcHNlIGN4PSIxMDYiIGN5PSIxIiBmaWxsPSIjZmZhY2JmIiByeD0iMjU1IiByeT0iOSIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMDIgMTYwaDM0djE4aC0zNHoiLz48Y2lyY2xlIHI9IjEiIGZpbGw9IiNmZmQzN2IiIHRyYW5zZm9ybT0icm90YXRlKDE3OC45IC0uMiAyMikgc2NhbGUoOS44NjYxNiAyMjAuOTc3NjkpIi8+PGVsbGlwc2UgY3g9IjI1NSIgY3k9Ijk4IiBmaWxsPSIjZmY2NjhlIiByeD0iMTAiIHJ5PSIyMDUiLz48ZWxsaXBzZSBjeD0iMTMzIiBjeT0iMTM5IiBmaWxsPSIjZmZmIiByeD0iNjciIHJ5PSI4Ii8+PC9nPjwvc3ZnPg==');
        ">
          <a href="//images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png">
            <picture>
              <source type="image/avif"
                srcset="//images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=avif&fit=scale&q=75&w=300&h=241 300w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=avif&fit=scale&q=75&w=500&h=403 500w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=avif&fit=scale&q=75&w=700&h=564 700w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=avif&fit=scale&q=75&w=900&h=725 900w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=avif&fit=scale&q=75&w=1100&h=887 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <source type="image/webp"
                srcset="//images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=webp&fit=scale&q=75&w=300&h=241 300w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=webp&fit=scale&q=75&w=500&h=403 500w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=webp&fit=scale&q=75&w=700&h=564 700w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=webp&fit=scale&q=75&w=900&h=725 900w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=webp&fit=scale&q=75&w=1100&h=887 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <img width="1000" height="806"
                srcset="//images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=jpg&fit=scale&q=75&w=300&h=241 300w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=jpg&fit=scale&q=75&w=500&h=403 500w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=jpg&fit=scale&q=75&w=700&h=564 700w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=jpg&fit=scale&q=75&w=900&h=725 900w, //images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png?fm=jpg&fit=scale&q=75&w=1100&h=887 1100w"
                sizes="(max-width: 50em) 98vw, 700px"
                src="//images.ctfassets.net/f20lfrunubsq/55vZMA0ojxEA1K2llZA297/c6a46cf8285143fea4b1172860f4db57/Screenshot_2024-01-24_at_22.08.46.png"
                alt="Graphic showing how to disable 1Password autofilling via the `data-1p-ignore` attribute"
                loading="lazy"
                onload="this.classList.add('kf-fade-in')">
            </picture>
          </a>
        </figure>
      </div>
    </p>
<p>Ignoring the question of when you would want to turn off automatic password filling, <strong>how hard can it be to tell browsers and password managers not to mess with an input field</strong>?</p>
<p>As Flavio shared, 1Password relies on <code>data-1p-ignore</code>. Fair enough. This attribute is oddly specific, though.</p>
<p>How can you turn off auto-completion for LastPass users, then? Add <code>data-lpignore=&quot;true&quot;</code> to your input field and change the &quot;Advanced Settings&quot;. Easy. For Bitwarden it's <code>data-bwignore</code>.</p>
<p>But I'm a happy Dashlane user; how about this one? Dashlane created an entire spec called <a href="https://dashlane.github.io/SAWF/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdashlane.github.io%2FSAWF%2F')">Semantically Annotated Web Forms</a> to handle form completion. An easy <code>data-form-type=&quot;other&quot;</code> will do it for this password manager.</p>
<p>Here's a complete attribute list.</p>
<table>
<thead>
<tr>
<th>PW manager</th>
<th>Turn off attribute</th>
</tr>
</thead>
<tbody>
<tr>
<td>1Password</td>
<td><code>data-1p-ignore</code> (<a href="https://stackoverflow.com/a/75486902/4253183" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fstackoverflow.com%2Fa%2F75486902%2F4253183')">Source</a>)</td>
</tr>
<tr>
<td>Lastpass</td>
<td><code>data-lpignore=&quot;true&quot;</code> (<a href="https://support.lastpass.com/s/document-item?language=en_US&amp;bundleId=lastpass&amp;topicId=LastPass/c_lp_prevent_fields_from_being_filled_automatically.html&amp;_LANG=enus" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fsupport.lastpass.com%2Fs%2Fdocument-item%3Flanguage%3Den_US%26bundleId%3Dlastpass%26topicId%3DLastPass%2Fc_lp_prevent_fields_from_being_filled_automatically.html%26_LANG%3Denus')">Source</a>)</td>
</tr>
<tr>
<td>Dashlane</td>
<td><code>data-form-type=&quot;other&quot;</code> (<a href="https://stackoverflow.com/a/68263873/4253183" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fstackoverflow.com%2Fa%2F68263873%2F4253183')">Source</a>)</td>
</tr>
<tr>
<td>Bitwarden</td>
<td><code>data-bwignore</code> (<a href="https://github.com/bitwarden/clients/blob/e1415af407fef139fb69126349e5cc6d286f4474/apps/browser/src/autofill/services/collect-autofill-content.service.ts#L884-L889" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fbitwarden%2Fclients%2Fblob%2Fe1415af407fef139fb69126349e5cc6d286f4474%2Fapps%2Fbrowser%2Fsrc%2Fautofill%2Fservices%2Fcollect-autofill-content.service.ts%23L884-L889')">Source</a>)</td>
</tr>
</tbody>
</table>
<p>And all these data attributes bring us to this beautiful markup to disable password managers for a form element.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span>
  <span class="token attr-name">autocomplete</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>off<span class="token punctuation">"</span></span> <span class="token attr-name">data-1p-ignore</span> <span class="token attr-name">data-bwignore</span>
  <span class="token attr-name">data-lpignore</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">data-form-type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>other<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
</code></pre>
<p>Isn't it beautiful? And that's only the four password managers I know. I bet there are many others out there...</p>
<p>But to take a step back, isn't the <code>autocomplete=&quot;off&quot;</code>'s job to turn off field completion?</p>
<p>As far as I understand, that was the idea behind the attribute, but <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#sect2" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FHTML%2FAttributes%2Fautocomplete%23sect2')">here's a quote from MDN</a>:</p>
<div class="highlightBox mdn margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-mdn"/>
            </svg>
          </div>
        

      
      <blockquote>
<p>In most modern browsers, setting autocomplete to &quot;off&quot; will not prevent a password manager from asking the user if they would like to save username and password information, or from automatically filling in those values in a site's login form.</p>
</blockquote>
</div><p>Ignoring <code>autocomplete=&quot;off&quot;</code> is a conscious security, accessibility and UX decision. People often know best when they want to autofill a field.</p>
<p>Anyways... do you know any other tricks to turn off password managers? If so, <a href="mailto:stefanjudis@gmail.com">I'd love to hear them</a> because collecting the required attributes for 1Password, Dashlane, Bitwarden and LastPass took me reading way more Stack Overflow threads than it should have.</p>
<hr aria-hidden="true"><p>👏 Shoutout to Kasper Mikiewicz, <a href="https://twitter.com/Idered/status/1750826222542532906" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Ftwitter.com%2FIdered%2Fstatus%2F1750826222542532906')">who provided the Bitwarden attribute</a>.</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to turn off password managers for fields">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Fading out text with CSS
      </title>
      <link href="https://www.stefanjudis.com/snippets/fading-out-text-with-css/"/>
      <published>2024-01-23T23:00:00+00:00</published>
      <updated>2024-01-23T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/fading-out-text-with-css/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://polypane.app/blog/my-take-on-fading-content-using-transparent-gradients-in-css/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fpolypane.app%2Fblog%2Fmy-take-on-fading-content-using-transparent-gradients-in-css%2F')">Here's Kilian over on the Polypane blog with a nice explanation</a> of how to create text that fades out with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FCSS%2Fmask-image')">CSS <code>mask-image</code></a>.</p>
<p>Using <code>mask-image</code> is smart.</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>Nice one! Bookmarked for later.😉</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Fading out text with CSS">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        CSS only &quot;scroll-to-top&quot;
      </title>
      <link href="https://www.stefanjudis.com/snippets/css-only-scroll-to-top/"/>
      <published>2023-12-07T23:00:00+00:00</published>
      <updated>2023-12-07T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/css-only-scroll-to-top/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://darn.es/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdarn.es%2F')">David Darnes</a> shared <a href="https://codepen.io/daviddarnes/pen/JjxmLpb" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fcodepen.io%2Fdaviddarnes%2Fpen%2FJjxmLpb')">a nifty tiny trick to build a &quot;scroll to top&quot; component</a> that automatically shows up after you scroll down a little.</p>
<p>And thanks to modern CSS, <code>position: sticky</code> is all you need. 💙</p>
<p>Here's a scroll-top link in action. 👇</p>
<p>[Interactive component: visit the article to see it...]</p>
<div class="highlightBox info margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-info"/>
            </svg>
          </div>
        

      
      <p>But why use <code>100vh</code> instead of <code>100%</code>?</p>
<p>Fun fact: percentages in <code>margin-top</code> refer to <strong>the logical width</strong> of the containing block. <code>margin-top: 50%</code> isn't half the container's height, but width.</p>
</div><p>Either way, this is a nifty little CSS trick. Thanks, David!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20CSS only &quot;scroll-to-top&quot;">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to remove all event listeners from a DOM element
      </title>
      <link href="https://www.stefanjudis.com/snippets/how-to-remove-all-event-listeners-from-a-dom-element/"/>
      <published>2023-08-17T22:00:00+00:00</published>
      <updated>2023-08-17T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/how-to-remove-all-event-listeners-from-a-dom-element/
      </id>
      <category term="snippet"></category>
        <category term="JavaScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Removing event listeners from DOM elements is pretty annoying because you must have the registered event handler function at hand.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">handleEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span>

<span class="token comment">// add an event listener</span>
document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'button'</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// to remove an event listener, the event type ('click') </span>
<span class="token comment">// and the function ('handleEvent') have to match </span>
<span class="token comment">// the `addEventListener` call</span>
document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'button'</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>With somewhat modern JavaScript, you could also <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FEventTarget%2FaddEventListener%23signal')">use <code>AbortSignals</code></a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#once" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FEventTarget%2FaddEventListener%23once')">the <code>once</code> config</a> option to remove event listeners, but what if it isn't your source code that's adding event listeners? What if there's one annoying 3rd party script messing with your events and you have to keep it?</p>
<p>Today I learned about a nuclear option to remove all existing event listeners.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> button <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span>'button<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// replace the element with a copy of itself</span>
<span class="token comment">// and nuke all the event listeners</span>
button<span class="token punctuation">.</span><span class="token function">replaceWith</span><span class="token punctuation">(</span>button<span class="token punctuation">.</span><span class="token function">cloneNode</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>Boom! And that's it — replacing a DOM element with a cloned version of itself removes all previously registered event handlers. Pretty handy!</p>
<div class="highlightBox info margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-info"/>
            </svg>
          </div>
        

      
      <p>Event handlers defined in an <code>on*</code> HTML attribute will be preserved when you clone a DOM element.</p>
<pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- This event handler will be copied when you cloned the node --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Still here!'</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token attr-name">`</span><span class="token punctuation">></span></span>click me<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>
</code></pre>
</div><p>If you want to see this pattern in action, here we go. 👇</p>
<p>[Interactive component: visit the article to see it...]</p></div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to remove all event listeners from a DOM element">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Copy an array and replace one element at a specific index with modern JavaScript
      </title>
      <link href="https://www.stefanjudis.com/snippets/copy-array-and-replace-one-element-at-index-javascript/"/>
      <published>2023-08-09T22:00:00+00:00</published>
      <updated>2023-08-09T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/copy-array-and-replace-one-element-at-index-javascript/
      </id>
      <category term="snippet"></category>
        <category term="JavaScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>How do you replace an array element at a given index?</p>
<p>Is this a trick question? Not really.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
numbers<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'new value'</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// [1, 'new value', 3, 4, 5]</span>
</code></pre>
<p>But when you're using React or any framework betting on immutability, changing an array entry will lead to subtle bugs because the array will still be the same object with the same reference. In an immutable world, <strong>data updates always have to result in a new object reference</strong>.</p>
<p>How can you change an array element via its index and spit out a new array in the same go then? <a href="https://stackoverflow.com/questions/5915789/how-to-replace-item-in-array" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fstackoverflow.com%2Fquestions%2F5915789%2Fhow-to-replace-item-in-array')">A 12-years-old Stack Overflow question with 1.5m views</a> has tons of advice, but because it's so old and has hundreds of upvotes, it's not showing the most modern solution in the top area.</p>
<p>Let's have a quick look at creative answers.</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="the-old-way-to-copy-an-array-and-change-one-item" href="#the-old-way-to-copy-an-array-and-change-one-item">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="the-old-way-to-copy-an-array-and-change-one-item">The old way to copy an array and change one item</h2></div><p>You certainly could iterate over the array and build up a new one with a good old <code>forEach</code> loop.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">changeItem</span><span class="token punctuation">(</span><span class="token parameter">array<span class="token punctuation">,</span> index<span class="token punctuation">,</span> newValue</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> newArray <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  array<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">num<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">===</span> index <span class="token punctuation">)</span> <span class="token keyword">return</span> newArray<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>newValue<span class="token punctuation">)</span><span class="token punctuation">;</span> 

    newArray<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> newArray<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> updatedNumbers <span class="token operator">=</span> <span class="token function">changeItem</span><span class="token punctuation">(</span>numbers<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'new value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment">// [1, 2, 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span>             <span class="token comment">// [1, 'new value', 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers <span class="token operator">===</span> updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// false</span>
</code></pre>
<p>But this isn't great. Changing an array entry with <code>map</code> is slightly better...</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">changeItem</span><span class="token punctuation">(</span><span class="token parameter">array<span class="token punctuation">,</span> index<span class="token punctuation">,</span> newValue</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> array<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">===</span> index<span class="token punctuation">)</span> <span class="token keyword">return</span> newValue<span class="token punctuation">;</span>

    <span class="token keyword">return</span> value<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> updatedNumbers <span class="token operator">=</span> <span class="token function">changeItem</span><span class="token punctuation">(</span>numbers<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'new value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment">// [1, 2, 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span>             <span class="token comment">// [1, 'new value', 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers <span class="token operator">===</span> updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// false</span>
</code></pre>
<p>And for the creatives, if you belong to this small group who remember the difference between <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FArray%2Fslice')">slice</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FArray%2Fsplice')">splice</a>, you could also use one of these.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">changeItem</span><span class="token punctuation">(</span><span class="token parameter">array<span class="token punctuation">,</span> index<span class="token punctuation">,</span> newValue</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">[</span>
    <span class="token operator">...</span>array<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> index<span class="token punctuation">)</span><span class="token punctuation">,</span>
    newValue<span class="token punctuation">,</span>
    <span class="token operator">...</span>array<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> updatedNumbers <span class="token operator">=</span> <span class="token function">changeItem</span><span class="token punctuation">(</span>numbers<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'new value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment">// [1, 2, 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span>             <span class="token comment">// [1, 'new value', 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers <span class="token operator">===</span> updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// false</span>
</code></pre>
<p>Okay, this one (☝️) is actually terrible.</p>
<p>But what if I told you that copying an array and changing a single entry is cross-browser supported in JavaScript today?</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="the-new-way-%E2%80%94-copy-an-array-and-change-an-entry-with-%60with()%60" href="#the-new-way-%E2%80%94-copy-an-array-and-change-an-entry-with-%60with()%60">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="the-new-way-%E2%80%94-copy-an-array-and-change-an-entry-with-%60with()%60">The new way — copy an array and change an entry with <code>with()</code></h2></div><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/with" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FArray%2Fwith')">All modern browsers support <code>with()</code></a> these days.</p>
<div class="highlightBox mdn margin-top-xl margin-bottom-xl">
        <div class="cornerBubble">
          <svg aria-hidden="true"><use xlink:href="/sprite.svg#icon-mdn"></use></svg>
        </div>
        <div class="highlightBox__header">MDN Compat Data (<a href="https://raw.githubusercontent.com/mdn/browser-compat-data/main/javascript/builtins/Array.json">source</a>)</div>
        <div class="highlightBox__body">
          <div class="highlightBox__overflow">
            <table class="highlightBox__compat">
              <caption>Browser support info for <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/with">Array.with()</a> </caption>
              <thead>
                <tr>
                  <td>
                        <img src="/assets/browsers/chrome.webp"
                              srcset="/assets/browsers/chrome.webp, /assets/browsers/chrome@2.webp 2x"
                              width="48" height="51" alt="chrome">
                      </td><td>
                        <img src="/assets/browsers/chrome_android.webp"
                              srcset="/assets/browsers/chrome_android.webp, /assets/browsers/chrome_android@2.webp 2x"
                              width="48" height="51" alt="chrome_android">
                      </td><td>
                        <img src="/assets/browsers/edge.webp"
                              srcset="/assets/browsers/edge.webp, /assets/browsers/edge@2.webp 2x"
                              width="48" height="51" alt="edge">
                      </td><td>
                        <img src="/assets/browsers/firefox.webp"
                              srcset="/assets/browsers/firefox.webp, /assets/browsers/firefox@2.webp 2x"
                              width="48" height="51" alt="firefox">
                      </td><td>
                        <img src="/assets/browsers/firefox_android.webp"
                              srcset="/assets/browsers/firefox_android.webp, /assets/browsers/firefox_android@2.webp 2x"
                              width="48" height="51" alt="firefox_android">
                      </td><td>
                        <img src="/assets/browsers/safari.webp"
                              srcset="/assets/browsers/safari.webp, /assets/browsers/safari@2.webp 2x"
                              width="48" height="51" alt="safari">
                      </td><td>
                        <img src="/assets/browsers/safari_ios.webp"
                              srcset="/assets/browsers/safari_ios.webp, /assets/browsers/safari_ios@2.webp 2x"
                              width="48" height="51" alt="safari_ios">
                      </td><td>
                        <img src="/assets/browsers/samsunginternet_android.webp"
                              srcset="/assets/browsers/samsunginternet_android.webp, /assets/browsers/samsunginternet_android@2.webp 2x"
                              width="48" height="51" alt="samsunginternet_android">
                      </td><td>
                        <img src="/assets/browsers/webview_android.webp"
                              srcset="/assets/browsers/webview_android.webp, /assets/browsers/webview_android@2.webp 2x"
                              width="48" height="51" alt="webview_android">
                      </td>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>
                        <span class="highlightBox__pill success margin-top-s">
                        110
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        110
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        110
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        115
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        115
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        16
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        16
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        21.0
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        110
                        </span>
                      </td>
                </tr>
              </tbody>
            </table>
          </div>
<p></div>
</div></p>
<p>And with it, this entire exercise becomes a nifty one-liner.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> updatedNumbers <span class="token operator">=</span> numbers<span class="token punctuation">.</span><span class="token function">with</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'new value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment">// [1, 2, 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span>             <span class="token comment">// [1, 'new value', 3, 4, 5]</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>numbers <span class="token operator">===</span> updatedNumbers<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// false</span>
</code></pre>
<p>Thank you modern JavaScript!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Copy an array and replace one element at a specific index with modern JavaScript">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        Make a PDF look like it was manually scanned
      </title>
      <link href="https://www.stefanjudis.com/snippets/make-a-pdf-look-like-it-was-manually-scanned/"/>
      <published>2023-03-16T23:00:00+00:00</published>
      <updated>2023-03-16T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/make-a-pdf-look-like-it-was-manually-scanned/
      </id>
      <category term="snippet"></category>
        <category term="Bash"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>I live in Germany, and while many think we have things in order here, we still need to sort out bureaucracy. It's a clu**fu** at best because German officials rely on paper – paper and handwritten signatures.</p>
<p>It can go so far that a scanned document image is preferred over a nice and sharp PDF. I don't own a printer, let alone a scanner, so occasionally, I drag myself to the local copy shop to do official business.</p>
<p>Until now! <a href="https://news.ycombinator.com/item?id=35132018" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fnews.ycombinator.com%2Fitem%3Fid%3D35132018')">I've been browsing Hacker News</a>, and the shell script below takes a PDF and makes it look like it's been scanned.</p>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/sh</span>
<span class="token assign-left variable">ROTATION</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">shuf</span> <span class="token parameter variable">-n</span> <span class="token number">1</span> <span class="token parameter variable">-e</span> <span class="token string">'-'</span> <span class="token string">''</span><span class="token variable">)</span></span><span class="token variable"><span class="token variable">$(</span><span class="token function">shuf</span> <span class="token parameter variable">-n</span> <span class="token number">1</span> <span class="token parameter variable">-e</span> <span class="token punctuation">$(</span>seq <span class="token number">0.05</span> .5<span class="token punctuation">)</span><span class="token variable">)</span></span>

convert <span class="token parameter variable">-density</span> <span class="token number">150</span> <span class="token variable">$1</span> <span class="token punctuation">\</span>
  -linear-stretch <span class="token string">'1.5%x2%'</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-rotate</span> <span class="token variable">${ROTATION}</span> <span class="token punctuation">\</span>
  <span class="token parameter variable">-attenuate</span> <span class="token string">'0.01'</span> <span class="token punctuation">\</span>
  +noise  Multiplicative <span class="token punctuation">\</span>
  <span class="token parameter variable">-colorspace</span> <span class="token string">'gray'</span> <span class="token variable">$2</span>
  
</code></pre>
<p>Suppose you called the script <code>index<wbr>.sh</code>, you can now run the following command...</p>
<pre class="language-bash"><code class="language-bash">./index.sh original.pdf scanned.pdf
</code></pre>
<p>... to scan your PDFs without a physical scanner.</p>
<p>
      <div class="sqip-container margin-top-l margin-bottom-l">
        <figure class="sqip-image" style="
          --color1: rgb(60,123,179); --color2: rgb(110,137,160); --color3: rgb(156,195,227); --color4: rgb(196,180,156); --color5: rgb(37,41,45);
          --sqip-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMTY4Ij48cGF0aCBmaWxsPSIjMjUyOTJkIiBkPSJNMCAwaDMwMHYxNjhIMHoiLz48ZyBmaWxsLW9wYWNpdHk9Ii41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguNiAuNikgc2NhbGUoMS4xNzE4OCkiPjxjaXJjbGUgY3g9IjEzMCIgY3k9Ijc1IiByPSIxNTUiIGZpbGw9IiNmZmYiLz48Y2lyY2xlIGN4PSIxMTciIGN5PSI0MyIgcj0iMTc0IiBmaWxsPSIjZmZmIi8+PGVsbGlwc2UgY3g9IjkzIiBjeT0iNDAiIGZpbGw9IiNmZmYiIHJ4PSIxNzIiIHJ5PSIxMjAiLz48Y2lyY2xlIHI9IjEiIGZpbGw9IiNmZmYiIHRyYW5zZm9ybT0icm90YXRlKDE2OCA2Mi45IDI3LjYpIHNjYWxlKDI1NSA4OS42OTkwNSkiLz48cGF0aCBmaWxsPSIjNGI0YTQ5IiBkPSJNMjQxLjcgMTA0LjUgMjQ2IDEzMmwtMjEuOCAzLjRMMjIwIDEwOHoiLz48cGF0aCBmaWxsPSIjNTg3YTg5IiBkPSJNMjIyIDM4aDI0djI5aC0yNHoiLz48cGF0aCBmaWxsPSIjODc4Nzg2IiBkPSJNMTM0IDEyNGg5NXYxMWgtOTV6Ii8+PGVsbGlwc2UgY3g9IjE0MSIgY3k9IjE0MyIgZmlsbD0iI2ZmZiIgcng9IjI1NSIgcnk9IjkiLz48L2c+PC9zdmc+');
        ">
          <a href="//images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg">
            <picture>
              <source type="image/avif"
                srcset="//images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=avif&fit=scale&q=75&w=300&h=168 300w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=avif&fit=scale&q=75&w=500&h=281 500w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=avif&fit=scale&q=75&w=700&h=394 700w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=avif&fit=scale&q=75&w=900&h=506 900w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=avif&fit=scale&q=75&w=1100&h=619 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <source type="image/webp"
                srcset="//images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=webp&fit=scale&q=75&w=300&h=168 300w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=webp&fit=scale&q=75&w=500&h=281 500w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=webp&fit=scale&q=75&w=700&h=394 700w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=webp&fit=scale&q=75&w=900&h=506 900w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=webp&fit=scale&q=75&w=1100&h=619 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <img width="1000" height="563"
                srcset="//images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=jpg&fit=scale&q=75&w=300&h=168 300w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=jpg&fit=scale&q=75&w=500&h=281 500w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=jpg&fit=scale&q=75&w=700&h=394 700w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=jpg&fit=scale&q=75&w=900&h=506 900w, //images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg?fm=jpg&fit=scale&q=75&w=1100&h=619 1100w"
                sizes="(max-width: 50em) 98vw, 700px"
                src="//images.ctfassets.net/f20lfrunubsq/3S00ZDdnRb5rR14oA9ZHYV/ef75a98d7600c127f77bffee65ee923c/pdfify.001.jpeg"
                alt="Examples showing how digital scanned documents could look like."
                loading="lazy"
                onload="this.classList.add('kf-fade-in')">
            </picture>
          </a>
        </figure>
      </div>
    </p>
<p>Side note: the <code>convert</code> command is part of the <code>imagemagick</code> tools and wasn't available on my machine. You might have to install it first. A <code>brew install imagemagick</code> did the trick for me, though.</p>
<div class="highlightBox info margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-info"/>
            </svg>
          </div>
        

      
      <p>As Thomas Steiner pointed out; <a href="https://lookscanned.io/scan" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Flookscanned.io%2Fscan')">you can reach similar visual effects with modern browser APIs right within your browser</a>.</p>
</div></div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20Make a PDF look like it was manually scanned">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        A userland React hook for managing global state
      </title>
      <link href="https://www.stefanjudis.com/snippets/a-userland-react-hook-for-managing-global-state/"/>
      <published>2023-02-17T23:00:00+00:00</published>
      <updated>2023-02-17T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/a-userland-react-hook-for-managing-global-state/
      </id>
      <category term="snippet"></category>
        <category term="React"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://yoavik.com/snippets/use-global-state" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fyoavik.com%2Fsnippets%2Fuse-global-state')">Yoav Kadosh shared a very <code>useGlobalState</code> React hook</a>.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> store <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> listeners <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">useGlobalState</span><span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> initialValue</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> _setState<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span>store<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">||</span> initialValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> setState <span class="token operator">=</span> <span class="token function">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">stateOrSetter</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> next <span class="token operator">=</span> stateOrSetter<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> stateOrSetter <span class="token operator">===</span> <span class="token string">"function"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      next <span class="token operator">=</span> <span class="token function">stateOrSetter</span><span class="token punctuation">(</span>store<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    listeners<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">l</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">l</span><span class="token punctuation">(</span>next<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    store<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> next<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// Store the initial state on the first call with this key</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>store<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      store<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> initialValue<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// Create an empty array of listener on the first call with this key</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>listeners<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      listeners<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// Register the observer</span>
    <span class="token keyword">const</span> <span class="token function-variable function">listener</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">state</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">_setState</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span>
    listeners<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>listener<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// Cleanup when unmounting</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> index <span class="token operator">=</span> listeners<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span>listener<span class="token punctuation">)</span><span class="token punctuation">;</span>
      listeners<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> setState<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>You can then use the defined <code>useGlobalState</code> hook to share or update application state across components. 💪</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> setState<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useGlobalState</span><span class="token punctuation">(</span><span class="token string">'someUniqueKey'</span><span class="token punctuation">,</span> <span class="token constant">INITIAL_STATE</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>Are there downsides to this pattern? I don't know, and I'm a little puzzled why I haven't seen this userland hook before, but if you think <code>useGlobalState</code> is a bad idea, <a href="mailto:stefanjudis@gmail.com">let me know</a>!</p>
<p>If you want to learn more, <a href="https://yoavik.com/snippets/use-global-state" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fyoavik.com%2Fsnippets%2Fuse-global-state')">Yoav explains how it works on his blog</a>!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20A userland React hook for managing global state">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to fail function calls with undefined arguments with a one-liner (sorta)
      </title>
      <link href="https://www.stefanjudis.com/snippets/how-to-fail-function-calls-with-undefined-arguments-with-a-one-liner-sorta/"/>
      <published>2023-01-29T23:00:00+00:00</published>
      <updated>2023-01-29T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/how-to-fail-function-calls-with-undefined-arguments-with-a-one-liner-sorta/
      </id>
      <category term="snippet"></category>
        <category term="JavaScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>I've been writing JavaScript for so long that I sometimes don't see its quirks. To be fair, there are fewer quirks than ten years ago, but some things would still make great language additions. One thing that would make a lot of sense is required function parameters.</p>
<p>Assume you have a function that defines required arguments. And you really really want to make sure the function is called with the correct arguments by throwing an error.</p>
<p>Here's what you might do:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">doSomethingWithAThing</span><span class="token punctuation">(</span><span class="token parameter">theThing</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> theThing <span class="token operator">===</span> <span class="token string">"undefined"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"theThing is required"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>That's quite a bit of code for such a simple thing.</p>
<p>Unfortunately, you can't do something like this in JavaScript ...</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">doSomethingWithAThing</span><span class="token punctuation">(</span>
  theThing <span class="token operator">=</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"theThing Is Undefined"</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span>
</code></pre>
<p>... because JavaScript doesn't like it.</p>
<p><a href="https://www.peterkroener.de/die-famose-fail-funktion/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fwww.peterkroener.de%2Fdie-famose-fail-funktion%2F')">Peter Kröner published a handy snippet that he carries around from project to project</a>. The post is in German, so here's the English gist of it.</p>
<p>First, define a <code>fail</code> function.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">fail</span><span class="token punctuation">(</span><span class="token parameter">reason<span class="token punctuation">,</span> ErrorConstructor <span class="token operator">=</span> Error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ErrorConstructor</span><span class="token punctuation">(</span>reason<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And second... use it. 🫣</p>
<p>By restructuring the code and transforming the error throwing portion into an expression, you can immediately throw in case a function is called without the required parameters.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">doSomethingWithAThing</span><span class="token punctuation">(</span>
  theThing <span class="token operator">=</span> <span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">'theThing Is Undefined'</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span>
</code></pre>
<p>What a beautiful sort of one-line workaround.</p>
<p>Thanks Peter!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to fail function calls with undefined arguments with a one-liner (sorta)">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to animate an element&#39;s height with CSS grid
      </title>
      <link href="https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/"/>
      <published>2022-12-21T23:00:00+00:00</published>
      <updated>2022-12-21T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://chriscoyier.net/2022/12/21/things-css-could-still-use-heading-into-2023/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fchriscoyier.net%2F2022%2F12%2F21%2Fthings-css-could-still-use-heading-into-2023%2F')">Did Chris just publish a post</a> and unveil the solution to a long-lasting CSS problem almost in a side sentence? It seems like it!</p>
<p>What of the many are we walking about? We're talking about animating (or transitioning) an element's height from 0 to something (<code>auto</code>) just with CSS. That's right – this is possible in all modern browsers today. No JavaScript required!</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="animated-grid-rows" href="#animated-grid-rows">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="animated-grid-rows">Animated grid rows</h2></div><p>Not too long ago, browsers shipped animatable CSS grid columns and rows which you could use to fly in a side menu or do fancy stuff like Michelle Barker. 👇</p>
<iframe height="300" style="width: 100%;" scrolling="no" title="grid-template-rows / grid-template-columns animation (Firefox only)" src="https://codepen.io/michellebarker/embed/oJmZKK?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
  See the Pen <a href="https://codepen.io/michellebarker/pen/oJmZKK">
  grid-template-rows / grid-template-columns animation (Firefox only)</a> by Michelle Barker (<a href="https://codepen.io/michellebarker">@michellebarker</a>)
  on <a href="https://codepen.io">CodePen</a>.
</iframe>
<p>I've never considered using animated grid rows to hide grid cells, though.</p>
<p>Here's the CSS trick to transition or animate an element's height to <code>auto</code>:</p>
<ol>
<li>define a grid container</li>
<li>define a single grid row with <code>0fr</code></li>
<li>add a transition for <code>grid-template-rows</code></li>
<li>set <code>overflow: hidden</code> to the one element inside the grid</li>
<li>redefine the <code>grid-template-rows</code> to be <code>1fr</code> with a CSS class or attribute selector</li>
</ol>
<pre class="language-css"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span>
  <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token comment">/* 1 */</span>
  <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> 0fr<span class="token punctuation">;</span> <span class="token comment">/* 2 */</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> grid-template-rows 0.5s ease-in-out<span class="token punctuation">;</span> <span class="token comment">/* 3 */</span>
<span class="token punctuation">}</span>

<span class="token selector">.grid.open</span> <span class="token punctuation">{</span>
  <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> 1fr<span class="token punctuation">;</span> <span class="token comment">/* 5 */</span>
<span class="token punctuation">}</span>

<span class="token selector">.grid-inner</span> <span class="token punctuation">{</span>
  <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span> <span class="token comment">/* 4 */</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And voila! You can now animate an element's height without any hardcoded values. It's just CSS — toggle a class and call it a day! CSS Grid figures out all the rest.</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>I. Am. Blown. Away!</p>
<div class="highlightBox attention margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-attention"/>
            </svg>
          </div>
        

      
      <p>Zero-height elements come with accessibility issues because their content can still be reached with assistive technology. The example above implements a mix of <code>aria-hidden</code>, <code>aria-controls</code> and <code>aria-expanded</code> to work around the issues.</p>
<p>Accessibility is tough, though, and I'm no expert. If you have any feedback on the implementation, <a href="mailto:stefanjudis@gmail.com">let me know</a>!</p>
</div><p>But to Chris' point, even though this technique works, it'd be nice to have an easier way to animate element heights in CSS. I 100% agree but for now, I'll take this approach because it's the best we got! Thanks Chris! 💯</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to animate an element&#39;s height with CSS grid">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        A native shell alternative to the trash-cli
      </title>
      <link href="https://www.stefanjudis.com/snippets/a-native-shell-alternative-to-the-trash-cli/"/>
      <published>2022-11-05T23:00:00+00:00</published>
      <updated>2022-11-05T23:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/a-native-shell-alternative-to-the-trash-cli/
      </id>
      <category term="snippet"></category>
        <category term="Bash"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://twitter.com/wesbos/status/1588520765380612098?s=20&amp;t=AfAAWfMMBKIw-ubT3PKAGw" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Ftwitter.com%2Fwesbos%2Fstatus%2F1588520765380612098%3Fs%3D20%26t%3DAfAAWfMMBKIw-ubT3PKAGw')">Wes Bos has advocated using the <code>trash-cli</code> instead of the <code>rm</code> command this week</a>.</p>
<p>The reasoning: <code>rm</code> is unrecoverable. Once you remove files or directories using <code>rm</code>, they're gone. Ergo, you can bring yourself into real trouble using <code>rm</code>.</p>
<div class="highlightBox question margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-question"/>
            </svg>
          </div>
        

      
      <p>I assume one could do some wild forensic stuff to restore bits and bytes on your hard drive, but the deleted files have to be very important to go this route.</p>
</div><p><a href="https://github.com/sindresorhus/trash-cli" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fsindresorhus%2Ftrash-cli')"><code>trash</code> can help out here</a>! The command doesn't delete files but moves them to your operating system's trash directory. When you then accidentally delete something important, head over to your lovely bin icon and recover files from there!</p>
<p>But here's the kicker: installing the global trash-cli package comes with 829 dependency files. Markdown, JavaScript, JSON and license files are all at your service, making up a whopping 4.3MB heavy <code>node_modules</code> directory.</p>
<p>Sure, 4 MB isn't an issue for me on my developer machine. And the command runs fast enough to protect me and my fat fingers from self-destruction. But still, that's a lot of dependency code to move files into another directory, isn't it?</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="a-native-%60trash%60-cli-function" href="#a-native-%60trash%60-cli-function">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="a-native-%60trash%60-cli-function">A native <code>trash</code> CLI function</h2></div><p><a href="https://twitter.com/liran_tal/status/1588529115350863872" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Ftwitter.com%2Fliran_tal%2Fstatus%2F1588529115350863872')">Liran Tal thought the same and shared a nifty four-liner giving you all of <code>trash</code>'s functionality</a>.</p>
<pre class="language-shell"><code class="language-shell"><span class="token function-name function">trash</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token builtin class-name">echo</span> <span class="token string">"[x] moving files to trash..."</span>
  <span class="token function">mv</span> <span class="token string">"<span class="token variable">$@</span>"</span> <span class="token string">"<span class="token environment constant">$HOME</span>/.trash"</span>
<span class="token punctuation">}</span>
</code></pre>
<p>I played around with Liran's function and adjusted it also to support multiple files.</p>
<pre class="language-shell"><code class="language-shell"><span class="token comment"># ✅ globs          – `trash file-*-.txt`</span>
<span class="token comment"># ✅ directories    – `trash directory`</span>
<span class="token comment"># ✅ multiple files - `trash file-1 dir-1 file-2`</span>
<span class="token keyword">function</span> <span class="token function-name function">trash</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token builtin class-name">echo</span> <span class="token string">"🗑️  Moving files to trash..."</span>

  <span class="token keyword">for</span> <span class="token for-or-select variable">var</span> <span class="token keyword">in</span> <span class="token string">"<span class="token variable">$@</span>"</span>
  <span class="token keyword">do</span>
    <span class="token function">mv</span> <span class="token string">"<span class="token variable">$var</span>"</span> <span class="token string">"<span class="token environment constant">$HOME</span>/.trash"</span>
  <span class="token keyword">done</span>
<span class="token punctuation">}</span>
</code></pre>
<p>So far, my new <code>trash</code> command has been working great. I'll let you know when I'll discover any issues!</p>
<div class="highlightBox attention margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-attention"/>
            </svg>
          </div>
        

      
      <p>Disclaimer: I only tested this shell function in my environment: macOS and ZSH.</p>
</div></div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20A native shell alternative to the trash-cli">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        A centered CSS grid with full-width components
      </title>
      <link href="https://www.stefanjudis.com/snippets/a-centered-css-grid-with-full-width-components/"/>
      <published>2022-10-21T21:00:00+00:00</published>
      <updated>2022-10-21T21:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/a-centered-css-grid-with-full-width-components/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Not too long ago, I was looking into refactoring my site to go all in with CSS grid!</p>
<p>I've been aiming for a classical centered blog layout with flexible side margins. The content should be 65 characters wide at max. But (!) I wanted to be able to break free, and some components should go full-width by setting a class. And it should all work with CSS grid.</p>
<p>After fiddling with it for a while, I didn't reach a satisfying result and decided to do it another day. In the meantime, <a href="https://ryanmulligan.dev/blog/layout-breakouts/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fryanmulligan.dev%2Fblog%2Flayout-breakouts%2F')">Ryan Mulligan's post &quot;Layout Breakouts with CSS Grid&quot;</a> provided the CSS solution I was looking for (<a href="https://codepen.io/hexagoncircle/pen/dyejrpE" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fcodepen.io%2Fhexagoncircle%2Fpen%2FdyejrpE')">see it in action on CodePen</a>).</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="the-solution%3A-named-%60grid-template-columns%60-and-some-math" href="#the-solution%3A-named-%60grid-template-columns%60-and-some-math">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="the-solution%3A-named-%60grid-template-columns%60-and-some-math">The solution: named <code>grid-template-columns</code> and some math</h2></div><p>Put all your content in a <code>content</code> wrapper element that defines named grid areas:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.content</span> <span class="token punctuation">{</span>
  <span class="token property">--gap</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 6vw<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">--full</span><span class="token punctuation">:</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--gap<span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">--content</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>50ch<span class="token punctuation">,</span> 100% - <span class="token function">var</span><span class="token punctuation">(</span>--gap<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">--popout</span><span class="token punctuation">:</span> <span class="token function">minmax</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">--feature</span><span class="token punctuation">:</span> <span class="token function">minmax</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 5rem<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  <span class="token property">grid-template-columns</span><span class="token punctuation">:</span>
    [full-start] <span class="token function">var</span><span class="token punctuation">(</span>--full<span class="token punctuation">)</span>
    [feature-start] <span class="token function">var</span><span class="token punctuation">(</span>--feature<span class="token punctuation">)</span>
    [popout-start] <span class="token function">var</span><span class="token punctuation">(</span>--popout<span class="token punctuation">)</span>
    [content-start] <span class="token function">var</span><span class="token punctuation">(</span>--content<span class="token punctuation">)</span> [content-end]
    <span class="token function">var</span><span class="token punctuation">(</span>--popout<span class="token punctuation">)</span> [popout-end]
    <span class="token function">var</span><span class="token punctuation">(</span>--feature<span class="token punctuation">)</span> [feature-end]
    <span class="token function">var</span><span class="token punctuation">(</span>--full<span class="token punctuation">)</span> [full-end]<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And then control all the component widths using grid template areas. 💯</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.content > *</span> <span class="token punctuation">{</span>
  <span class="token property">grid-column</span><span class="token punctuation">:</span> content<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.popout</span> <span class="token punctuation">{</span>
  <span class="token property">grid-column</span><span class="token punctuation">:</span> popout<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.feature</span> <span class="token punctuation">{</span>
  <span class="token property">grid-column</span><span class="token punctuation">:</span> feature<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.full</span> <span class="token punctuation">{</span>
  <span class="token property">grid-column</span><span class="token punctuation">:</span> full<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>The post even includes a beautiful visualization that explains what's going on. 👏</p>
<p>
      <div class="sqip-container margin-top-l margin-bottom-l">
        <figure class="sqip-image" style="
          --color1: rgb(236,68,124); --color2: rgb(163,100,163); --color3: rgb(236,115,179); --color4: rgb(179,212,188); --color5: rgb(133,74,91);
          --sqip-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMDAgMjE1Ij48cGF0aCBmaWxsPSIjODU0YTViIiBkPSJNMCAwaDMwMHYyMTVIMHoiLz48ZyBmaWxsLW9wYWNpdHk9Ii41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSguNiAuNikgc2NhbGUoMS4xNzE4OCkiPjxjaXJjbGUgY3g9IjUxIiBjeT0iNTUiIHI9IjI0MiIgZmlsbD0iI2ZmYzRmZiIvPjxlbGxpcHNlIGN4PSIxMjciIGN5PSI5MiIgZmlsbD0iI2ZmMTc4MiIgcng9Ijc4IiByeT0iMTgzIi8+PGVsbGlwc2UgY3g9IjIzNCIgY3k9Ijg3IiBmaWxsPSIjZTNmZmYwIiByeD0iMzIiIHJ5PSIxODMiLz48ZWxsaXBzZSBjeD0iMTciIGN5PSI5NSIgZmlsbD0iI2UwZmZlZiIgcng9IjM1IiByeT0iMTgzIi8+PHBhdGggZmlsbD0iIzZjODhiYyIgZD0iTTIzIDcxaDE5N3YyMkgyM3oiLz48ZWxsaXBzZSBjeD0iMjIyIiBjeT0iODIiIGZpbGw9IiM4MGZmZjgiIHJ4PSIxMSIgcnk9IjE4MyIvPjxlbGxpcHNlIGN4PSIzMyIgY3k9IjkxIiBmaWxsPSIjODRmZmY3IiByeD0iMTEiIHJ5PSIxODMiLz48ZWxsaXBzZSBjeD0iMjQ4IiBjeT0iMTAyIiBmaWxsPSIjZmZmYmFlIiByeD0iMTYiIHJ5PSIxODMiLz48L2c+PC9zdmc+');
        ">
          <a href="https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png">
            <picture>
              <source type="image/avif"
                srcset="https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=avif&fit=scale&q=75&w=300&h=216 300w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=avif&fit=scale&q=75&w=500&h=360 500w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=avif&fit=scale&q=75&w=700&h=504 700w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=avif&fit=scale&q=75&w=900&h=648 900w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=avif&fit=scale&q=75&w=1100&h=792 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <source type="image/webp"
                srcset="https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=webp&fit=scale&q=75&w=300&h=216 300w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=webp&fit=scale&q=75&w=500&h=360 500w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=webp&fit=scale&q=75&w=700&h=504 700w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=webp&fit=scale&q=75&w=900&h=648 900w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=webp&fit=scale&q=75&w=1100&h=792 1100w"
                sizes="(max-width: 50em) 98vw, 700px">
              <img width="1000" height="720"
                srcset="https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=jpg&fit=scale&q=75&w=300&h=216 300w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=jpg&fit=scale&q=75&w=500&h=360 500w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=jpg&fit=scale&q=75&w=700&h=504 700w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=jpg&fit=scale&q=75&w=900&h=648 900w, https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png?fm=jpg&fit=scale&q=75&w=1100&h=792 1100w"
                sizes="(max-width: 50em) 98vw, 700px"
                src="https://images.ctfassets.net/f20lfrunubsq/376ucNGfg8ndiCB6UjZyfP/db6f683f75bed8b56467c2c9a1519d0f/Screen_Shot_2022-10-22_at_13.54.41.png"
                alt="A CSS grid visualization showing a base grid that includes components breaking out of the main content."
                loading="lazy"
                onload="this.classList.add('kf-fade-in')">
            </picture>
          </a>
        </figure>
      </div>
    </p>
<p>I love it. 💙 Thank you, Ryan!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20A centered CSS grid with full-width components">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to detect if a font is supported
      </title>
      <link href="https://www.stefanjudis.com/snippets/how-to-detect-if-a-font-is-supported/"/>
      <published>2022-09-17T22:00:00+00:00</published>
      <updated>2022-09-17T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/how-to-detect-if-a-font-is-supported/
      </id>
      <category term="snippet"></category>
        <category term="JavaScript"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Jim Nielsen shared <a href="https://blog.jim-nielsen.com/2022/font-family-and-supports/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fblog.jim-nielsen.com%2F2022%2Ffont-family-and-supports%2F')">how to detect Apple’s “New York” font face</a>, and while reading the post, I realized that I'd forgotten about <code>document<wbr>.fonts</code>. 🙈</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFontFaceSet')">The CSS Font Loading API</a> enables you to access or load fonts via JavaScript. It's good stuff!</p>
<p>And if you want to test if a browser supports a font, <a href="https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/check" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FFontFaceSet%2Fcheck')">the <code>check</code> method's your friend</a>!</p>
<pre class="language-javascript"><code class="language-javascript">document<span class="token punctuation">.</span>fonts<span class="token punctuation">.</span><span class="token function">check</span><span class="token punctuation">(</span><span class="token string">'12px ui-serif'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to detect if a font is supported">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to create a module-based Node.js executable
      </title>
      <link href="https://www.stefanjudis.com/snippets/how-to-create-a-module-based-node-js-executable/"/>
      <published>2022-08-01T22:00:00+00:00</published>
      <updated>2022-08-01T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/how-to-create-a-module-based-node-js-executable/
      </id>
      <category term="snippet"></category>
        <category term="NodeJS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Many of my projects include Node.js scripts to perform setup or teardown steps. And while I could run all of them with the <code>node</code> binary (<code>node create-thumbnails<wbr>.js</code>), I prefer to remove the file extension and make the scripts executables (<code>./create-thumbnails</code>). This approach saves characters, and makes me feel like a hacker!</p>
<p>My steps to create an executable are:</p>
<ul>
<li>Remove the <code>.js</code> file extension (<code>mv create-thumbnails<wbr>.js create-thumbnails</code>).</li>
<li>Make the file executable  (<code>chmod 744 create-thumbnails</code>).</li>
<li>Add a shebang (<code>#!/usr/bin/env node</code>) to signal that the executing shell should use the <code>node</code> binary.</li>
</ul>
<p>Et voilà, you just created a Node.js executable!</p>
<p>This approach has served me well for CommonJS-based scripts using the <code>require</code> function. But it's 2022, and I planned to adopt ECMAScript modules in Node.js executables. Unfortunately, it's not that easy.</p>
<div class="highlightBox info margin-top-xl margin-bottom-xl">
      
          <div class="cornerBubble">
            <svg aria-hidden="true">
              <use xlink:href="/sprite.svg#icon-info"/>
            </svg>
          </div>
        

      
      <p>This article summarizes the most valuable parts of Axel Rauschmayer's extensive guide <a href="https://2ality.com/2022/07/nodejs-esm-shell-scripts.html" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2F2ality.com%2F2022%2F07%2Fnodejs-esm-shell-scripts.html')">&quot;Node.js: creating ESM-based shell scripts for Unix and Windows&quot;</a>. Head on over if you want to dive into cross-platform executables.</p>
</div><p>But what's the problem?</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="the-node.js-binary-is-missing-a-flag-to-specify-module-files" href="#the-node.js-binary-is-missing-a-flag-to-specify-module-files">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="the-node.js-binary-is-missing-a-flag-to-specify-module-files">The Node.js binary is missing a flag to specify module files</h2></div><p><a href="https://nodejs.org/api/esm.html#enabling" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fnodejs.org%2Fapi%2Fesm.html%23enabling')">There are three ways to enable ECMAScript modules in Node.js</a>:</p>
<ol>
<li>use the <code>.mjs</code> file extension</li>
<li>have a surrounding <code>package<wbr>.json</code> with a <code>&quot;type&quot;: &quot;module&quot;</code> field</li>
<li>call <code>node</code> with the <code>--input-type=module</code> flag</li>
</ol>
<p>My executables should work without a file extension, which rules out the first option. I also don't want to declare all files as module files or add a <code>package<wbr>.json</code>, so the <code>type</code> field is out, too.</p>
<p>The <code>--input-type</code> flag looks promising at first, but it only works for strings you pipe into the Node binary yourself.</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># pipe JavaScript code into the node binary</span>
<span class="token builtin class-name">echo</span> <span class="token string">"import { mkdir } from 'node:fs/promises';"</span> <span class="token operator">|</span> <span class="token function">node</span> --input-type<span class="token operator">=</span>module
</code></pre>
<p>Why's there no flag to enable modules when running a file?</p>
<p>I spent the last 15 minutes reading Node.js issues and discussions about the topic, and I still can't answer this question. If you want to read more, <a href="https://github.com/nodejs/node/discussions/37857" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fdiscussions%2F37857')">here's a very long GitHub discussion on why such a flag isn't available yet</a>.</p>
<p>Knock yourself out, the discussion takes many turns, and folks have strong opinions!</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="a-%22hacky%22-solution-to-run-module-based-javascript-executables" href="#a-%22hacky%22-solution-to-run-module-based-javascript-executables">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="a-%22hacky%22-solution-to-run-module-based-javascript-executables">A &quot;hacky&quot; solution to run module-based JavaScript executables</h2></div><p>There are multiple ways to make ECMAScript module-based executables work. They all come with slightly different spins but use the same trick.</p>
<p><a href="https://2ality.com/2022/07/nodejs-esm-shell-scripts.html#unix%3A-arbitrary-filename-extension-via-a-shell-prolog" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2F2ality.com%2F2022%2F07%2Fnodejs-esm-shell-scripts.html%23unix%253A-arbitrary-filename-extension-via-a-shell-prolog')">Axel recommends the following for UNIX environments</a>.</p>
<pre class="language-javascript"><code class="language-javascript"><span class="token hashbang comment">#!/bin/sh</span>
<span class="token string">':'</span> <span class="token comment">// ; cat "$0" | node --input-type=module - $@ ; exit $?</span>

<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> os <span class="token keyword">from</span> <span class="token string">'node:os'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token punctuation">{</span>username<span class="token punctuation">}</span> <span class="token operator">=</span> os<span class="token punctuation">.</span><span class="token function">userInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>username<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>This snippet is wild! In short, it does the following:</p>
<ul>
<li>the <code>cat</code> command reads the current file (the executable itself)</li>
<li>the file content is then piped into <code>node</code> with the <code>--input-type</code> flag</li>
<li>all the original parameters are handed over to <code>node</code>, too (<code>$@</code>)</li>
<li>the <code>node</code> call's exit code is caught and propagated to the executable</li>
</ul>
<p><strong>This instruction makes the file read and pipe itself into the <code>node</code> binary because there's no flag to enable modules</strong>. Wow! 🤯</p>
<hr aria-hidden="true"><p>As said, this post is only for my reference. Here are more resources if you want to learn more about other approaches or make it work on Windows.</p>
<ul>
<li><a href="https://2ality.com/2022/07/nodejs-esm-shell-scripts.html#unix%3A-arbitrary-filename-extension-via-a-shell-prolog" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2F2ality.com%2F2022%2F07%2Fnodejs-esm-shell-scripts.html%23unix%253A-arbitrary-filename-extension-via-a-shell-prolog')">Node.js: creating ESM-based shell scripts for Unix and Windows</a></li>
<li><a href="https://sambal.org/2014/02/passing-options-node-shebang-line/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fsambal.org%2F2014%2F02%2Fpassing-options-node-shebang-line%2F')">Passing options to node on the shebang (#!) line</a></li>
<li><a href="https://gist.github.com/WebReflection/8840ec29d296f2fa98d8be0102f08590" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fgist.github.com%2FWebReflection%2F8840ec29d296f2fa98d8be0102f08590')"> NodeJS Executable Standalone Module </a></li>
</ul>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to create a module-based Node.js executable">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        The CSS quantity query with :nth-last-child()
      </title>
      <link href="https://www.stefanjudis.com/snippets/the-css-quantity-query-with-nth-last-child/"/>
      <published>2022-07-26T22:00:00+00:00</published>
      <updated>2022-07-26T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/the-css-quantity-query-with-nth-last-child/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p>Just parking this CSS snippet on my blog for my future self. 🙈</p>
<p>If you want to select elements only when there's a given number of them, you can <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-last-child" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FCSS%2F%3Anth-last-child')">use a <code>:nth-last-child()</code> selector combination</a>.</p>
<p>The following selector combination is called a &quot;quantity query&quot;.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">li:nth-last-child(n+3),
li:nth-last-child(3) ~ li</span> <span class="token punctuation">{</span>
  <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And it selects all list elements when there are at least three of them. See it in action below. 👇</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>The CSS selector is hard to read but works. 🫣</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="update%3A-%60%3Ahas()%60-enables-easier-to-read-quantity-queries" href="#update%3A-%60%3Ahas()%60-enables-easier-to-read-quantity-queries">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="update%3A-%60%3Ahas()%60-enables-easier-to-read-quantity-queries">Update: <code>:has()</code> enables easier-to-read quantity queries</h2></div><p>The quantity query above is based on some selector trickery and definitely hard to remember. Luckily, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FCSS%2F%3Ahas')">the new <code>:has()</code> pseudo-class</a> is on its way to cross-browser support to simplify such monstrosities!</p>
<div class="highlightBox mdn margin-top-xl margin-bottom-xl">
        <div class="cornerBubble">
          <svg aria-hidden="true"><use xlink:href="/sprite.svg#icon-mdn"></use></svg>
        </div>
        <div class="highlightBox__header">MDN Compat Data (<a href="https://raw.githubusercontent.com/mdn/browser-compat-data/main/css/selectors/has.json">source</a>)</div>
        <div class="highlightBox__body">
          <div class="highlightBox__overflow">
            <table class="highlightBox__compat">
              <caption>Browser support info for <a href="https://developer.mozilla.org/docs/Web/CSS/Reference/Selectors/:has">`:has()`</a> </caption>
              <thead>
                <tr>
                  <td>
                        <img src="/assets/browsers/chrome.webp"
                              srcset="/assets/browsers/chrome.webp, /assets/browsers/chrome@2.webp 2x"
                              width="48" height="51" alt="chrome">
                      </td><td>
                        <img src="/assets/browsers/chrome_android.webp"
                              srcset="/assets/browsers/chrome_android.webp, /assets/browsers/chrome_android@2.webp 2x"
                              width="48" height="51" alt="chrome_android">
                      </td><td>
                        <img src="/assets/browsers/edge.webp"
                              srcset="/assets/browsers/edge.webp, /assets/browsers/edge@2.webp 2x"
                              width="48" height="51" alt="edge">
                      </td><td>
                        <img src="/assets/browsers/firefox.webp"
                              srcset="/assets/browsers/firefox.webp, /assets/browsers/firefox@2.webp 2x"
                              width="48" height="51" alt="firefox">
                      </td><td>
                        <img src="/assets/browsers/firefox_android.webp"
                              srcset="/assets/browsers/firefox_android.webp, /assets/browsers/firefox_android@2.webp 2x"
                              width="48" height="51" alt="firefox_android">
                      </td><td>
                        <img src="/assets/browsers/safari.webp"
                              srcset="/assets/browsers/safari.webp, /assets/browsers/safari@2.webp 2x"
                              width="48" height="51" alt="safari">
                      </td><td>
                        <img src="/assets/browsers/safari_ios.webp"
                              srcset="/assets/browsers/safari_ios.webp, /assets/browsers/safari_ios@2.webp 2x"
                              width="48" height="51" alt="safari_ios">
                      </td><td>
                        <img src="/assets/browsers/samsunginternet_android.webp"
                              srcset="/assets/browsers/samsunginternet_android.webp, /assets/browsers/samsunginternet_android@2.webp 2x"
                              width="48" height="51" alt="samsunginternet_android">
                      </td><td>
                        <img src="/assets/browsers/webview_android.webp"
                              srcset="/assets/browsers/webview_android.webp, /assets/browsers/webview_android@2.webp 2x"
                              width="48" height="51" alt="webview_android">
                      </td>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        121
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        121
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        15.4
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        15.4
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        20.0
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td>
                </tr>
              </tbody>
            </table>
          </div>
<p></div>
</div></p>
<p>With <code>:has()</code> the quantity query becomes a resonable one-liner that reads as <em>&quot;Select all <code>li</code> elements in a list that includes a third last child counting from the end.&quot;</em></p>
<p>[Interactive component: visit the article to see it...]</p>
<p>Boom! And that's all it takes to select elements from a list with at least three items. 🎉</p>
<p>And even exact matches are possible by selecting <em>&quot;all list elements in a list including a list element which is the third and last child.&quot;</em></p>
<p>[Interactive component: visit the article to see it...]</p>
<p><code>:has()</code> is a gift that keeps giving!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20The CSS quantity query with :nth-last-child()">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
    
    <entry>
      <title>
        How to select the previous sibling of an element
      </title>
      <link href="https://www.stefanjudis.com/snippets/how-to-select-the-previous-sibling/"/>
      <published>2022-07-15T22:00:00+00:00</published>
      <updated>2022-07-15T22:00:00+00:00</updated>
      <id>
        https://www.stefanjudis.com/snippets/how-to-select-the-previous-sibling/
      </id>
      <category term="snippet"></category>
        <category term="CSS"></category>
      
      <content type="html">
        <![CDATA[
          <div class="markdown"><p><a href="https://blog.jim-nielsen.com/2022/previous-sibling-selector/" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fblog.jim-nielsen.com%2F2022%2Fprevious-sibling-selector%2F')">Jim Nielsen blogged about a mind-boggling feature of the new <code>:has()</code> pseudo-class</a>. The pseudo-class isn't cross-browser supported yet, but this CSS addition unlocks countless use cases that Frontend engineers have been dreaming of for years.</p>
<p>Here's the current browser support according to MDN.</p>
<div class="highlightBox mdn margin-top-xl margin-bottom-xl">
        <div class="cornerBubble">
          <svg aria-hidden="true"><use xlink:href="/sprite.svg#icon-mdn"></use></svg>
        </div>
        <div class="highlightBox__header">MDN Compat Data (<a href="https://raw.githubusercontent.com/mdn/browser-compat-data/main/css/selectors/has.json">source</a>)</div>
        <div class="highlightBox__body">
          <div class="highlightBox__overflow">
            <table class="highlightBox__compat">
              <caption>Browser support info for <a href="https://developer.mozilla.org/docs/Web/CSS/Reference/Selectors/:has">`:has()`</a> </caption>
              <thead>
                <tr>
                  <td>
                        <img src="/assets/browsers/chrome.webp"
                              srcset="/assets/browsers/chrome.webp, /assets/browsers/chrome@2.webp 2x"
                              width="48" height="51" alt="chrome">
                      </td><td>
                        <img src="/assets/browsers/chrome_android.webp"
                              srcset="/assets/browsers/chrome_android.webp, /assets/browsers/chrome_android@2.webp 2x"
                              width="48" height="51" alt="chrome_android">
                      </td><td>
                        <img src="/assets/browsers/edge.webp"
                              srcset="/assets/browsers/edge.webp, /assets/browsers/edge@2.webp 2x"
                              width="48" height="51" alt="edge">
                      </td><td>
                        <img src="/assets/browsers/firefox.webp"
                              srcset="/assets/browsers/firefox.webp, /assets/browsers/firefox@2.webp 2x"
                              width="48" height="51" alt="firefox">
                      </td><td>
                        <img src="/assets/browsers/firefox_android.webp"
                              srcset="/assets/browsers/firefox_android.webp, /assets/browsers/firefox_android@2.webp 2x"
                              width="48" height="51" alt="firefox_android">
                      </td><td>
                        <img src="/assets/browsers/safari.webp"
                              srcset="/assets/browsers/safari.webp, /assets/browsers/safari@2.webp 2x"
                              width="48" height="51" alt="safari">
                      </td><td>
                        <img src="/assets/browsers/safari_ios.webp"
                              srcset="/assets/browsers/safari_ios.webp, /assets/browsers/safari_ios@2.webp 2x"
                              width="48" height="51" alt="safari_ios">
                      </td><td>
                        <img src="/assets/browsers/samsunginternet_android.webp"
                              srcset="/assets/browsers/samsunginternet_android.webp, /assets/browsers/samsunginternet_android@2.webp 2x"
                              width="48" height="51" alt="samsunginternet_android">
                      </td><td>
                        <img src="/assets/browsers/webview_android.webp"
                              srcset="/assets/browsers/webview_android.webp, /assets/browsers/webview_android@2.webp 2x"
                              width="48" height="51" alt="webview_android">
                      </td>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        121
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        121
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        15.4
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        15.4
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        20.0
                        </span>
                      </td><td>
                        <span class="highlightBox__pill success margin-top-s">
                        105
                        </span>
                      </td>
                </tr>
              </tbody>
            </table>
          </div>
<p></div>
</div></p>
<p>When I heard first about <code>:has()</code>, I thought it's only the long-awaited &quot;parent selector&quot;, but Jim shared that it's the &quot;previous sibling selector&quot;, too! 🤯</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* 
  Select every &lt;a> element that's a child of 
  a &lt;p> element that directly precedes an &lt;hr> element.
*/</span>
<span class="token selector">p:has(+ hr) a</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span>
</code></pre>
<p>With <code>:has()</code> entering the web platform, we can select elements in all directions — <strong>we wanted the &quot;parent selector&quot; but will soon have a selector to match the entire family</strong>.</p>
<p>Styling the next and previous siblings becomes a nifty one liner. Let's look at a few examples!</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="select-the-following-element-siblings" href="#select-the-following-element-siblings">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="select-the-following-element-siblings">Select the following element siblings</h2></div><p>Before getting into the new and fancy things, let's recap how to select next (or following) DOM elements.</p>
<p>To select an element's next sibling, use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FCSS%2FAdjacent_sibling_combinator')">the adjacent sibling selector (<code>+</code>)</a>. The selector will match the element that immediately follows another element.</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>Similarly, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator" style="--image-url: url('https://avatar.stefanjudis.com/https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FCSS%2FGeneral_sibling_combinator')">the general sibling selector (<code>~</code>)</a> allows you to select all following elements.</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>Are you ready for the <code>:has()</code> selector beauty?</p>
<div class="heading-wrapper">
      <a class="margin-left-s text-no-underline" aria-label="Link to heading" aria-describedby="select-the-previous-element-siblings" href="#select-the-previous-element-siblings">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="/sprite.svg#icon-link"/>
        </svg>
      </a>
      <h2 id="select-the-previous-element-siblings">Select the previous element siblings</h2></div><p>We can now invert these selectors with <code>:has()</code> and match elements in the other direction to select previous siblings. 🤯</p>
<p>Use <code>:has()</code> with the adjacent sibling selector (<code>+</code>) to select the immediately previous element...</p>
<p>[Interactive component: visit the article to see it...]</p>
<p>... or the general sibling selector (<code>~</code>) to match all previous siblings.</p>
<p>[Interactive component: visit the article to see it...]</p>
<hr aria-hidden="true"><p><code>:has()</code> is a pretty big deal in CSS land and I can't wait until it finally lands in all browsers, so we can match elements in all directions. Up and down! And left and right — the CSS future's bright!</p>
</div>

          <a href="mailto:stefanjudis@gmail.com?subject=Re%3A%20How to select the previous sibling of an element">
            Reply to Stefan
          </a>
        ]]>
      </content>
    </entry>
</feed>
