@hashicorp/design-system-components
Version:
Helios Design System Components
270 lines (267 loc) • 10.3 kB
JavaScript
import Component from '@glimmer/component';
import { modifier } from 'ember-modifier';
import { TrackedMap } from 'tracked-built-ins';
import { hash } from '@ember/helper';
import { isPixelSize, pixelToNumber } from '../utils.js';
import { precompileTemplate } from '@ember/template-compilation';
import { setComponentTemplate } from '@ember/component';
const DEFAULT_WIDTH = '1fr'; // default to '1fr' to allow flexible width
const DEFAULT_MIN_WIDTH = '150px';
const DEFAULT_MAX_WIDTH = '800px';
class HdsAdvancedTableColumnManagerWidth extends Component {
_columnWidths = new TrackedMap();
_originalColumnWidths = new TrackedMap();
_transientColumnWidths = new TrackedMap();
_columnDebts = new TrackedMap();
syncWidthValues = modifier(() => {
const {
columns
} = this.args;
for (const column of columns) {
this._columnWidths.set(column.key, column.width ?? DEFAULT_WIDTH);
this._originalColumnWidths.set(column.key, column.width ?? DEFAULT_WIDTH);
}
});
get gridTemplateColumns() {
const {
isSelectable,
orderedColumns
} = this.args;
let style = isSelectable ? 'min-content ' : '';
for (const col of orderedColumns) {
const appliedWidth = this.getAppliedWidth(col.key);
style += ` ${appliedWidth}`;
}
return style;
}
getAppliedWidth = columnKey => {
const width = this._columnWidths.get(columnKey);
const transientWidth = this._transientColumnWidths.get(columnKey);
return transientWidth ?? width ?? `${this.args.thElements.get(columnKey)?.offsetWidth ?? 0}px`;
};
getSiblingColumnKeys = columnKey => {
if (columnKey === null) {
return {};
}
const columnIndex = this.args.columnOrder.indexOf(columnKey);
if (columnIndex === -1) {
return {};
}
return {
previous: columnIndex === 0 ? undefined : this.args.columnOrder[columnIndex - 1],
next: columnIndex === this.args.columnOrder.length - 1 ? undefined : this.args.columnOrder[columnIndex + 1]
};
};
_getPxWidth(key) {
const width = this._columnWidths.get(key);
if (width !== undefined && isPixelSize(width)) {
return pixelToNumber(width);
} else {
return this.args.thElements.get(key)?.offsetWidth ?? 0;
}
}
_getPxMinWidth(key) {
const column = this.args.getColumnByKey(key);
const minWidth = column?.minWidth ?? DEFAULT_MIN_WIDTH;
return isPixelSize(minWidth) ? pixelToNumber(minWidth) : 0;
}
applyTransientWidth = columnKey => {
if (columnKey == undefined) {
return;
}
const transientWidth = this._transientColumnWidths.get(columnKey);
this._columnWidths.set(columnKey, transientWidth);
};
setTransientColumnWidths = (options = {}) => {
const roundValues = options.roundValues ?? false;
this._columnWidths.forEach((width, key) => {
let _width;
if (width !== undefined && isPixelSize(width)) {
_width = pixelToNumber(width);
} else {
_width = this.args.thElements.get(key)?.offsetWidth ?? 0;
}
this._transientColumnWidths.set(key, `${roundValues ? Math.round(_width) : _width}px`);
});
};
resetTransientColumnWidths = () => {
this._transientColumnWidths.clear();
};
setTransientColumnWidth = (columnKey, width, clamped = true) => {
const column = this.args.getColumnByKey(columnKey);
if (column === undefined) {
return;
}
if (clamped) {
const {
minWidth,
maxWidth
} = column;
const minWidthInPixels = minWidth === undefined ? 1 : pixelToNumber(minWidth);
const maxWidthInPixels = maxWidth === undefined ? Infinity : pixelToNumber(maxWidth);
const transientColumnWidthInPixels = Math.min(Math.max(pixelToNumber(width), minWidthInPixels), maxWidthInPixels);
this._transientColumnWidths.set(columnKey, `${transientColumnWidthInPixels}px`);
} else {
this._transientColumnWidths.set(columnKey, width);
}
};
updateResizeDebt = (columnKey, delta) => {
if (delta === 0) {
return;
}
const {
next: nextColumnKey
} = this.getSiblingColumnKeys(columnKey);
if (nextColumnKey === undefined) {
return;
}
// determine borrower and lender
const borrowerKey = delta > 0 ? columnKey : nextColumnKey;
const lenderKey = delta > 0 ? nextColumnKey : columnKey;
let amount = Math.abs(delta);
// check if lender has existing debt to borrower
const lenderDebts = this._columnDebts.get(lenderKey) ?? {};
const existingDebt = lenderDebts[borrowerKey] ?? 0;
if (existingDebt > 0) {
const paymentAmount = Math.min(amount, existingDebt);
const newDebt = existingDebt - paymentAmount;
const updatedDebts = {
...lenderDebts
};
if (newDebt <= 0) {
delete updatedDebts[borrowerKey];
} else {
updatedDebts[borrowerKey] = newDebt;
}
this._columnDebts.set(lenderKey, updatedDebts);
amount = amount - paymentAmount;
}
// if amount remains, create new debt
if (amount > 0) {
const borrowerDebts = this._columnDebts.get(borrowerKey) ?? {};
this._columnDebts.set(borrowerKey, {
...borrowerDebts,
[lenderKey]: (borrowerDebts[lenderKey] ?? 0) + amount
});
}
};
// restores a column to its original width, settling all debts first
restoreColumnWidth = columnKey => {
// settle debts (collect from debtors, pay lenders)
this._settleWidthDebts(columnKey);
// restore original width
const originalWidth = this._originalColumnWidths.get(columnKey);
if (originalWidth) {
this._columnWidths.set(columnKey, originalWidth);
this._transientColumnWidths.delete(columnKey);
}
};
_settleWidthDebts(key) {
this._collectWidthDebts(key);
this._payWidthDebts(key);
}
_collectWidthDebts(collectorKey) {
// iterate over all known columns to see if they owe the collector
this.args.columnOrder.forEach(debtorKey => {
if (debtorKey === collectorKey) {
return;
}
const debtorDebts = this._columnDebts.get(debtorKey);
const debtToCollect = debtorDebts?.[collectorKey] ?? 0;
if (debtToCollect <= 0) {
return;
}
// attempt to source funds from the debtor
const amountPaid = this._sourceFundsForPayment(debtorKey, debtToCollect);
if (amountPaid > 0) {
// add funds to collector
const currentCollectorWidth = this._getPxWidth(collectorKey);
this._columnWidths.set(collectorKey, `${currentCollectorWidth + amountPaid}px`);
this._transientColumnWidths.delete(collectorKey);
// update debtors ledger
const remainingDebt = debtToCollect - amountPaid;
const currentDebts = this._columnDebts.get(debtorKey) ?? {};
const updatedDebts = {
...currentDebts
};
if (remainingDebt > 0) {
updatedDebts[collectorKey] = remainingDebt;
} else {
delete updatedDebts[collectorKey];
}
this._columnDebts.set(debtorKey, updatedDebts);
}
});
}
_payWidthDebts(payerKey) {
const debts = this._columnDebts.get(payerKey);
if (debts === undefined) {
return;
}
Object.entries(debts).forEach(([lenderKey, amount]) => {
// give width back to lender
const currentLenderWidth = this._getPxWidth(lenderKey);
this._columnWidths.set(lenderKey, `${currentLenderWidth + amount}px`);
this._transientColumnWidths.delete(lenderKey);
});
// lear payers debts
this._columnDebts.delete(payerKey);
}
_sourceFundsForPayment(key, amountNeeded) {
let fundsSourced = 0;
// preferentially source width from our own surplus first
const currentWidth = this._getPxWidth(key);
const minWidth = this._getPxMinWidth(key);
const surplus = Math.max(0, currentWidth - minWidth);
const paymentFromSurplus = Math.min(amountNeeded, surplus);
if (paymentFromSurplus > 0) {
this._columnWidths.set(key, `${currentWidth - paymentFromSurplus}px`);
this._transientColumnWidths.delete(key);
fundsSourced = fundsSourced + paymentFromSurplus;
}
// if shortfall, source from our debtors (recursive)
const shortfall = amountNeeded - fundsSourced;
if (shortfall > 0) {
// find who owes this column (key)
const ourDebtors = this.args.columnOrder.filter(debtorKey => {
const debtorDebts = this._columnDebts.get(debtorKey);
return debtorDebts && debtorDebts[key] && debtorDebts[key] > 0;
});
for (const subDebtorKey of ourDebtors) {
const amountStillNeeded = amountNeeded - fundsSourced;
if (amountStillNeeded <= 0) {
break;
}
const subDebtorDebts = this._columnDebts.get(subDebtorKey);
const subDebtOwed = subDebtorDebts[key] ?? 0;
const amountToRequest = Math.min(amountStillNeeded, subDebtOwed);
const collectedFromSubDebtor = this._sourceFundsForPayment(subDebtorKey, amountToRequest);
if (collectedFromSubDebtor > 0) {
fundsSourced = fundsSourced + collectedFromSubDebtor;
// update sub-debtor ledger
const remaining = subDebtOwed - collectedFromSubDebtor;
const updatedSubDebts = {
...subDebtorDebts
};
if (remaining > 0) {
updatedSubDebts[key] = remaining;
} else {
delete updatedSubDebts[key];
}
this._columnDebts.set(subDebtorKey, updatedSubDebts);
}
}
}
return fundsSourced;
}
static {
setComponentTemplate(precompileTemplate("{{yield (hash gridTemplateColumns=this.gridTemplateColumns syncWidthValues=this.syncWidthValues applyTransientWidth=this.applyTransientWidth getAppliedWidth=this.getAppliedWidth getSiblingColumnKeys=this.getSiblingColumnKeys restoreColumnWidth=this.restoreColumnWidth setTransientColumnWidths=this.setTransientColumnWidths setTransientColumnWidth=this.setTransientColumnWidth resetTransientColumnWidths=this.resetTransientColumnWidths updateResizeDebt=this.updateResizeDebt)}}", {
strictMode: true,
scope: () => ({
hash
})
}), this);
}
}
export { DEFAULT_MAX_WIDTH, DEFAULT_MIN_WIDTH, DEFAULT_WIDTH, HdsAdvancedTableColumnManagerWidth as default };
//# sourceMappingURL=width.js.map