mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-18 03:20:27 -06:00
179 lines
5.6 KiB
TypeScript
179 lines
5.6 KiB
TypeScript
import { LayoutContext } from '../layouting';
|
|
import { ComponentId } from '../dataModels';
|
|
import { ComponentBase, ComponentState } from './componentBase';
|
|
|
|
export type FlowState = ComponentState & {
|
|
_type_: 'FlowContainer-builtin';
|
|
children?: ComponentId[];
|
|
row_spacing?: number;
|
|
column_spacing?: number;
|
|
justify?: 'left' | 'center' | 'right' | 'justified' | 'grow';
|
|
};
|
|
|
|
export class FlowComponent extends ComponentBase {
|
|
state: Required<FlowState>;
|
|
|
|
createElement(): HTMLElement {
|
|
let element = document.createElement('div');
|
|
element.classList.add('rio-flow-container');
|
|
return element;
|
|
}
|
|
|
|
updateElement(
|
|
deltaState: FlowState,
|
|
latentComponents: Set<ComponentBase>
|
|
): void {
|
|
// Update the children
|
|
this.replaceChildren(latentComponents, deltaState.children);
|
|
|
|
// Regardless of whether the children or the spacing changed, a
|
|
// re-layout is required
|
|
this.makeLayoutDirty();
|
|
}
|
|
|
|
updateNaturalWidth(ctx: LayoutContext): void {
|
|
this.naturalWidth = 0;
|
|
|
|
for (let child of this.children) {
|
|
this.naturalWidth = Math.max(
|
|
this.naturalWidth,
|
|
child.requestedWidth
|
|
);
|
|
}
|
|
}
|
|
|
|
private processRow(row: ComponentBase[], rowWidth: number): void {
|
|
// Determine how to use the additional space
|
|
let spaceToTheLeft, spaceToGrow, spaceForGap;
|
|
let additionalWidth = this.allocatedWidth - rowWidth;
|
|
|
|
if (this.state.justify === 'left') {
|
|
spaceToTheLeft = 0;
|
|
spaceToGrow = 0;
|
|
spaceForGap = 0;
|
|
} else if (this.state.justify === 'center') {
|
|
spaceToTheLeft = additionalWidth * 0.5;
|
|
spaceToGrow = 0;
|
|
spaceForGap = 0;
|
|
} else if (this.state.justify === 'right') {
|
|
spaceToTheLeft = additionalWidth;
|
|
spaceToGrow = 0;
|
|
spaceForGap = 0;
|
|
} else if (this.state.justify === 'justified') {
|
|
if (row.length === 1) {
|
|
spaceToTheLeft = additionalWidth * 0.5;
|
|
spaceToGrow = 0;
|
|
spaceForGap = 0;
|
|
} else {
|
|
spaceToTheLeft = 0;
|
|
spaceToGrow = 0;
|
|
spaceForGap = additionalWidth / (row.length - 1);
|
|
}
|
|
} else {
|
|
// 'grow'
|
|
spaceToTheLeft = 0;
|
|
spaceToGrow = additionalWidth / row.length;
|
|
spaceForGap = spaceToGrow;
|
|
}
|
|
|
|
// Assign the positions
|
|
for (let ii = 0; ii < row.length; ii++) {
|
|
let child = row[ii];
|
|
|
|
// Assign the position
|
|
let left =
|
|
(child as any)._flowContainer_posX +
|
|
spaceToTheLeft +
|
|
ii * spaceForGap;
|
|
child.element.style.left = `${left}rem`;
|
|
|
|
// Assign the width
|
|
child.allocatedWidth = child.requestedWidth + spaceToGrow;
|
|
}
|
|
}
|
|
|
|
updateAllocatedWidth(ctx: LayoutContext): void {
|
|
// Allow the code below to assume there's at least one child
|
|
if (this.children.size === 0) {
|
|
return;
|
|
}
|
|
|
|
// Divide the children into rows
|
|
//
|
|
// For performance and simplicity, store the row index and x positions
|
|
// right inside the children.
|
|
let posX = 0;
|
|
let rowIndex = 0;
|
|
let currentRow: ComponentBase[] = [];
|
|
|
|
for (let child of this.children) {
|
|
// If the child is too wide, move on to the next row
|
|
if (posX + child.requestedWidth > this.allocatedWidth) {
|
|
this.processRow(currentRow, posX - this.state.column_spacing);
|
|
|
|
posX = 0;
|
|
++rowIndex;
|
|
currentRow = [];
|
|
}
|
|
|
|
// Assign the child to the row
|
|
(child as any)._flowContainer_rowIndex = rowIndex;
|
|
(child as any)._flowContainer_posX = posX;
|
|
currentRow.push(child);
|
|
|
|
// Advance the position
|
|
posX += child.requestedWidth + this.state.column_spacing;
|
|
}
|
|
|
|
// Process the final row
|
|
this.processRow(currentRow, posX - this.state.column_spacing);
|
|
}
|
|
|
|
updateNaturalHeight(ctx: LayoutContext): void {
|
|
// Allow the code below to assume there's at least one child
|
|
if (this.children.size === 0) {
|
|
this.naturalHeight = 0;
|
|
return;
|
|
}
|
|
|
|
// Find the tallest child for each row
|
|
let rowHeights: number[] = [];
|
|
|
|
for (let child of this.children) {
|
|
let rowIndex = (child as any)._flowContainer_rowIndex;
|
|
let childHeight = child.requestedHeight;
|
|
|
|
if (rowHeights[rowIndex] === undefined) {
|
|
rowHeights[rowIndex] = childHeight;
|
|
} else {
|
|
rowHeights[rowIndex] = Math.max(
|
|
rowHeights[rowIndex],
|
|
childHeight
|
|
);
|
|
}
|
|
}
|
|
|
|
// Determine the total height
|
|
let rowTops: number[] = [0];
|
|
|
|
for (let ii = 0; ii < rowHeights.length; ii++) {
|
|
rowTops.push(rowTops[ii] + rowHeights[ii] + this.state.row_spacing);
|
|
}
|
|
|
|
this.naturalHeight = rowTops[rowTops.length - 1];
|
|
|
|
// Position the children
|
|
for (let child of this.children) {
|
|
let rowIndex = (child as any)._flowContainer_rowIndex;
|
|
let rowTop = rowTops[rowIndex];
|
|
let childHeight = rowHeights[rowIndex];
|
|
|
|
child.element.style.top = `${rowTop}rem`;
|
|
// child.element.style.height = `${childHeight}rem`;
|
|
child.allocatedHeight = childHeight;
|
|
}
|
|
}
|
|
|
|
updateAllocatedHeight(ctx: LayoutContext): void {}
|
|
}
|