Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
refactor: move always expanded items to overflow
Browse files Browse the repository at this point in the history
  • Loading branch information
johanlahti committed Sep 20, 2023
1 parent 4fb0517 commit fbc24ca
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// const flatten = (arr = []) => arr.reduce((prev, cur) => prev.concat(cur));

import { IListboxResource } from '../../hooks/types';
import { IColumn } from './interfaces';

export function getSadItem(column: IColumn) {
const sadItem = column.items?.filter((item) => item.alwaysExpanded && !item.expand)[0];
return sadItem;
}

export function moveOverflowItemToColumn(columnItems: IListboxResource[], overflowing: IListboxResource[]) {
const newOverflowing = [...overflowing];
const newColumnItems = [...columnItems];
let changed = 0;
const overflowIndex = newOverflowing.findIndex((itm: IListboxResource) => !itm.alwaysExpanded);
if (overflowIndex > -1) {
const itemToColumn = newOverflowing.splice(overflowIndex, 1)[0];
newColumnItems.splice(0, 0, itemToColumn);
changed += 1;
}
return {
changed,
columnItems: newColumnItems as IListboxResource[],
overflowing: newOverflowing as IListboxResource[],
};
}

export function moveSadItemToOverflow(sadItem: IListboxResource, parentColumn: IColumn, overflowing: IListboxResource[]) {
const sadItemIndex = parentColumn.items?.indexOf(sadItem) as number;
if (sadItemIndex === -1) {
return {
changed: 0,
parentColumnItems: parentColumn.items as IListboxResource[],
overflowing: overflowing as IListboxResource[],
};
}
parentColumn.items?.splice(sadItemIndex, 1); // remove from parent column
return {
changed: 1,
columnItems: parentColumn.items as IListboxResource[],
overflowing: [...overflowing, { ...sadItem, expand: false }] as IListboxResource[],
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const getExpandedRowHeight = (dense: boolean, isGridMode = false) => {
};

export function getListBoxMinHeight(resource: IListboxResource, outerWidth = false, asCollapsed = false) {
const { dense = false, collapseMode, dataLayout } = resource.layout.layoutOptions || {};
const { dense = false, collapseMode, dataLayout } = resource?.layout?.layoutOptions || {};

let h = 0;
if (collapseMode === 'never' && !asCollapsed) {
Expand Down Expand Up @@ -122,15 +122,15 @@ export const getListBoxMaxHeight = (item: IListboxResource) => {
* Iterate through all items in the column and summarise the height of all
* individual listboxes.
*/
export function estimateColumnHeight(column: IColumn) {
export function estimateColumnHeight(column: IColumn, atMinSize = false) {
let totHeight = 2;
column.items?.forEach((item) => {
const {
expand = false, fullyExpanded = false, height,
} = item;

if (item.neverExpanded) {
totHeight += getListBoxMinHeight(item);
if (item.neverExpanded || atMinSize) {
totHeight += getListBoxMinHeight(item, false, atMinSize);
} else if (expand || item.alwaysExpanded) {
if (fullyExpanded) {
totHeight += getListBoxMaxHeight(item);
Expand All @@ -145,6 +145,10 @@ export function estimateColumnHeight(column: IColumn) {
}
totHeight += ITEM_SPACING;
});
if (column.hiddenItems) {
const AS_COLLAPSED = true;
totHeight += getListBoxMinHeight({} as IListboxResource, true, AS_COLLAPSED);
}
return totHeight;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IListboxResource, ListboxResourcesArr } from '../../hooks/types';
import { getSadItem, moveOverflowItemToColumn, moveSadItemToOverflow } from './distribute-always-expanded';
import {
countListBoxes,
doAllFit,
Expand Down Expand Up @@ -97,17 +98,9 @@ export function balanceColumns(size: ISize, columns: IColumn[], resources: IList
const extraItems = index < collapsedItems % collapsed.length ? 1 : 0;
const itemCount = Math.floor(collapsedItems / collapsed.length) + extraItems;

const lastColumn = columns[columns.length - 1];

const isLastColumn = column === lastColumn;

// However, we should only push items to first column if it can fit more listboxes…
if (!(isLastColumn && column.hiddenItems && columns.length > 1) && size.height >= estimateColumnHeight({ ...column, itemCount, items: resources.slice(index, index + itemCount) })) {
if (size.height >= estimateColumnHeight({ ...column, itemCount, items: resources.slice(index, index + itemCount) })) {
column.itemCount = itemCount;
} else {
// …and if it does not fit, we can move it to the last column and make items hidden.
lastColumn.itemCount = 0;
lastColumn.hiddenItems = true;
}
});
}
Expand All @@ -131,11 +124,14 @@ export function assignListboxesToColumns(columns: IColumn[], resources: IListbox
}

const setFullyExpanded = (item: IListboxResource) => {
if (!item.expand) {
return;
}
item.fullyExpanded = true;
item.height = `${getListBoxMaxHeight(item)}px`;
};

function expandUntilFull(sortedItems: IListboxResource[] | undefined, initialLeftOverHeight: number, isSingleGridLayout = false) {
function expandUntilFull(sortedItems: IListboxResource[] | undefined, initialLeftOverHeight: number, hiddenItems = false) {
if (!sortedItems) return;
let i;
let item;
Expand All @@ -148,10 +144,11 @@ function expandUntilFull(sortedItems: IListboxResource[] | undefined, initialLef
if (item.cardinal && !item.fullyExpanded) {
// 1. Calculate column height without the target item, then
// 2. Subtract the target's expanded height to see if it fits.
leftOverHeight = initialLeftOverHeight - estimateColumnHeight({ items: [...sortedItems.slice(0, i), ...sortedItems.slice(i + 1)] });
const itemsSliced = [...sortedItems.slice(0, i), ...sortedItems.slice(i + 1)];
leftOverHeight = initialLeftOverHeight - estimateColumnHeight({ items: itemsSliced, hiddenItems });
expandedHeight = getListBoxMaxHeight(item) + ITEM_SPACING;
itemFits = leftOverHeight >= expandedHeight;
item.fits = itemFits;
item.fits = itemFits; // fits fully expanded
if (itemFits && !item.neverExpanded) {
item.expand = true;
setFullyExpanded(item);
Expand All @@ -161,12 +158,12 @@ function expandUntilFull(sortedItems: IListboxResource[] | undefined, initialLef
// See if listbox fits as expanded and if not – as collapsed.
const expandedMinHeight = getListBoxMinHeight(item, false, false);
const collapsedMinHeight = getListBoxMinHeight(item, false, true);
const expandedHeightLimit = getExpandedHeightLimit({ alwaysExpanded: item.alwaysExpanded, hasHeader: item.hasHeader, isSingleGridLayout });
const expandedHeightLimit = getExpandedHeightLimit({ alwaysExpanded: item.alwaysExpanded, hasHeader: item.hasHeader, isSingleGridLayout: false });
const fitsAsExpanded = leftOverHeight - expandedMinHeight >= expandedHeightLimit;
const fitsAsCollapsed = leftOverHeight >= collapsedMinHeight;
if (fitsAsExpanded) {
if (fitsAsExpanded || (item.alwaysExpanded && leftOverHeight >= expandedMinHeight)) {
item.expand = true;
item.height = item.alwaysExpanded && isSingleGridLayout ? DEFAULT_CSS_HEIGHT : `${leftOverHeight}px`;
item.height = `${leftOverHeight}px`;
} else if (fitsAsCollapsed) {
item.expand = false;
}
Expand All @@ -179,16 +176,24 @@ function setDefaultItemSettings({
item, isSingleColumn, isSingleGridLayout, innerHeight, isSmallDevice,
}: { item: IListboxResource, isSingleColumn: boolean, isSingleGridLayout: boolean, innerHeight: number, isSmallDevice: boolean }) {
// Set default value for expand mode, based on circumstances.
let expand = false;
item.fullyExpanded = false;
if (item.neverExpanded) {
item.expand = false;
expand = false;
} else {
const expandedHeightLimit = getExpandedHeightLimit({ alwaysExpanded: item.alwaysExpanded, hasHeader: item.hasHeader, isSingleGridLayout });
const canFitSingleExpanded = !isSmallDevice && isSingleColumn && innerHeight - getListBoxMinHeight(item) >= expandedHeightLimit;
item.expand = canFitSingleExpanded;
if (!isSmallDevice && isSingleColumn) {
if (item.alwaysExpanded) {
expand = innerHeight - getListBoxMinHeight(item) >= expandedHeightLimit;
} else {
expand = innerHeight >= expandedHeightLimit;
}
}
if (item.alwaysExpanded) {
item.height = canFitSingleExpanded ? DEFAULT_CSS_HEIGHT : `${getListBoxMinHeight(item)}px`;
item.height = expand ? DEFAULT_CSS_HEIGHT : `${getListBoxMinHeight(item)}px`;
}
}
item.expand = expand;
}

function sortColumnItems(columnItems: IListboxResource[]) {
Expand All @@ -211,7 +216,7 @@ function sortColumnItems(columnItems: IListboxResource[]) {
export const calculateExpandPriority = (columns: IColumn[], size: ISize, expandProps: ExpandProps, isSmallDevice: boolean) => {
let allExpandedItems = <IListboxResource[]>[];

const innerHeight = size.height; // - ITEM_SPACING;
const innerHeight = size.height;
let leftOverHeight = 0;

columns.forEach((column: IColumn) => {
Expand All @@ -232,7 +237,7 @@ export const calculateExpandPriority = (columns: IColumn[], size: ISize, expandP

if (column.expand) {
if ((sortedItems.length ?? 0) > 1) {
expandUntilFull(sortedItems, innerHeight);
expandUntilFull(sortedItems, innerHeight, column.hiddenItems);
} else if (expandProps.isSingleGridLayout) {
// Ensure we set expand to true in this corner case (product designer request).
const hasRoom = haveRoomToExpandOne(size, sortedItems[0], isSmallDevice, expandProps);
Expand Down Expand Up @@ -271,6 +276,43 @@ export const calculateExpandPriority = (columns: IColumn[], size: ISize, expandP
return { columns, expandedItemsCount: allExpandedItems.length };
};

export function moveAlwaysExpandedToOverflow(columns: IColumn[], overflowing: IListboxResource[]) {
const newColumns = [...columns];
const newOverflowing = [...overflowing];

const isSingleColumn = columns.length === 1;
const overflowColumn = newColumns[newColumns.length - 1];
const nonOverflowColumns = isSingleColumn ? [overflowColumn] : newColumns.slice(0, newColumns.length - 1);

const sadBecameHappy = nonOverflowColumns.some((column) => {
const sadItem = getSadItem(column);
if (sadItem) {
let changedItems = moveSadItemToOverflow(sadItem, column, newOverflowing);
// Move one overflow item which is not alwaysExpanded (if any) to the
// column where we previously found the sad item.
changedItems = moveOverflowItemToColumn(changedItems.columnItems || [], changedItems.overflowing);
column.items = changedItems.columnItems;
newOverflowing.length = 0;
newOverflowing.push(...changedItems.overflowing);
column.itemCount = column.items?.length ?? 0;
return true; // this breaks the loop
}
return false;
});

if (newOverflowing.length > overflowing.length) {
if (!overflowColumn.hiddenItems) {
overflowColumn.hiddenItems = true;
}
}

return {
overflowing: newOverflowing,
columns: newColumns,
wasMoved: sadBecameHappy,
};
}

export const setDefaultValues = (resources: IListboxResource[], isSmallDevice = false) => resources.map((resource: IListboxResource) => {
const { collapseMode = 'auto' } = resource.layout.layoutOptions || {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import getWidthHeight from './get-size';
import {
setDefaultValues, balanceColumns, calculateColumns, calculateExpandPriority, assignListboxesToColumns,
hasHeader,
moveAlwaysExpandedToOverflow,
} from './distribute-resources';
import { ExpandProps, IColumn, ISize } from './interfaces';
import { IEnv } from '../../types/types';
Expand Down Expand Up @@ -55,12 +56,35 @@ export default function useHandleResize({
hasHeader: hasHeader(resources[0]),
alwaysExpanded: resources[0].layout?.layoutOptions?.collapseMode === 'never',
};
const calculatedColumns = calculateColumns(size, [], isSmallDevice, expandProps, resources);
const balancedColumns = balanceColumns(size, calculatedColumns, resources, isSmallDevice, expandProps);
const { columns: mergedColumnsAndResources, overflowing } = assignListboxesToColumns(balancedColumns, resources, isSmallDevice);

let columnsTemp;
let overflowing;
let expandedItemsCount = 0;

columnsTemp = calculateColumns(size, [], isSmallDevice, expandProps, resources);
columnsTemp = balanceColumns(size, columnsTemp, resources, isSmallDevice, expandProps);
({ columns: columnsTemp, overflowing } = assignListboxesToColumns(columnsTemp, resources, isSmallDevice));
({ columns: columnsTemp, expandedItemsCount } = calculateExpandPriority(columnsTemp, size, expandProps, isSmallDevice));

// Move listboxes which should always be expanded, but do not have room to expand; into the overflow dropdown.
// This is done iteratively by:
// 1. Moving one listbox to overflow
// 2. Re-evaluating the expand capabilities of remaining listboxes and expand if possible
// 3. If nothing can be moved, break the loop (all always expanded listboxes have either moved or expanded).
let wasMoved;
for (let i = 0, len = resources.length; i < len; i++) {
({ columns: columnsTemp, overflowing, wasMoved } = moveAlwaysExpandedToOverflow(columnsTemp, overflowing));
columnsTemp = columnsTemp.filter((column) => !(column.itemCount === 0 && !column.hiddenItems)); // remove empty columns resulting from moving listboxes
({ columns: columnsTemp, expandedItemsCount } = calculateExpandPriority(columnsTemp, size, expandProps, isSmallDevice));
if (!wasMoved) {
break;
}
}
columnsTemp = balanceColumns(size, columnsTemp, resources, isSmallDevice, expandProps);
({ columns: columnsTemp, expandedItemsCount } = calculateExpandPriority(columnsTemp, size, expandProps, isSmallDevice));

setOverflowingResources(overflowing);
const { columns: expandedAndCollapsedColumns, expandedItemsCount } = calculateExpandPriority(mergedColumnsAndResources, size, expandProps, isSmallDevice);
setColumns(expandedAndCollapsedColumns);
setColumns(columnsTemp);
prepareRenderTracker(expandedItemsCount, renderTracker);
};

Expand Down

0 comments on commit fbc24ca

Please sign in to comment.