Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix minWidth and maxWidth not being respected with slack column when gte-container-slack is set #1148

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions addon/-private/column-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -763,8 +763,14 @@ export default EmberObject.extend({
return;
}

let widthConstraint = get(this, 'widthConstraint');
let isSlackModeEnabled = get(this, 'isSlackModeEnabled');

// For gte-container-slack, first try to expand columns to their maxWidth
if (widthConstraint === WIDTH_CONSTRAINT.GTE_CONTAINER_SLACK) {
this.expandColumnsToMax();
}

if (isSlackModeEnabled) {
this.updateSlackColumn();
}
Expand Down Expand Up @@ -1177,4 +1183,80 @@ export default EmberObject.extend({
this.token
);
},

/**
* Expands columns to their maxWidth when possible, while respecting container width
*/
expandColumnsToMax() {
let containerWidth = this.getContainerWidth();
let leaves = get(this, 'root.leaves').filter(node => !get(node, 'isSlack'));

// Calculate total fixed width from columns with explicit width
let flexibleColumns = leaves.filter(node => !get(node, 'column.width'));
let totalFixedWidth = leaves.reduce((sum, node) => {
let columnWidth = get(node, 'column.width');
return sum + (columnWidth || 0);
}, 0);

let remainingWidth = containerWidth - totalFixedWidth;

// First pass: ensure all columns get at least their minWidth
let totalMinWidth = 0;
flexibleColumns.forEach(node => {
let minWidth = get(node, 'minWidth');
totalMinWidth += minWidth;
});

// If we don't have space for all minWidths, set all columns to minWidth
if (remainingWidth <= totalMinWidth) {
flexibleColumns.forEach(node => {
set(node, 'width', get(node, 'minWidth'));
});
return;
}

// Keep track of columns that still need width allocated
let columnsNeedingWidth = [...flexibleColumns];
let loopCount = 0;

// Continue redistributing width until all columns are satisfied or we hit guard
while (columnsNeedingWidth.length > 0 && loopCount < LOOP_COUNT_GUARD) {
let availableWidth = remainingWidth;
columnsNeedingWidth.forEach(node => {
availableWidth -= get(node, 'minWidth');
});

let equalExtraWidth = Math.floor(availableWidth / columnsNeedingWidth.length);
let columnsToRemove = [];

// Try to set each column to minWidth + equalExtraWidth
columnsNeedingWidth.forEach(node => {
let minWidth = get(node, 'minWidth');
let maxWidth = get(node, 'maxWidth');
let targetWidth = minWidth + equalExtraWidth;
let width = Math.min(Math.max(targetWidth, minWidth), maxWidth);

// If column hits a constraint, remove it from future calculations
if (width !== targetWidth) {
columnsToRemove.push(node);
remainingWidth -= width;
}

set(node, 'width', width);
});

// If no columns hit constraints, we're done
if (columnsToRemove.length === 0) {
break;
}

// Remove columns that hit constraints and continue redistributing
columnsToRemove.forEach(node => {
let index = columnsNeedingWidth.indexOf(node);
columnsNeedingWidth.splice(index, 1);
});

loopCount++;
}
},
});
5 changes: 3 additions & 2 deletions tests/helpers/generate-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
export { configureTableGeneration, resetTableGenerationConfig, generateColumns, generateRows };

const fullTable = hbs`
<div style="height: 500px;">
<div style="height: 500px; {{if this.containerWidth (concat 'width: ' this.containerWidth 'px;')}}">
<EmberTable data-test-main-table as |t|>
<EmberThead
@api={{t}}
Expand Down Expand Up @@ -127,7 +127,7 @@ export function generateTableValues(
footerRowCount = 0,
columnCount = 10,
columnOptions,

containerWidth,
rowComponent = 'ember-tr',

...options
Expand All @@ -137,6 +137,7 @@ export function generateTableValues(
testContext.set(property, options[property]);
}
testContext.set('rowComponent', rowComponent);
testContext.set('containerWidth', containerWidth);

columns = columns || generateColumns(columnCount, columnOptions);

Expand Down
88 changes: 88 additions & 0 deletions tests/integration/components/headers/main-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,94 @@ module('Integration | header | main', function() {
assert.equal(header.logicalWidth, 100, 'header is resized');
assert.equal(slackHeader.logicalWidth, containerWidth - 100, 'slack column is expanded');
});

test('expandColumnsToMax behavior in gte-container-slack mode', async function(assert) {
await generateTable(this, {
widthConstraint: 'gte-container-slack',
columnCount: 3,
columnOptions: {
minWidth: 50,
maxWidth: 200,
},
containerWidth: 600,
});

let containerWidth = table.logicalContainerWidth;
let header1 = table.headers.objectAt(0);
let header2 = table.headers.objectAt(1);
let header3 = table.headers.objectAt(2);
let slackHeader = table.slackHeaders.objectAt(0);

// Initially, columns should expand to fill container equally to 600
assert.equal(
header1.logicalWidth + header2.logicalWidth + header3.logicalWidth,
containerWidth,
'columns fill container width initially'
);

await generateTable(this, {
widthConstraint: 'gte-container-slack',
columnCount: 3,
columnOptions: {
minWidth: 50,
},
columns: [
{ name: 'A', maxWidth: 50 },
{ name: 'B', maxWidth: 200 }, // flexible
{ name: 'C', maxWidth: 200 }, // flexible
],
containerWidth: 600,
});

assert.equal(header1.logicalWidth, 50, 'first column respects maxWidth');

// Remaining space should be distributed between other columns up to maxWidth
let remainingWidth = containerWidth - 50;
let expectedWidth = Math.min(remainingWidth / 2, 200);
assert.equal(header2.logicalWidth, expectedWidth, 'second column expands within maxWidth');
assert.equal(header3.logicalWidth, expectedWidth, 'third column expands within maxWidth');

// If there's still space after maxWidth, it should go to slack
let totalColumnWidth = 50 + expectedWidth * 2;
if (totalColumnWidth < containerWidth) {
assert.ok(slackHeader.isRendered, 'slack column appears when space available');
assert.equal(
slackHeader.logicalWidth,
containerWidth - totalColumnWidth,
'slack column fills remaining space'
);
} else {
assert.notOk(slackHeader.isRendered, 'no slack column when columns fill width');
}

// Test flexible columns without explicit width
await generateTable(this, {
widthConstraint: 'gte-container-slack',
columnCount: 3,
columnOptions: {
minWidth: 50,
},
columns: [
{ name: 'A', width: 100 },
{ name: 'B', maxWidth: 200 },
{ name: 'C', maxWidth: 200 },
],
containerWidth: 600,
});

header1 = table.headers.objectAt(0);
header2 = table.headers.objectAt(1);
header3 = table.headers.objectAt(2);

// Fixed width column should maintain its width
assert.equal(header1.logicalWidth, 100, 'fixed width column maintains width');

// Flexible columns should share remaining space equally up to maxWidth
remainingWidth = containerWidth - 100;
expectedWidth = Math.min(remainingWidth / 2, 200);
assert.equal(header2.logicalWidth, expectedWidth, 'flexible column expands within maxWidth');
assert.equal(header3.logicalWidth, expectedWidth, 'flexible column expands within maxWidth');
});
});

componentModule('subcolumns', function() {
Expand Down