From 491f3796f8fb04590899c3f2c3430f6d719632b1 Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Sun, 13 Oct 2019 10:50:43 -0700 Subject: [PATCH] also check for opening rss tag closes #89 --- lib/XRay/Parser.php | 6 + tests/FeedTest.php | 10 + tests/data/feed.example.com/rss-no-xml-tag | 329 +++++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 tests/data/feed.example.com/rss-no-xml-tag diff --git a/lib/XRay/Parser.php b/lib/XRay/Parser.php index bce1b64..3ee2d4b 100644 --- a/lib/XRay/Parser.php +++ b/lib/XRay/Parser.php @@ -63,6 +63,12 @@ class Parser { return Formats\XML::parse($http_response); } + // Some feeds don't start with assertEquals('Barnaby Walters', $item->author->name); $this->assertEquals('https://waterpigs.co.uk', $item->author->url); } + + public function testRSSWithNoXMLTag() { + $url = 'http://feed.example.com/rss-no-xml-tag'; + $response = $this->parse(['url' => $url, 'expect' => 'feed']); + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body)->data; + + $this->assertEquals('feed', $data->type); + } } diff --git a/tests/data/feed.example.com/rss-no-xml-tag b/tests/data/feed.example.com/rss-no-xml-tag new file mode 100644 index 0000000..defa695 --- /dev/null +++ b/tests/data/feed.example.com/rss-no-xml-tag @@ -0,0 +1,329 @@ +HTTP/1.1 200 OK +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: application/rss+xml; charset=utf-8 +Connection: keep-alive + + + + + The Every Layout Blog + https://every-layout.dev + CSS layout problems, solved with an algorithmic approach + en-us + 2019-10-07T08:46:29.383Z + + + + Algorithmic Design + https://every-layout.dev/blog/algorithmic-design/ + 2019-06-14T00:00:00.000Z + https://every-layout.dev/blog/algorithmic-design/ + <p>The term “system” has two meanings:</p> +<ol> +<li>A set of principles or procedures to be followed; a method</li> +<li>A set of interconnecting parts, working together</li> +</ol> +<p>When someone says “design system”, one or other of these meanings may come to mind. An <em>effective</em> design system is both these things together: an exemplification of how to proceed. Weak design systems tend to be either (1) or (2) alone; <em>“do as I say”</em> or <em>“here’s what was done”</em>.</p> +<p>Unfortunately, it’s quite possible to document something within a (nominal) design system without it being a product of systems thinking. Contradiction, duplication, and any number of errors are inevitable unless you take the whole system into account <em>each</em> time you make a contribution. No individual can do this reliably—especially where the extant “system” has already devolved into a morass of exceptions and caveats.</p> +<h2 id="automating-intent">Automating intent</h2> +<p>Systematic design is better than acting randomly, but it relies too much on the vigilance of its human contributors. An algorithmic approach to design is the antidote.</p> +<p>Algorithms, like systems, constitute rules devised by humans. It’s just that an algorithm’s rules don’t have to be <em>carried out</em> by a human. It’s dutifully done on their behalf. This means fewer errors and greater consistency, without sacrificing control.</p> +<p>Documentation is to a system what <em>extrapolation</em> is to an algorithm. Algorithms <em>amplify</em> design.</p> +<div role="figure"> +<p><img src="https://every-layout.dev/images/illustrations/algorithmic_amplifier.svg" alt="Guitar plugged into an amplifier. The human input is small—just the plucking of a string—but the amplifier’s output can be huge"></p> +</div> +<p>So long as your algorithms are well-defined, they should be able to reason for themselves and handle different contexts and circumstances. Where the rules are ill-defined, you’ll suffer a leaky or malfunctioning system. Like a poorly balanced guitar, it will demand continual tuning.</p> +<h2 id="algorithmic-design-for-the-web">Algorithmic design for the web</h2> +<p>The layout of web content is innately algorithmic, and the web is responsive due largely to the text wrapping algorithm: words automatically wrap according to the available space, ensuring no content is obscured.</p> +<p>We make many of our biggest mistakes as visual designers for the web by insisting on hard coding designs. We break browsers’ layout algorithms by applying fixed positions and dimensions to our content.</p> +<p>Instead, we should be deferential to the underlying algorithms that power CSS, and we should think in terms of algorithms as we extrapolate layouts based on these foundations. We need to be leveraging selector logic, harnessing flow and wrapping behavior, and using calculations to adapt layout to context.</p> +<p>The tools for flexible, robust, and efficient web layout are there. We are just too busy churning out CSS to use them.</p> + + + + + Eschewing Shadow DOM + https://every-layout.dev/blog/eschewing-shadow-dom/ + 2019-06-14T00:00:00.000Z + https://every-layout.dev/blog/eschewing-shadow-dom/ + <p>Regarding styling, Shadow DOM is really good at one thing: preventing per-component styles from leaking out and affecting other parts of the document.</p> +<p>Unfortunately, Shadow DOM also has a very opinionated way of preventing styles being applied from the parent document. Here is a roundup of the quirks and associated issues I’ve encountered:</p> +<h2 id="shadow-dom-styling-issues">Shadow DOM styling issues</h2> +<h3 id="universal-styles">Universal styles</h3> +<p>Some universal styles (using the <code>*</code> selector) are applied, and others are not. The <code>color</code> property appears to pierce shadow boundaries, but I’ve had no luck with <code>box-sizing</code>. Having to set <code>box-sizing: border-box</code> for each component is redundant.</p> +<h3 id="inheritance">Inheritance</h3> +<p>Document styles <em>are</em> inherited in Shadow DOM. But inheritance only gets you so far, especially since not all properties are inheritable by default anyway. I’ve tried hacking things together with the explicit <code>inherit</code> keyword, but that seems to have no effect.</p> +<p>Using the following is no good, because it forces every node to inherit <em>every</em> property from the parent. If <code>&lt;my-component&gt;</code> had <code>display: flex</code> applied, every one of the component’s descendants would adopt it.</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">*</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">all</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token comment">/* yikes */</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<h3 id="slot-restrictions">Slot restrictions</h3> +<p>For performance reasons, the <code>::slotted()</code> selector, which allows you to style the Light DOM content of your component from within your Shadow DOM, only lets you affect child elements and not descendants at depth. This is a bit restrictive. Worse, you cannot seem to use sibling combinators like <code>+</code> or <code>~</code>. None of the following works:</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">slotted</span><span class="token punctuation">(</span>*<span class="token punctuation">)</span> + * <span class="token comment">/* Nope */</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">slotted</span><span class="token punctuation">(</span>* + *<span class="token punctuation">)</span> <span class="token comment">/* Nope */</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">slotted</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">slotted</span><span class="token punctuation">(</span>*<span class="token punctuation">)</span> <span class="token comment">/* Nope */</span></span></code></pre> +<h3 id="specificity">Specificity</h3> +<p>In making layout components, I like to take a progressive approach: style the component and its Light DOM nodes in my document stylesheet, then enhance with instance-specific styles via props (attributes) in Shadow DOM.</p> +<p>For a component instance that looks like this...</p> +<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-component</span> <span class="token attr-name">itemWidth</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>10rem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-component</span><span class="token punctuation">></span></span></span></code></pre> +<p>...I would apply the <code>itemWidth</code> value in the Shadow DOM using interpolation:</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token template-string"><span class="token string">`</span><br><span class="highlight-line">&lt;style></span><br><span class="highlight-line"> ::slotted(*) {</span><br><span class="highlight-line"> max-width: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>itemWidth<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;</span><br><span class="highlight-line"> }</span><br><span class="highlight-line">&lt;/style></span><br><span class="highlight-line">`</span></span><span class="token punctuation">;</span></span></code></pre> +<p>However, despite being a value <em>specific</em> to my component instance, this will be overridden by the default/fallback styles because they inevitably use a more specific selector:</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">my-component > *</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">max-width</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>This is fixed with <code>!important</code>, but feels counter-intuitive and hacky:</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token operator">&lt;</span>style<span class="token operator">></span></span><br><span class="highlight-line"> <span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">slotted</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> max<span class="token operator">-</span>width<span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>itemWidth<span class="token punctuation">}</span> <span class="token operator">!</span>important<span class="token punctuation">;</span> <span class="token comment">/* urgh */</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token operator">&lt;</span><span class="token operator">/</span>style<span class="token operator">></span></span></code></pre> +<h2 id="instance-styling-without-shadow-dom">Instance styling without Shadow DOM</h2> +<p>For all these reasons, I have developed a different approach to styling my layout components. It’s experimental for web components, but is not unprecedented: Conceptually it is influenced by the sadly defunct <code>scoped</code> CSS spec (as <a href="https://vue-loader.vuejs.org/guide/scoped-css.html">emulated in Vue</a>) and implementations for React like <a href="https://www.styled-components.com/">Styled Components</a>. In short: it namespaces and embeds prop-derived instance styles in the document head.</p> +<p>Here’s the full custom element code for a simple component. It lets you use a <code>measure</code> prop to control the <code>max-width</code> of a center-aligned block element.</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">class</span> <span class="token class-name">Center</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">render</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">this</span><span class="token punctuation">.</span>i <span class="token operator">=</span> <span class="token template-string"><span class="token string">`Center-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>measure<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token keyword">this</span><span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> document<span class="token punctuation">.</span>head<span class="token punctuation">.</span>innerHTML <span class="token operator">+=</span> <span class="token template-string"><span class="token string">`</span><br><span class="highlight-line"> &lt;style id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"></span><br><span class="highlight-line"> [data-i="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"] {</span><br><span class="highlight-line"> max-width: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>measure<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;</span><br><span class="highlight-line"> }</span><br><span class="highlight-line"> &lt;/style></span><br><span class="highlight-line"> `</span></span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"> <span class="token keyword">get</span> <span class="token function">measure</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'measure'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">'65ch'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"> <span class="token keyword">set</span> <span class="token function">measure</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'measure'</span><span class="token punctuation">,</span> val<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"> <span class="token keyword">static</span> <span class="token keyword">get</span> <span class="token function">observedAttributes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token string">'measure'</span><span class="token punctuation">]</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"> <span class="token function">connectedCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"> <span class="token function">attributeChangedCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">'customElements'</span> <span class="token keyword">in</span> window<span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">'center-l'</span><span class="token punctuation">,</span> Center<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>On <code>connectedCallback</code>, and each time the <code>measure</code> prop changes, the constructor’s <code>render</code> function is fired. First, the function creates a string based on the new <code>measure</code> value.</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">this</span><span class="token punctuation">.</span>i <span class="token operator">=</span> <span class="token template-string"><span class="token string">`Center-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>measure<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span></span></code></pre> +<p>This identifier is applied to the component instance as a data attribute. Correspondingly, it is used as the <code>id</code> for a <code>&lt;style&gt;</code> element, and within that <code>&lt;style&gt;</code> element using the attribute selector syntax.</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">this</span><span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> document<span class="token punctuation">.</span>head<span class="token punctuation">.</span>innerHTML <span class="token operator">+=</span> <span class="token template-string"><span class="token string">`</span><br><span class="highlight-line"> &lt;style id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"></span><br><span class="highlight-line"> [data-i="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"] {</span><br><span class="highlight-line"> max-width: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>measure<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;</span><br><span class="highlight-line"> }</span><br><span class="highlight-line"> &lt;/style></span><br><span class="highlight-line"> `</span></span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>Note the <code>if</code>. A new stylesheet is only added if one with an identifier that matches the current configuration does not already exist. That is, if I were to add a second <code>&lt;center-l&gt;</code> component to my page with the same <code>measure</code> value, it would defer to the existing embedded stylesheet. This keeps DOM operations and bloat to a minimum.</p> +<h3 id="default-values">Default values</h3> +<p>My <code>measure</code> getter supplies a default value where none exists:</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">get</span> <span class="token function">measure</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'measure'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">'65ch'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>This is needed so that <code>this.measure</code> does not return <code>undefined</code>. But it does not preclude me from adding a default to my document stylesheet as well — for where JS fails or custom elements are not supported:</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">center-l</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">margin-left</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">margin-right</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">max-width</span><span class="token punctuation">:</span> 65ch<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<h3 id="performance">Performance</h3> +<p>Crucially, because Shadow DOM is not involved, it is possible to use a library like JSDOM or headless Chrome to run the code and embedded the necessary (initial) styles on the server. Such tools cannot currently prerender Shadow DOM content (read <a href="https://medium.com/@treshugart/%C3%A5server-side-rendering-web-components-e5df705f3f48">Server-side rendering web components</a> for more detail).</p> +<p>With the static site generator, <a href="https://www.11ty.io/">Eleventy</a>, I was able to server-side render initial styles using JSDOM and a post-processing “transform” function. Note this required <a href="https://www.npmjs.com/package/@tbranyen/jsdom">a fork of JSDOM that supports custom elements</a>. Hopefully official custom element support will land soon.</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line">eleventyConfig<span class="token punctuation">.</span><span class="token function">addTransform</span><span class="token punctuation">(</span><span class="token string">'ssr'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>page<span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">let</span> dom <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JSDOM</span><span class="token punctuation">(</span>page<span class="token punctuation">,</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> resources<span class="token punctuation">:</span> <span class="token string">'usable'</span><span class="token punctuation">,</span></span><br><span class="highlight-line"> runScripts<span class="token punctuation">:</span> <span class="token string">'dangerously'</span></span><br><span class="highlight-line"> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token keyword">let</span> document <span class="token operator">=</span> dom<span class="token punctuation">.</span>window<span class="token punctuation">.</span>document<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token string">'&lt;!DOCTYPE html>\r\n'</span> <span class="token operator">+</span> document<span class="token punctuation">.</span>documentElement<span class="token punctuation">.</span>outerHTML<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code></pre> +<p>With server-side rendering in place, these kinds of layout-specific components do not require JavaScript to run on the client at all—at least not for initial styling. Rerendering is supported on <code>attributeChangedCallback</code> mostly for in-browser design experimentation, using developer tools.</p> + + + + + You Pay (Or Maybe You Don’t) + https://every-layout.dev/blog/you-pay/ + 2019-06-21T00:00:00.000Z + https://every-layout.dev/blog/you-pay/ + <p>Sometimes people get things spectacularly wrong. So wrong, in fact, that you might suspect they are lying, or someone has been lying to them. On more than one occasion, I’ve been told the political and economic theory of Socialism would insist I <em>worked for free</em>. I’ve had people identify me as a socialist, or <em>leftist</em>, and demand that—in accordance with my political stance—I should give them my books, designs, and other wares for <em>no money whatsoever</em>.</p> +<p>Suffice it to say that Socialism (wherever it is not being deliberately misrepresented) is interested in disseminating rights, protections, and fair compensation for the world’s workers and makers. Instead, it is Capitalism that tries to pay you as little as possible. The less you <em>give</em> for the value you <em>take</em> from the worker, the more excess money (read: capital) you can accumulate.</p> +<p>I hope that clears that up.</p> +<p>Some people can afford to work for nothing: people who have already accumulated lots of capital, for example, or are subsidized by their families. For them, “exposure” alone may be enough motivation. But it’s important they <em>do</em> charge for their work anyway. Why? Because the more people work for free, the less perceived value the work has, and the more likely those who can’t afford to work for <em>fuck all</em> will not be able to <strong>secure the money they need to live</strong>.</p> +<blockquote class="docs-quote"> + <span> + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-quote-left"></use> + </svg> + Always charge. If not for yourself, then for the next person. + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-quote-right"></use> + </svg> + </span> +</blockquote> +<p>Andy and I have put a lot of work and thought into <strong>Every Layout</strong>. We want it to be the best resource it can be. Naturally, we would like to be paid for the value we are giving you. However, we know that not everyone can afford the things they’d like to have. We’re trying to address this in two ways:</p> +<ol> +<li>A large selection of free content, including all of the “rudiment” articles that cover the basics of (our take on) contemporary CSS</li> +<li>An honor system, wherein you can claim to be eligible for the full <strong>Every Layout</strong> <em>for free</em></li> +</ol> +<p>What makes you eligible for (2)? If you are currently out of work, you are a full-time student or under 19 years old, you are trying to get your first job as a web developer or designer, or you are an unpaid volunteer for a charitable organization not involved in <a href="https://en.wikipedia.org/wiki/Proselytism">proselytism</a>: consider yourself a match. Also, if you are the sole person of your ethnicity, gender, or sexual orientation at your company or in your local developer community: we will gift <strong>Every Layout</strong> to you. If you have a disability that makes accessing equivalent resources difficult, you can have this resource for free. We are trying to make it as accessible as possible.</p> +<p><strong>However:</strong> If you are a paying student (including a student or former student of a code bootcamp) the teaching establishment in question should be paying for its teaching materials. If your teacher sends you here for a free copy, let us know. We are happy to finance individuals, but not businesses. We will offer them a bulk license at a discount, so you can get your free <strong>Every Layout</strong> through them.</p> +<p>It would be logistically and ethically implausible for us to <em>vet</em>, or otherwise judge, if you deserve to have <strong>Every Layout</strong> without a charge. If you <em>believe</em> you fit into the criteria above, let us know. That’s the honor part.</p> +<blockquote class="docs-quote" aria-hidden="true"> + <span> + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-quote-left"></use> + </svg> + If you’re caught punching down, you get fuck all for free from us + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-quote-right"></use> + </svg> + </span> +</blockquote> +<p>Are we suckers? Perhaps, to some. But while <em>you</em> alone decide if you qualify, we decide if you <em>disqualify</em>. That is, if we see you saying or sharing racist, homophobic, transphobic, sexist, or fascist sentiments, or you’re caught engaging in what we consider, in any way, <a href="https://www.urbandictionary.com/define.php?term=punch%20down">punching down</a>, you get <em>fuck all</em> for free from us.</p> +<div role="figure"> +<p><img src="https://every-layout.dev/images/illustrations/venn.svg" alt="Venn diagram. On the left: rich. On the right: Dick face. At the intersection: Good luck"></p> +</div> +<p>We figure the kind of people who <em>want</em> our product, and can comfortably <em>afford</em> it, but resent the idea of subsidizing those who <em>can’t</em> afford it, will more than likely disqualify themselves in one way or another. It’s not watertight, but that’s how it’s going to work. Thank you for reading.</p> +<h2 id="sign-up">Sign up</h2> +<p>If you think you qualify for the Honour System, head over to and fill out the <a href="https://every-layout.dev/honour-system/">application form</a>. We carefully consider each application, so would love to hear from you.</p> + + + + + Multi-column manipulation + https://every-layout.dev/blog/multi-column-manipulation/ + 2019-07-20T00:00:00.000Z + https://every-layout.dev/blog/multi-column-manipulation/ + <p>Despite predating both Grid and Flexbox, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns/Using_multi-column_layouts">Multi-column Layout</a> represents—at least to me—an even more radical departure from the way we normally <em>do</em> and <em>think about</em> CSS layout. Dividing just one element into a multi-column representation of its contents feels weird, heretical even.</p> +<p>Setting a multi-column context means asking (flow) content to progress, by column, in a horizontal direction. This invokes one of two issues, depending on whether you set a height on the element.</p> +<p>With no set height, there's no limit to the height of the columns. This will result in vertical overflow, and the necessity to scroll down and up the page to read each successive column. Many are likely to find this arduous.</p> +<div role="figure"> +<p><img src="https://every-layout.dev/images/illustrations/multicol_up_down.svg" alt="A zig zag line illustrates the reading direction of multiple columns"></p> +</div> +<p>With a set height, columns are forced to spawn in the inline (horizontal) direction, creating horizontal overflow. Setting <code>overflow: auto</code> frames this correctly.</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.columns</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">height</span><span class="token punctuation">:</span> 25vh<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token comment">/* ↓ columns defined by width */</span></span><br><span class="highlight-line"> <span class="token property">columns</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>However, despite the increasing popularity of <a href="https://iamsteve.me/blog/entry/horizontal-scrolling-responsive-menu">scrolling menus</a> and other such patterns harnessing horizontal scrolling, it is still an unconventional interaction paradigm — and <a href="https://inclusivedesignprinciples.org/#be-consistent">unconventional patterns are liable to be misapprehended by users</a>. There are ways to increase <a href="https://www.interaction-design.org/literature/book/the-glossary-of-human-computer-interaction/affordances">perceived affordance</a>, of course—perhaps by adding some custom styling to the scrollbar (<code>-webkit-scrollbar</code>), or providing some <a href="http://lea.verou.me/2012/04/background-attachment-local/">overflow-dependent shadows</a>. But this may not be enough.</p> +<h2 id="quantity-dependent-columns">Quantity-dependent columns</h2> +<p>One thing I have been experimenting with is the application of multiple columns in response to content quantity.</p> +<p>Let's say I have a bullet list, and let's assume each bullet point is likely to be relatively short; no more than a sentence. There's no benefit to dividing the list into columns when there are only a few points. But the overall height of the list is shortened (and the chance of the reader being able to see the whole list without scrolling is increased) when a long list is divided into two.</p> +<div role="figure"> +<p><img src="https://every-layout.dev/images/illustrations/multicol_split_list.svg" alt="Left: a one column list breeches the viewport at the top and bottom. Right: the list is split into two and can now be contained by the viewport height"></p> +</div> +<p>In the following example, the list is split into two columns where there are 5 or more list items.</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">ol, ul</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">columns</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">column-gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">li</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">column-span</span><span class="token punctuation">:</span> all<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">li:nth-last-child(n+5), </span><br><span class="highlight-line">li:nth-last-child(n+5) ~ *</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">column-span</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>By default, <code>column-span</code> is set to <code>all</code>, meaning each list item ignores the two-column mandate. A <a href="http://alistapart.com/article/quantity-queries-for-css/">quantity query</a> (the final declaration block) then resets <code>column-span</code> to <code>none</code> where 5 or more list items are present. Despite the misleading <code>none</code> value, this means list items will span one of the two columns.</p> +<div role="figure"> +<p><img src="https://every-layout.dev/images/illustrations/multicol_spanning.svg" alt="Three columns. The first element in just one column wide because it has column-span: none. The second element is the width of all three columns, because it has column-span: all."></p> +</div> +<p>To follow is a live demo. Try opening up developer tools and removing a couple of list items. Note that this behavior is not currently available in Firefox. <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=616436">There is a bug open to implement <code>column-span</code></a>. Thank you to Erik Wallace for finding it.</p> +<noscript> + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-warning"></use> + </svg> + This demo becomes interactive with JavaScript. If you enable JavaScript, you will be able to use the interactive functionality. +</noscript> +<div class="docs-demo-inline"> +<style> + .columns-list { + margin: 0; + padding: 0; + columns: 2; + column-gap: 2rem; + list-style-position: inside; + } + + .columns-list li { + column-span: all; + } + + .columns-list li+li { + margin-top: 1rem; + } + + .columns-list li:nth-last-child(n+5), + .columns-list li:nth-last-child(n+5)~* { + column-span: none; + } +</style> +<shrink-grow static="" label="A bullet list, divided into 2 columns with 5 or more items" height="auto" unit="rem"> + <ul class="columns-list"> + <li> + <words-l count="8,10" capitalize=""></words-l> + </li> + <li> + <words-l count="8,10" capitalize=""></words-l> + </li> + <li> + <words-l count="8,10" capitalize=""></words-l> + </li> + <li> + <words-l count="8,10" capitalize=""></words-l> + </li> + <li> + <words-l count="8,10" capitalize=""></words-l> + </li> + <li> + <words-l count="8,10" capitalize=""></words-l> + </li> + </ul> +</shrink-grow> + <div class="docs-launcher"> + <a class="cta" href="https://every-layout.dev/demos/multicol-quantity-query" target="_blank"> + Launch + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-external"></use> + </svg> + <span class="vh">(opens new tab)</span> + </a> + </div> +</div> +<h2 id="block-direction-overflow">Block direction overflow</h2> +<p>As <a href="https://github.com/w3c/csswg-drafts/issues/2923">Rachel Andrew has proposed</a>, it would be beneficial to be able to control both the inline and block overflow direction. The support of block overflow would mean we could assume control over <em>both</em> column width and height. So long as the chosen height is no taller than the current viewport, the repetitive vertical scrolling issue described above disappears.</p> +<div role="figure"> +<p><img src="https://every-layout.dev/images/illustrations/multicol_rows.svg" alt="Two rows, each of four columns of text. The first row is entirely within the height of the viewport"></p> +<div class="figcaption"><span class="vh">Image caption: </span>Reading down columns no longer necessitates vertical scrolling.</div> +</div> +<p>How block overflow direction is implemented and exposed is still <a href="https://github.com/w3c/csswg-drafts/issues/2923">up for grabs</a> so, if you have any ideas, you may want to voice them.</p> +<p>My initial thinking is that a <code>column-height</code> property should be supported alongside <code>column-width</code> and <code>column-count</code>. The <code>columns</code> shorthand property would then need to take height as a third parameter.</p> +<p>Currently, <code>columns</code> takes <code>column-width</code> and <code>column-count</code> in any order—presumably because parsing can identify which is a length value and which is just an integer. This becomes more complex with two length parameters, so an expected order for these properties may need to be set. If that expected order is width-before-height, then the following values would be considered valid (where <code>30ch</code> represents the width, and <code>25vh</code> represents the height):</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.columns</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">columns</span><span class="token punctuation">:</span> 30ch 25vh<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">.columns</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">columns</span><span class="token punctuation">:</span> 30ch 25vh 3<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">.columns</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">columns</span><span class="token punctuation">:</span> 30ch 3 25vh<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">.columns</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">columns</span><span class="token punctuation">:</span> 3 30ch 25vh<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>It's worth noting that 'hard-coding' a <code>column-count</code> is not likely to be useful in most cases, since both columns and rows are now being dynamically provisioned based on the available space. It's also worth noting that <code>column-width</code> sets an <em>ideal</em> width, not a fixed one, much like <code>flex-basis</code>. This eliminates overlaps and gaps.</p> +<p>The <code>column-gap</code> property injects space <em>between</em> columns. With support of <code>column-height</code> in place, <code>row-gap</code> would have to be supported as well. Although you are probably more familiar with the CSS Grid-specific properties <code>grid-gap</code>, <code>grid-row-gap</code>, and <code>grid-column-gap</code>, Firefox already supports the generic <code>gap</code>, <code>row-gap</code>, and <code>column-gap</code> properties for Flexbox. The intention is to normalize <code>gap</code> across the Grid, Flexbox, and Multi-column modules.</p> + + + + + Why you should buy your staff a copy of Every Layout + https://every-layout.dev/blog/your-boss-pays/ + 2019-07-30T00:00:00.000Z + https://every-layout.dev/blog/your-boss-pays/ + <p>Dear bosses,</p> +<p>You have probably been sent this link by a staff member who would like you to buy them a copy of Every Layout. In this post, I’ll outline some benefits that you, the boss, can enjoy if you invest in your staff’s learning and training by purchasing a copy of Every Layout.</p> +<h2 id="when-you-understand-css%2C-you-generally-make-fewer-mistakes">When you understand CSS, you generally make fewer mistakes</h2> +<p>So many mistakes—especially with CSS—can be directly attributed to a lack of knowledge, but you only get that knowledge from learning and experience. We share our vast, combined experience in Every Layout to give people a head-start.</p> +<p>CSS is hard, but what we do with Every Layout is demonstrate how keeping things as simple as possible can give your CSS the most power. We leverage the power of the browser and guide it, rather than micro-manage it, for a much more resilient front-end. This approach is completely valid for building a tiny blog, all the way up to a huge design system.</p> +<blockquote class="docs-quote"> + <span> + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-quote-left"></use> + </svg> + We leverage the power of the browser and guide it, rather than micro-manage it + <svg class="docs-icon" focusable="false"> + <use xlink:href="/images/all.svg#icon-quote-right"></use> + </svg> + </span> +</blockquote> +<p>We’re convinced that our approach in Every Layout will reduce mistakes. In fact, we know it will, because that approach has served us both very well in our combined experience of over 20 years, working on huge projects and tiny projects alike.</p> +<h2 id="good-css-knowledge-will-help-to-reduce-technical-debt">Good CSS knowledge will help to reduce technical debt</h2> +<p>CSS is great, but when you over-engineer and over-complicate it, it can get pretty hairy, pretty quickly. A common, knee-jerk response to this conundrum is to employ the use of a CSS-in-JS library or heavy-duty framework, but that is like taking out a <a href="https://en.wikipedia.org/wiki/Wonga.com">Wonga loan</a> to pay off your house.</p> +<p>Technical debt is incredibly expensive, and with modern, all-encompassing web-tooling, the interest rates can be extortionate. In contrast to this, the content we created for Every Layout encourages you to write <em>very</em> powerful layouts, with a tiny, native footprint. These layouts are also portable and interoperable, which means that you can very likely use them across most of your team’s projects. Using these layouts to refactor your existing projects can drastically reduce that high-interest technical debt.</p> +<h2 id="wrapping-up">Wrapping up</h2> +<p>You get a lot of bang-for-your-buck with Every Layout and it should be a valuable resource for both you and your staff, because their enhanced knowledge will have a positive effect on your business as a whole.</p> +<p>We also try to make the licensing as viable as possible, so if you do want to buy licences for multiple staff members, <a href="mailto:support@emails.every-layout.dev">get in touch</a> and we can work out a bulk discount for you.</p> +<p>If you’re not the boss and need to convince your boss to buy you a copy of Every Layout, send them a link to this post.</p> + + + + + Dynamic CSS Components Without JavaScript + https://every-layout.dev/blog/css-components/ + 2019-10-07T00:00:00.000Z + https://every-layout.dev/blog/css-components/ + <p>What is a component? In electronics, it's a discrete device used to affect the behavior of the electricity in a circuit, or <em>electrical system</em>. Electronic components are modular, and are often used in combination to achieve specific values. For example, a number of resistor components of different values might be used <em>in series</em> to produce a desired resistance.</p> +<p>Electronic components take an input, augment it internally, and return a different output. In this respect, the only real conceptual difference between electronic components and web interface components is that the former passes the signal <em>along</em>, and the latter passes it <em>down</em>.</p> +<h2 id="props">Props</h2> +<p>Whether you are using React, Vue, custom elements, or some other component-organized system, <em>inputs</em> tend to be passed to the component via <em>props</em> (properties). The property names/identifiers belong to the component; the <em>values</em> of the properties belong to a containing (ancestor) component and its current state.</p> +<p>A UI component outputs (using a <code>return</code> statement in JavaScript) some UI, and the shape this UI takes is, at least partially, beholden to the values of the props originally passed. Like resitors augment the current, UI components augment the interface.</p> +<h2 id="rendering">Rendering</h2> +<p>On the web, the UI a component outputs consists of markup/HTML. The structure of that HTML will differ depending on how the current values of its props are interpolated. For example, if a hypothetical <code>users</code> prop consisted of an array of 726 users in its current state, the following render function would output an unordered list of 726 users. Array of 891; list of 891.</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">const</span> <span class="token function-variable function">render</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token template-string"><span class="token string">`&lt;ul></span><br><span class="highlight-line"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>prop<span class="token punctuation">.</span>users<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>user <span class="token operator">=></span> `</span><br><span class="highlight-line"> <span class="token operator">&lt;</span>li<span class="token operator">></span>$<span class="token punctuation">{</span>user<span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/li></span><br><span class="highlight-line"> `</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">''</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">></span>`</span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> +<p>What's neat is that I have a single component, as one module definition, that can be instantiated under differing circumstances and produce different outcomes. For example, I might use the same component just to display logged in users. All that has to change is the input: the value of the <code>users</code> prop.</p> +<h2 id="css-components">CSS components</h2> +<p>When it comes to CSS, it would probably be beneficial to work in a similar way: leverage a master styling module (née a <em>stylesheet</em>), and provide props just to tweak certain values.</p> +<p>This is the philosophy of the layout primitives that manifest <strong>Every Layout's</strong> layouts. But the interpolation of prop values is achieved using template literals inside custom element definitions (read: JavaScript). They are only partly “JavaScript independent” because server-side rendering applies the initial styles. Subsequent changes to styling props, on the client, mean re-rendering those styles by manipulating the DOM. This is from the <a href="https://every-layout.dev/layouts/stack"><strong>Stack</strong></a> layout:</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line">document<span class="token punctuation">.</span>head<span class="token punctuation">.</span>innerHTML <span class="token operator">+=</span> <span class="token template-string"><span class="token string">`</span><br><span class="highlight-line"> &lt;style id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"></span><br><span class="highlight-line"> [data-i="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"]</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>recursive <span class="token operator">?</span> <span class="token string">''</span> <span class="token punctuation">:</span> <span class="token string">' >'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> * + * {</span><br><span class="highlight-line"> margin-top: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>space<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;</span><br><span class="highlight-line"> }</span><br><span class="highlight-line"></span><br><span class="highlight-line"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>splitAfter <span class="token operator">?</span> `</span><br><span class="highlight-line"> <span class="token punctuation">[</span>data<span class="token operator">-</span>i<span class="token operator">=</span>"$<span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"]:only-child {</span><br><span class="highlight-line"> height: 100%;</span><br><span class="highlight-line"> }</span><br><span class="highlight-line"></span><br><span class="highlight-line"> [data-i="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"] > :nth-child(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>splitAfter<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">) {</span><br><span class="highlight-line"> margin-bottom: auto;</span><br><span class="highlight-line"> }`</span></span></span><br><span class="highlight-line"> <span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">}</span></span><br><span class="highlight-line"> <span class="token operator">&lt;</span><span class="token operator">/</span>style<span class="token operator">></span></span><br><span class="highlight-line">`<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/\s\s+/g</span><span class="token punctuation">,</span> <span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code></pre> +<p>(There are <a href="https://every-layout.dev/blog/eschewing-shadow-dom">reasons why we don't use Shadow DOM</a>.)</p> +<p>Since only JavaScript can affect the prop/attribute that sets the re-render in motion in the first place, one could argue this setup already hangs together as well as can be expected. But what if we could do something similar with just CSS and HTML attribution? It would certainly be less intensive.</p> +<h2 id="custom-properties">Custom properties</h2> +<p>Custom properties, unlike static Sass variables, work with the cascade. They can be set globally, but also locally and contextually. Typically, we do this all in a stylesheet file away from our component's HTML. Take the following example, which uses custom properties to manage “separator” symbols applied between adjacent/inline list items.</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token comment">/* start template */</span></span><br><span class="highlight-line"><span class="token selector">.separated > *</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">display</span><span class="token punctuation">:</span> inline<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">.separated > * + *::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--symbol, <span class="token string">'→'</span><span class="token punctuation">)</span><span class="token string">'\0020'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token comment">/* end template */</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token comment">/* start instance */</span></span><br><span class="highlight-line"><span class="token selector">.separated.hand</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">--symbol</span><span class="token punctuation">:</span> <span class="token string">'☞'</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token comment">/* end instance */</span></span></code></pre> +<p>The first two of the three declarations is a kind of template (as commented). It takes care of all the gnarly CSS, as well as applying a default value (<code>→</code>). When defining my special hand-based separator component, all I have to do is supply a property representing my chosen symbol. This is analogous to templating HTML using props and JavaScript string interpolation.</p> +<p>Except for one thing: <code>.separated.hand</code> is not an <em>instance</em> of a component, so much as an identifier for a possible/proposed instance of a component. It is not CSS I will necessarily need, and would represent bloat where the <code>class=&quot;separated hand&quot;</code> component is retired.</p> +<p>Instead, why don't I just apply the symbol part in the HTML, as needed?</p> +<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>separated<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">--symbol</span><span class="token punctuation">:</span> <span class="token string">'☞'</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>First separated item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Second separated item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>etc.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></span></code></pre> +<p>This works without any JavaScript (on the client <em>or</em> server). It's just CSS.</p> +<p>But it's also highly efficient for client runs/renders where it <em>is</em> a part of HTML templating (think JSX). Whereas a typical CSS-in-JS solution uses JavaScript interpolation to template a stylesheet and append it to the DOM, all that has to change here is the symbol itself. The CSS “module” will already be loaded and, since it doesn't need to be manipulated directly, can also be cached.</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">return</span> <span class="token template-string"><span class="token string">`</span><br><span class="highlight-line"> &lt;ul class="separated" style="--symbol: {symbol}"></span><br><span class="highlight-line"> &lt;li>First separated item&lt;/li></span><br><span class="highlight-line"> &lt;li>Second separated item&lt;/li></span><br><span class="highlight-line"> &lt;li>Setc.&lt;/li></span><br><span class="highlight-line"> &lt;/ul></span><br><span class="highlight-line">`</span></span><span class="token punctuation">;</span></span></code></pre> +<p>Moreover, you can affect the props directly in your browser's dev tools, by editing the <code>style</code> property's HTML. Because we felt this would be a handy feature for exploring and prototyping with the <strong>Every Layout</strong> layouts, we built this feature into the custom elements. We had to specify getters and setters, and observe prop changes with the <code>attributeChangedCallback</code>. None of that is necessary when the props are just CSS, as in the method proposed here.</p> +<p>CSS is reactive by nature, in a way JavaScript can only aspire to be.</p> +<h2 id="multiple-properties">Multiple properties</h2> +<p>Of course, you can use multiple properties if you need. Maybe I'd like to display my list as a CSS Grid and control the <code>grid-template-columns</code> and <code>grid-gap</code> properties…</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token operator">&lt;</span>ul <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"grid"</span> style<span class="token operator">=</span><span class="token string">"--columns: 20ch 1fr 50px; --gap: 2rem"</span><span class="token operator">></span></span><br><span class="highlight-line"> <span class="token operator">&lt;</span>li<span class="token operator">></span>First separated item<span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span></span><br><span class="highlight-line"> <span class="token operator">&lt;</span>li<span class="token operator">></span>Second separated item<span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span></span><br><span class="highlight-line"> <span class="token operator">&lt;</span>li<span class="token operator">></span>etc<span class="token punctuation">.</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span></span><br><span class="highlight-line"><span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">></span></span></code></pre> +<p>Again, the basic CSS grid CSS file (including <code>display: grid</code>, <code>align-items: center</code> and whatever else is constant) can be cached and reused, without having to forego the props-as-inputs authoring model. The stylesheet acts just like a JavaScript module for a function, accepting (in this example) two arguments.</p> +<p>Perhaps you'd like to pass multiple props around in objects? Because that's easier to read and work with in JS? No problem: a small mapping function can convert from an object into a custom property <code>style</code> string with ease:</p> +<pre class="language-js"><code class="language-js"><span class="highlight-line"><span class="token keyword">const</span> <span class="token function-variable function">toCustomProps</span> <span class="token operator">=</span> obj <span class="token operator">=></span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>entry <span class="token operator">=></span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token keyword">return</span> <span class="token template-string"><span class="token string">`--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>entry<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>entry<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;`</span></span></span><br><span class="highlight-line"> <span class="token punctuation">}</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">''</span><span class="token punctuation">)</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token keyword">const</span> styleProps <span class="token operator">=</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> columns<span class="token punctuation">:</span> <span class="token string">'20ch 1fr 50px'</span><span class="token punctuation">,</span></span><br><span class="highlight-line"> gap<span class="token punctuation">:</span> <span class="token string">'20rem'</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token keyword">let</span> styleString <span class="token operator">=</span> <span class="token function">toCustomProps</span><span class="token punctuation">(</span>styleProps<span class="token punctuation">)</span></span><br><span class="highlight-line"><span class="token comment">// → '--columns:20ch 1fr 50px;--gap:20rem;'</span></span></code></pre> +<p>Since we are only dealing with <em>custom</em> properties, for which we can choose any name, we don't have to worry about things like the difference between CSS's <code>background-color</code> and JavaScript's equivalent <code>backgroundColor</code>.</p> +<h2 id="portability">Portability</h2> +<p>In many ways, the CSS components I'm proposing are like <strong>Every Layout's</strong> custom element layout primitives. But because they use class names instead of element definitions, they are more portable.</p> +<p>They can be applied to any element or component, inluding any semantic HTML. One of the shortcomings of custom elements is that they are unsemantic by default, meaning you have to bolt on semantics using ARIA. In previous examples, I used <code>&lt;ul&gt;</code> and <code>&lt;li&gt;</code>. Using custom elements instead, I'd have to apply the <code>list</code> and <code>listitem</code> roles.</p> +<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-list</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>list<span class="token punctuation">"</span></span><span class="token punctuation">></span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-listitem</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>listitem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-listitem</span><span class="token punctuation">></span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-listitem</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>listitem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-listitem</span><span class="token punctuation">></span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-listitem</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>listitem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-listitem</span><span class="token punctuation">></span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-list</span><span class="token punctuation">></span></span></span></code></pre> +<h2 id="limitations">Limitations</h2> +<p>I think there's a lot of potential in this approach. I like how it gives granular control over styling without encouraging the proliferation of either manually-written classes or JavaScript generated selectors.</p> +<p>The glaring, though not necessarily deal-breaking, limitation is that only CSS property values can be affected. I can't interpolate custom properties into CSS selectors. The ability to write selectors dynamically, just in CSS, would make for an interesting feature.</p> +<pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.stripes > :nth-child(var(--pattern, 'even'))</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> grey<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">.stripes.odd</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">--pattern</span><span class="token punctuation">:</span> <span class="token string">'odd'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> + + + + +