@hashicorp/design-system-components
Version:
Helios Design System Components
318 lines (300 loc) • 10.9 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { modifier } from 'ember-modifier';
import { requestAnimationFrameWaiter } from './utils.js';
import { BORDER_WIDTH } from './index.js';
import { precompileTemplate } from '@ember/template-compilation';
import { g, i, n } from 'decorator-transforms/runtime';
import { setComponentTemplate } from '@ember/component';
var TEMPLATE = precompileTemplate("{{!\n Copyright (c) HashiCorp, Inc.\n SPDX-License-Identifier: MPL-2.0\n}}\n\n{{! template-lint-disable no-pointer-down-event-binding }}\n<div\n class={{this.classNames}}\n draggable=\"false\"\n role=\"slider\"\n aria-orientation=\"horizontal\"\n aria-valuenow={{or @column.pxAppliedWidth @column.thElement.offsetWidth}}\n aria-valuemin={{@column.pxMinWidth}}\n aria-valuemax={{@column.pxMaxWidth}}\n tabindex=\"0\"\n aria-label={{hds-t\n \"hds.components.advanced-table.th-resize-handle.aria-label\"\n columnLabel=@column.label\n default=(concat \"Resize \" @column.label \" column\")\n }}\n {{this._registerHandleElement}}\n {{on \"pointerdown\" this.startResize}}\n {{on \"keydown\" this.handleKeydown}}\n {{style height=this.height}}\n ...attributes\n/>");
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
const KEYBOARD_RESIZE_STEP = 10;
function calculateEffectiveDelta(deltaX, col, startColW, nextCol, startNextColW) {
const colMin = col.pxMinWidth ?? 0;
const colMax = col.pxMaxWidth ?? Infinity;
const nextMin = nextCol.pxMinWidth ?? 0;
const nextMax = nextCol.pxMaxWidth ?? Infinity;
let effectiveDelta = 0;
// expanding col, shrinking nextCol
if (deltaX > 0) {
const maxCanExpandCol = colMax - startColW;
const maxCanShrinkNext = startNextColW - nextMin;
effectiveDelta = Math.min(deltaX, maxCanExpandCol, maxCanShrinkNext);
effectiveDelta = Math.max(0, effectiveDelta);
}
// shrinking col, expanding nextCol
else if (deltaX < 0) {
const absDeltaX = -deltaX;
const maxCanShrinkCol = startColW - colMin;
let maxCanExpandNext;
if (startNextColW > nextMax) {
maxCanExpandNext = Infinity;
} else {
maxCanExpandNext = nextMax - startNextColW;
}
effectiveDelta = -Math.min(absDeltaX, maxCanShrinkCol, maxCanExpandNext);
effectiveDelta = Math.min(0, effectiveDelta);
}
return effectiveDelta;
}
class HdsAdvancedTableThResizeHandle extends Component {
static {
g(this.prototype, "resizing", [tracked], function () {
return null;
});
}
#resizing = (i(this, "resizing"), void 0);
static {
g(this.prototype, "_transientDelta", [tracked], function () {
return 0;
});
}
#_transientDelta = (i(this, "_transientDelta"), void 0); // track the width change as it is changing, applied when resizing stops
static {
g(this.prototype, "_isUpdateQueued", [tracked], function () {
return false;
});
}
#_isUpdateQueued = (i(this, "_isUpdateQueued"), void 0);
static {
g(this.prototype, "_lastPointerEvent", [tracked], function () {
return null;
});
}
#_lastPointerEvent = (i(this, "_lastPointerEvent"), void 0);
_handleElement;
_boundResize;
_boundStopResize;
_registerHandleElement = modifier(element => {
this._handleElement = element;
});
constructor(owner, args) {
super(owner, args);
this._boundResize = this._resize.bind(this);
this._boundStopResize = this._stopResize.bind(this);
}
get height() {
const {
tableHeight
} = this.args;
if (tableHeight === undefined) {
return;
}
return `${tableHeight - BORDER_WIDTH * 2}px`;
}
get classNames() {
const classes = ['hds-advanced-table__th-resize-handle'];
if (this.resizing !== null) {
classes.push('hds-advanced-table__th-resize-handle--resizing');
}
return classes.join(' ');
}
_applyTransientWidths() {
const {
column
} = this.args;
const {
next: nextColumn
} = column.siblings;
column.width = column.appliedWidth;
if (nextColumn !== undefined) {
nextColumn.width = nextColumn.appliedWidth;
}
}
onColumnResize(key, width) {
const {
onColumnResize
} = this.args;
if (typeof onColumnResize === 'function' && key !== undefined) {
onColumnResize(key, width);
}
}
static {
n(this.prototype, "onColumnResize", [action]);
}
handleKeydown(event) {
const validKeys = ['ArrowLeft', 'ArrowRight'];
if (!validKeys.includes(event.key)) {
return;
}
event.preventDefault();
event.stopPropagation();
const {
column
} = this.args;
const {
next: nextColumn
} = column.siblings;
if (nextColumn === undefined) {
return;
}
column.table.setTransientColumnWidths({
roundValues: true
});
const startColumnPxWidth = Math.round(column.pxAppliedWidth ?? 0);
const startNextColumnPxWidth = Math.round(nextColumn.pxAppliedWidth ?? 0);
const deltaX = event.key === 'ArrowRight' ? KEYBOARD_RESIZE_STEP : -KEYBOARD_RESIZE_STEP;
this._applyResizeDelta(deltaX, column, startColumnPxWidth, nextColumn, startNextColumnPxWidth);
// ensure the resize handle remains visible during keyboard navigation.
this._handleElement.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
// use a microtask to commit the final state after the render pass.
queueMicrotask(() => {
// reset transient values
this._setWidthDebts();
this._applyTransientWidths();
column.table.resetTransientColumnWidths();
this._transientDelta = 0;
this.onColumnResize(column.key, column.width);
});
}
static {
n(this.prototype, "handleKeydown", [action]);
}
startResize(event) {
if (event.button !== 0) {
return;
}
event.preventDefault();
event.stopPropagation();
const {
column
} = this.args;
const {
next: nextColumn
} = column.siblings;
column.table.setTransientColumnWidths();
this.resizing = {
startX: event.clientX,
startColumnPxWidth: Math.round(column.pxAppliedWidth ?? 0),
startNextColumnPxWidth: Math.round(nextColumn?.pxAppliedWidth ?? 0)
};
window.addEventListener('pointermove', this._boundResize);
window.addEventListener('pointerup', this._boundStopResize);
}
static {
n(this.prototype, "startResize", [action]);
}
_setColumnWidth(column, width) {
if (width > column.pxMaxWidth || width < column.pxMinWidth) {
column.pxTransientWidth = width;
} else {
column.setPxTransientWidth(width);
}
}
_applyResizeDelta(deltaX, column, startColumnPxWidth, nextColumn, startNextColumnPxWidth) {
const canResizeNeighbor = nextColumn !== undefined && startNextColumnPxWidth !== undefined;
if (canResizeNeighbor) {
const effectiveDelta = calculateEffectiveDelta(deltaX, column, startColumnPxWidth, nextColumn, startNextColumnPxWidth);
// set the width for the current column
this._setColumnWidth(column, Math.round(startColumnPxWidth + effectiveDelta));
// the actual new column width may differ from the intended width due to min/max constraints.
const actualNewColumnWidth = column.pxAppliedWidth ?? startColumnPxWidth;
const actualAppliedDelta = actualNewColumnWidth - startColumnPxWidth;
// set the width for the next sibling column
this._setColumnWidth(nextColumn, Math.round(startNextColumnPxWidth - actualAppliedDelta));
this._transientDelta = actualAppliedDelta;
} else {
column.setPxTransientWidth(Math.round(startColumnPxWidth + deltaX));
}
}
_resize(event) {
this._lastPointerEvent = event;
if (this._isUpdateQueued) {
return;
}
this._isUpdateQueued = true;
requestAnimationFrameWaiter(() => {
if (this.resizing === null || this._lastPointerEvent === null) {
this._isUpdateQueued = false;
return;
}
const event = this._lastPointerEvent;
event.preventDefault();
const {
column
} = this.args;
const {
next: nextColumn
} = column.siblings;
const {
startX,
startColumnPxWidth,
startNextColumnPxWidth
} = this.resizing;
const deltaX = event.clientX - startX;
this._applyResizeDelta(deltaX, column, startColumnPxWidth,
// Width at the start of the drag
nextColumn, startNextColumnPxWidth // Width of next col at the start of the drag
);
this._isUpdateQueued = false;
});
}
_stopResize() {
const {
column
} = this.args;
window.removeEventListener('pointermove', this._boundResize);
window.removeEventListener('pointerup', this._boundStopResize);
this._setWidthDebts();
this._applyTransientWidths();
// reset the transient width
column.table.resetTransientColumnWidths();
// reset the resizing state
this.resizing = null;
this._transientDelta = 0;
this.onColumnResize(column.key, column.appliedWidth);
}
_addDebt(borrower, lenderKey, amount) {
borrower.widthDebts = {
...borrower.widthDebts,
[lenderKey]: (borrower.widthDebts[lenderKey] ?? 0) + amount
};
}
_setWidthDebts() {
const {
column
} = this.args;
const {
next: nextColumn
} = column.siblings;
const delta = this._transientDelta;
if (delta === 0 || nextColumn === undefined || nextColumn.key === undefined || column.key === undefined) {
return;
}
// Determine the borrower, lender, and the amount of width transferred
const borrower = delta > 0 ? column : nextColumn;
const lender = delta > 0 ? nextColumn : column;
let amount = Math.abs(delta);
if (borrower.key === undefined || lender.key === undefined) {
return;
}
// Check if the lender already has a debt to the borrower.
// If so, this transaction is a "payment" against that existing debt.
const existingDebt = lender.widthDebts[borrower.key] ?? 0;
if (existingDebt > 0) {
const paymentAmount = Math.min(amount, existingDebt);
// Reduce the lender's debt by the payment amount
lender.widthDebts[borrower.key] = existingDebt - paymentAmount;
if (lender.widthDebts[borrower.key] <= 0) {
delete lender.widthDebts[borrower.key];
}
// The amount of the new debt is reduced by the amount paid
amount = amount - paymentAmount;
}
// If there is still a remaining amount, create a new debt for the borrower.
if (amount > 0) {
this._addDebt(borrower, lender.key, amount);
}
}
}
setComponentTemplate(TEMPLATE, HdsAdvancedTableThResizeHandle);
export { HdsAdvancedTableThResizeHandle as default };
//# sourceMappingURL=th-resize-handle.js.map