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 }