chromium/chrome/browser/resources/commerce/product_specifications/loading_state.ts

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'chrome://resources/cr_elements/cr_loading_gradient/cr_loading_gradient.js';

import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
import type {PropertyValues} from 'chrome://resources/lit/v3_0/lit.rollup.js';

import {getCss} from './loading_state.css.js';
import {getHtml} from './loading_state.html.js';

const GAP_X = 8;
const BODY_INDENT = 8;
const COLUMN_WIDTH = 220;

interface Rect {
  y: number;
  width: number;
  height: number;
  rx: number;
  firstColumnOnly: boolean;
}

function getX(index: number, isHeader: boolean): number {
  if (index === 0) {
    return 0;
  }
  const headerStart = index * (COLUMN_WIDTH + GAP_X);
  return isHeader ? headerStart : headerStart + BODY_INDENT;
}

export interface LoadingStateElement {
  $: {
    clipPath: HTMLElement,
    loadingContainer: HTMLElement,
    svg: HTMLElement,
  };
}

export class LoadingStateElement extends CrLitElement {
  static get is() {
    return 'loading-state';
  }

  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  static override get properties() {
    return {
      columnCount: {type: Number},
    };
  }

  columnCount: number = 0;
  private rects_: Rect[] = [
    {y: 0, width: COLUMN_WIDTH, height: 38, rx: 8, firstColumnOnly: false},
    {y: 44, width: COLUMN_WIDTH, height: 130, rx: 9.6, firstColumnOnly: false},
    {y: 215, width: 91.2, height: 16.8, rx: 4.8, firstColumnOnly: false},
    {y: 279.2, width: 118, height: 20, rx: 4.8, firstColumnOnly: false},
    {y: 344, width: 194, height: 17, rx: 4.8, firstColumnOnly: false},
    {y: 369.2, width: 116, height: 17, rx: 4.8, firstColumnOnly: false},
    {y: 431, width: 178, height: 17, rx: 4.8, firstColumnOnly: false},
    {y: 493, width: 141, height: 17, rx: 4.8, firstColumnOnly: false},
    {y: 190, width: 79, height: 17, rx: 4.8, firstColumnOnly: true},
    {y: 254.2, width: 79, height: 17, rx: 4.8, firstColumnOnly: true},
    {y: 319.2, width: 79, height: 17, rx: 4.8, firstColumnOnly: true},
    {y: 406.2, width: 79, height: 17, rx: 4.8, firstColumnOnly: true},
    {y: 468.2, width: 79, height: 17, rx: 4.8, firstColumnOnly: true},
  ];

  override updated(changedProperties: PropertyValues<this>) {
    super.updated(changedProperties);

    if (changedProperties.has('columnCount')) {
      // Limit the amount of columns that can show in the loading state.
      this.columnCount = Math.min(5, this.columnCount);
      this.generateLoadingUi_();
    }
  }

  // Generate the contents of the SVG here, rather than in the HTML. We can't
  // use .map or dynamic bindings in the HTML because SVG parts need to be
  // created with createElementNS to properly pass in attributes.
  private generateLoadingUi_() {
    while (this.$.clipPath.firstChild) {
      this.$.clipPath.removeChild(this.$.clipPath.lastChild!);
    }
    for (let columnIndex = 0; columnIndex < this.columnCount; columnIndex++) {
      this.rects_.forEach((rect, rectIndex) => {
        if (rect.firstColumnOnly && columnIndex > 0) {
          return;
        }
        const rectElement =
            document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rectElement.setAttribute('x', `${getX(columnIndex, rectIndex <= 1)}`);
        rectElement.setAttribute('y', `${rect.y}`);
        rectElement.setAttribute('width', `${rect.width}`);
        rectElement.setAttribute('height', `${rect.height}`);
        rectElement.setAttribute('rx', `${rect.rx}`);
        this.$.clipPath.appendChild(rectElement);
      });
    }
    const svgWidth = this.columnCount === 0 ?
        0 :
        this.columnCount * (COLUMN_WIDTH + GAP_X) - GAP_X;
    this.$.svg.setAttribute('width', `${svgWidth}`);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'loading-state': LoadingStateElement;
  }
}

customElements.define(LoadingStateElement.is, LoadingStateElement);