github.com/hernad/nomad@v1.6.112/ui/app/components/flex-masonry.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  import Component from '@glimmer/component';
     7  import { tracked } from '@glimmer/tracking';
     8  import { next } from '@ember/runloop';
     9  import { action } from '@ember/object';
    10  import { minIndex, max } from 'd3-array';
    11  
    12  export default class FlexMasonry extends Component {
    13    @tracked element = null;
    14  
    15    @action
    16    captureElement(element) {
    17      this.element = element;
    18    }
    19  
    20    @action
    21    reflow() {
    22      next(() => {
    23        // There's nothing to do if there is no element
    24        if (!this.element) return;
    25  
    26        const items = this.element.querySelectorAll(
    27          ':scope > .flex-masonry-item'
    28        );
    29  
    30        // Clear out specified order and flex-basis values in case this was once a multi-column layout
    31        if (this.args.columns === 1 || !this.args.columns) {
    32          for (let item of items) {
    33            item.style.flexBasis = null;
    34            item.style.order = null;
    35          }
    36          this.element.style.maxHeight = null;
    37          return;
    38        }
    39  
    40        const columns = new Array(this.args.columns).fill(null).map(() => ({
    41          height: 0,
    42          elements: [],
    43        }));
    44  
    45        // First pass: assign each element to a column based on the running heights of each column
    46        for (let item of items) {
    47          const styles = window.getComputedStyle(item);
    48          const marginTop = parseFloat(styles.marginTop);
    49          const marginBottom = parseFloat(styles.marginBottom);
    50          const height = item.clientHeight;
    51  
    52          // Pick the shortest column accounting for margins
    53          const column = columns[minIndex(columns, (c) => c.height)];
    54  
    55          // Add the new element's height to the column height
    56          column.height += marginTop + height + marginBottom;
    57          column.elements.push(item);
    58        }
    59  
    60        // Second pass: assign an order to each element based on their column and position in the column
    61        columns
    62          .mapBy('elements')
    63          .flat()
    64          .forEach((dc, index) => {
    65            dc.style.order = index;
    66          });
    67  
    68        // Guarantee column wrapping as predicted (if the first item of a column is shorter than the difference
    69        // beteen the height of the column and the previous column, then flexbox will naturally place the first
    70        // item at the end of the previous column).
    71        columns.forEach((column, index) => {
    72          const nextHeight =
    73            index < columns.length - 1 ? columns[index + 1].height : 0;
    74          const item = column.elements.lastObject;
    75          if (item) {
    76            item.style.flexBasis =
    77              item.clientHeight + Math.max(0, nextHeight - column.height) + 'px';
    78          }
    79        });
    80  
    81        // Set the max height of the container to the height of the tallest column
    82        this.element.style.maxHeight = max(columns.mapBy('height')) + 1 + 'px';
    83      });
    84    }
    85  }