github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/components/flex-masonry.js (about)

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