简体   繁体   中英

How to translate plain CSS into web component and correctly apply child selector?

I have the code also in a Pen at https://codepen.io/veksi/pen/xxGpXWM?editors=1010 but reproduced here to preserve it for posterity.

I have a piece of code that works when using CSS without using web components (or lit-html in this case, but shouldn't matter). However, there is some sort of a mental block since I struggle to tranform the "plain CSS" version into one that works with web components. I think the problem is with :host and ::slotted selectors and using them, but it puzzles me as to what exactly is the problem. Can someone help and point the way with the right selector?

Here's the code:


<html>
  <head>      
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      .sidebar-test {
        overflow: hidden;
      }

      .sidebar-test > * {
        display: flex;
        flex-wrap: wrap;
        margin: calc(var(10px) / 2 * -1);               
      }

      .sidebar-test > * > * {
        margin: calc(var(10px) / 2);
        flex-basis: 1;
        flex-grow: 1;
      }

      .sidebar-test > * > :first-child {
        flex-basis: 0;
        flex-grow: 999;
        min-width: calc(50% - 10px);
      }
    </style>
  </head>
  <body style="width: 95vw; border-style: solid;">
     <div style="width: 80vw; height:40vh; border-style: dotted;">
      <p>Testing sidebar without web compoment</p>
      <div class="sidebar-test">
        <form>
          <input type="text" aria-label="Search" />
          <button type="submit">Some longer search text to realign on small views</button>
        </form>
      </div>

       <div class="sidebar-test">
        <div>
          <p style="background-color: yellow">Strawberries.</p>
          <img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
        </div>
      </div>
    </div>

    <div style="width: 80vw; height:40vh; border-style: dotted;">
      <p>Testing sidebar1...</p>
      <test-sidebar1 space="10px" side="right" contentMin="55.0%" style="border-style: dashed;">
        <form>
          <input type="text" aria-label="Search" />
          <button type="submit">Some longer search text to realign on small views</button>
        </form>
      </test-sidebar1>

      <test-sidebar1 space="10px" side="left" contentMin="55.0%" style="border-style: dashed;">
        <div>
          <p style="background-color: yellow">Strawberries.</p>
          <img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
        </div>
      </test-sidebar1>
    </div>

  <div style="width: 80vw; height:40vh; border-style: dotted;margin-top:2rem;">
    <p>Testing sidebar2...</p>
      <test-sidebar2 space="10px" side="right" contentMin="55.0%" style="border-style: dashed;">
        <form>
          <input type="text" aria-label="Search" />
          <button type="submit">Some longer search text to realign on small views</button>
        </form>
      </test-sidebar2>

      <test-sidebar2 space="10px" side="left" contentMin="55.0%" style="border-style: dashed;">
        <div>
          <p style="background-color: yellow">Strawberries.</p>
          <img src="https://images.unsplash.com/photo-1495570689269-d883b1224443" width="100" height="100">
        </div>
      </test-sidebar2>
    </div>
  </body>
</html>

import { LitElement, css, html } from 'https://unpkg.com/lit-element?module';


/* This seem to work almost while version 2 does not at all.
The problem in this version is alignment. The 'Search' button
is not on the right side and the search field occupying the
free space accordingly. For some reason flex-basis and
flex-grow are not matched by the CSS selector...

Basically trying to recreate .sidebar-test as given in the document CSS, but wrapped into an element.
*/
export class Sidebar1 extends LitElement {
  static get styles() {
    return css`    
    :host {
      overflow: hidden;
    }

    :host ::slotted(*) {
      display: flex;
      flex-wrap: wrap;
    }

    :host ::slotted(*) > ::slotted(*) {
      flex-grow: 1;
    }    
    `;
  }

  get sideStyle() {
    return html`
      <style>
        :host ::slotted(*) { margin: calc(${this.space} / 2 * -1); ${this.noStretch ? 'align-items: flex-start;' : ''} }

        :host ::slotted(*) > ::slotted(*) {  margin: calc(${this.space} / 2); ${this.sideWidth ? `flex-basis: ${this.sideWidth};` : ''} }

        :host ::slotted(*) > ${this.side !== 'left' ? `::slotted(*:first-child)` : `::slotted(*:last-child)`} {
            flex-basis: 0;
            flex-grow: 999;
            min-width: calc(${this.contentMin} - ${this.space});
          }
      </style>
    `;
  }

  static get properties() {
    return {
      side: { type: String },
      sideWidth: { type: String },
      contentMin: { type: String },
      space: { type: String },
      x: { type: String },
      noStretch: { type: Boolean, reflect: true, attribute: true }
    };
  }

  constructor() {
    super();

    this.side = "left";
    this.contentMin = "50%";

    if (this.children[0].children.length != 2) {
      console.error(`Should have exactly two children..`);
    }
  }

  render() {
    if (!this.contentMin.includes('%')) {
      console.warn('Minimum content should be in percentages to avoid overflows.');
    }

    return html`${this.sideStyle}<slot></slot>`;
  }
}

customElements.define('test-sidebar1', Sidebar1);


/* Compared to Sidebar1 there's a problem with layout. As if sideStyle wouldn't be applied correctly.*/
export class Sidebar2 extends LitElement {
  static get styles() {
    return css`
    :host {
      --sidebarSpace: 1rem;
      --sidebarContentMin: 50%;
      --sidebarWidth: 30ch;

      overflow: hidden;
    }

    :host > ::slotted(*) {
      display: flex;
      flex-wrap: wrap;

      margin: calc(var(--sidebarSpace) / 2 * -1);
    }

    :host([noStretch]) {
      :host > ::slotted(*) {
        align-items: flex-start;
      }
    }

    :host > ::slotted(*) > ::slotted(*) {
      margin: calc(var(--sidebarSpace) / 2);
      flex-grow: 1;
    }

    :host > ::slotted(*) > ::slotted(*:first-child) {
      flex-basis: 0;
      flex-grow: 999;
      min-width: calc(var(--sidebarContentMin) - var(--sidebarSpace));
    }
    `;
  }

  get sideStyle() {
    return html`
      <style>
         :host > ::slotted(*) > ::slotted(*) {
          ${this.sideWidth ? 'flex-basis: var(--sidebarWidth)' : ''}
        }

        :host > ::slotted(*) > ${this.side !== 'left' ? '::slotted(*:first-child)' : '::slotted(*:last-child)'} {
          flex-basis: 0;
          flex-grow: 999;
          min-width: calc(var(--sidebarContentMin) - var(--sidebarSpace));
        }
      </style>
    `;
  }

  static get properties() {
    return {
      side: { type: String },
      sideWidth: { type: String },
      contentMin: { type: String },
      space: { type: String },
      x: { type: String },
      noStretch: { type: Boolean, reflect: true, attribute: true }
    };
  }

  updated(changedProperties) {
    if (changedProperties.has("space")) {
      this.style.setProperty("--sidebarSpace", this.space);
    } else if (changedProperties.has("contentMin")) {
      this.style.setProperty("--sidebarContentMin", this.contentMin);
    } else if (changedProperties.has("sideWidth")) {
      this.style.setProperty("--sidebarWidth", this.sideWidth);
    }
  }

  constructor() {
    super();

    this.side = "left";
    this.contentMin = "50%";

    if (this.children[0].children.length != 2) {
      console.error(`Should have exactly two children..`);
    }
  }

  render() {
    if (!this.contentMin.includes('%')) {
      console.warn('Minimum content should be in percentages to avoid overflows.');
    }

    return html`${this.sideStyle}<slot></slot>`;
  }
}

customElements.define('test-sidebar2', Sidebar2);

The issue seem to be that compound selectors are not supported in shadow DOM. Relevant discussion with some solutions specifically regarding lit-html can be had at https://github.com/Polymer/lit-element/issues/42 .

If you go to https://lit-element.polymer-project.org/guide/styles#style-the-components-children and read down the page a bit, you will find the following:

Note that only direct slotted children can be styled with ::slotted().

 <my-element> <div>Stylable with ::slotted()</div> </my-element> <my-element> <div><p>Not stylable with ::slotted()</p></div> </my-element>

so I don't think selectors like :host > ::slotted(*) > ::slotted(*) are going to work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM