diff --git a/dev/vscode-table/shift-table-columns.html b/dev/vscode-table/shift-table-columns.html index 547e680aa..8ffcbe025 100644 --- a/dev/vscode-table/shift-table-columns.html +++ b/dev/vscode-table/shift-table-columns.html @@ -37,27 +37,21 @@

Basic example

- + - Id - First name - Last name - Email - Company @@ -101,6 +95,28 @@

Basic example

+ + +
diff --git a/src/includes/VscElement.ts b/src/includes/VscElement.ts index a08762f83..13f8db001 100644 --- a/src/includes/VscElement.ts +++ b/src/includes/VscElement.ts @@ -11,7 +11,7 @@ const warn = (message: string, componentInstance?: VscElement) => { console.warn(`${prefix}${message}\n%o`, componentInstance); } else { // eslint-disable-next-line no-console - console.warn(`${message}\n%o`, componentInstance); + console.warn(`${prefix}${message}`); } }; diff --git a/src/vscode-table-body/vscode-table-body.ts b/src/vscode-table-body/vscode-table-body.ts index 0d97f7576..f2a74499e 100644 --- a/src/vscode-table-body/vscode-table-body.ts +++ b/src/vscode-table-body/vscode-table-body.ts @@ -14,8 +14,15 @@ export class VscodeTableBody extends VscElement { @property({reflect: true}) override role = 'rowgroup'; + private _handleSlotChange() { + /** @internal */ + this.dispatchEvent( + new Event('vsc-table-body-slot-changed', {bubbles: true}) + ); + } + override render(): TemplateResult { - return html` `; + return html` `; } } @@ -23,4 +30,8 @@ declare global { interface HTMLElementTagNameMap { 'vscode-table-body': VscodeTableBody; } + + interface GlobalEventHandlersEventMap { + 'vsc-table-body-slot-changed': Event; + } } diff --git a/src/vscode-table-header-cell/vscode-table-header-cell.ts b/src/vscode-table-header-cell/vscode-table-header-cell.ts index 30b77b2e3..29c42ccb4 100644 --- a/src/vscode-table-header-cell/vscode-table-header-cell.ts +++ b/src/vscode-table-header-cell/vscode-table-header-cell.ts @@ -8,6 +8,11 @@ export type VscTableChangeMinColumnWidthEvent = CustomEvent<{ propertyValue: string; }>; +export type VscTableChangePreferredColumnWidthEvent = CustomEvent<{ + columnIndex: number; + propertyValue: string; +}>; + /** * @tag vscode-table-header-cell * @@ -22,6 +27,9 @@ export class VscodeTableHeaderCell extends VscElement { @property({attribute: 'min-width'}) minWidth = '0'; + @property({attribute: 'preferred-width'}) + preferredWidth = 'auto'; + /** @internal */ @property({type: Number}) index = -1; @@ -40,6 +48,16 @@ export class VscodeTableHeaderCell extends VscElement { }) as VscTableChangeMinColumnWidthEvent ); } + + if (changedProperties.has('preferredWidth') && this.index > -1) { + /** @internal */ + this.dispatchEvent( + new CustomEvent('vsc-table-change-preferred-column-width', { + detail: {columnIndex: this.index, propertyValue: this.preferredWidth}, + bubbles: true, + }) as VscTableChangePreferredColumnWidthEvent + ); + } } override render(): TemplateResult { @@ -58,5 +76,6 @@ declare global { interface GlobalEventHandlersEventMap { 'vsc-table-change-min-column-width': VscTableChangeMinColumnWidthEvent; + 'vsc-table-change-preferred-column-width': VscTableChangePreferredColumnWidthEvent; } } diff --git a/src/vscode-table/initial-column-widths.ts b/src/vscode-table/initial-column-widths.ts new file mode 100644 index 000000000..3cb09a972 --- /dev/null +++ b/src/vscode-table/initial-column-widths.ts @@ -0,0 +1,61 @@ +import {percent, Percent} from '../includes/sizes.js'; + +const PERCENT_FULL = percent(100); + +export type ColumnWidth = Percent | 'auto'; + +export interface Column { + preferredWidth: ColumnWidth; + minWidth: Percent; +} + +export function calculateInitialWidths(columns: Column[]): Percent[] { + const finalWidths: Percent[] = columns.map( + (c) => + typeof c.preferredWidth === 'number' + ? percent(Math.max(c.preferredWidth, c.minWidth)) + : percent(0) // auto placeholder + ); + + const autoIndices = columns + .map((c, i) => (c.preferredWidth === 'auto' ? i : -1)) + .filter((i) => i >= 0); + + const totalMinWidth = columns.reduce( + (sum, c) => percent(sum + c.minWidth), + percent(0) + ); + + if (totalMinWidth > PERCENT_FULL) { + const scale = PERCENT_FULL / totalMinWidth; + return columns.map((c) => percent(c.minWidth * scale)); + } + + const fixedWidthSum = finalWidths.reduce( + (sum, w) => percent(sum + w), + percent(0) + ); + const remainingSpace = percent(PERCENT_FULL - fixedWidthSum); + + if (remainingSpace > 0 && autoIndices.length > 0) { + const extraPerAuto = remainingSpace / autoIndices.length; + for (const i of autoIndices) { + finalWidths[i] = percent(Math.max(columns[i].minWidth, extraPerAuto)); + } + return finalWidths; + } + + if (autoIndices.length > 0) { + for (const i of autoIndices) { + finalWidths[i] = columns[i].minWidth; + } + return finalWidths; + } + + if (remainingSpace > 0 && autoIndices.length === 0) { + const scale = PERCENT_FULL / fixedWidthSum; + return finalWidths.map((w) => percent(w * scale)); + } + + return finalWidths; +} diff --git a/src/vscode-table/vscode-table.ts b/src/vscode-table/vscode-table.ts index d8098c798..78bd0ffbe 100644 --- a/src/vscode-table/vscode-table.ts +++ b/src/vscode-table/vscode-table.ts @@ -22,7 +22,11 @@ import { } from '../includes/sizes.js'; import styles from './vscode-table.styles.js'; import {ColumnResizeController} from './ColumnResizeController.js'; -import {VscTableChangeMinColumnWidthEvent} from '../vscode-table-header-cell/vscode-table-header-cell.js'; +import { + VscTableChangeMinColumnWidthEvent, + VscTableChangePreferredColumnWidthEvent, +} from '../vscode-table-header-cell/vscode-table-header-cell.js'; +import {calculateInitialWidths, Column} from './initial-column-widths.js'; /** * @tag vscode-table @@ -35,6 +39,8 @@ import {VscTableChangeMinColumnWidthEvent} from '../vscode-table-header-cell/vsc export class VscodeTable extends VscElement { static override styles = styles; + //#region properties + /** @internal */ @property({reflect: true}) override role = 'table'; @@ -128,6 +134,10 @@ export class VscodeTable extends VscElement { @property({type: Boolean, reflect: true, attribute: 'zebra-odd'}) zebraOdd = false; + //#endregion + + //#region private variables + @query('.header') private _headerElement!: HTMLDivElement; @@ -192,6 +202,10 @@ export class VscodeTable extends VscElement { private _columnResizeController = new ColumnResizeController(this); + //#endregion + + //#region lifecycle methods + constructor() { super(); @@ -199,6 +213,10 @@ export class VscodeTable extends VscElement { 'vsc-table-change-min-column-width', this._handleMinColumnWidthChange ); + this.addEventListener( + 'vsc-table-change-preferred-column-width', + this._handlePreferredColumnWidthChange + ); } override connectedCallback(): void { @@ -235,6 +253,10 @@ export class VscodeTable extends VscElement { } } + //#endregion + + //#region private methods + private _memoizeComponentDimensions() { const cr = this.getBoundingClientRect(); @@ -502,6 +524,63 @@ export class VscodeTable extends VscElement { this._activeSashElementIndex = -1; } + private _updateColumnWidths() { + this._headerCells = this._queryHeaderCells(); + const minWidths: Percent[] = []; + const preferredWidths: Percent[] = []; + minWidths.fill(percent(0), 0, this._headerCells.length - 1); + preferredWidths.fill(percent(0), 0, this._headerCells.length - 1); + + this._headerCells.forEach((c, i) => { + c.index = i; + + if (c.minWidth) { + const minWidth = + parseSizeAttributeToPercent(c.minWidth, this._componentW) ?? + percent(0); + this._columnResizeController.setColumnMinWidthAt(i, minWidth); + } + }); + + const columns = this._headerCells.map((cell) => { + const preferredWidth = + cell.preferredWidth !== 'auto' + ? parseSizeAttributeToPercent(cell.preferredWidth, this._componentW) + : cell.preferredWidth; + const minWidth = parseSizeAttributeToPercent( + cell.minWidth, + this._componentW + ); + + return {preferredWidth, minWidth} as Column; + }); + const calculatedWidths = calculateInitialWidths(columns); + + this._columnResizeController.setColumWidths(calculatedWidths); + this._resizeColumns(true); + } + + private _updateBodyColumnWidths() { + const widths = this._columnResizeController.columnWidths; + const firstRowCells = this._getCellsOfFirstRow(); + firstRowCells.forEach((c, i) => (c.style.width = `${widths[i]}%`)); + } + + private _resizeColumns(resizeBodyCells = true) { + const widths = this._columnResizeController.columnWidths; + + const headerCells = this._getHeaderCells(); + headerCells.forEach((h, i) => (h.style.width = `${widths[i]}%`)); + + if (resizeBodyCells) { + this._updateBodyColumnWidths(); + } + } + + //#endregion + + //#region event handlers + private _onDefaultSlotChange() { this._assignedElements.forEach((el) => { if (el.tagName.toLowerCase() === 'vscode-table-header') { @@ -517,24 +596,11 @@ export class VscodeTable extends VscElement { } private _onHeaderSlotChange() { - this._headerCells = this._queryHeaderCells(); - const minWidths: Percent[] = []; - minWidths.fill(percent(0), 0, this._headerCells.length - 1); - - this._headerCells.forEach((c, i) => { - c.index = i; - - if (c.minWidth) { - const minWidth = - parseSizeAttributeToPercent(c.minWidth, this._componentW) ?? - percent(0); - this._columnResizeController.setColumnMinWidthAt(i, minWidth); - } - }); + this._updateColumnWidths(); } private _onBodySlotChange() { - this._initDefaultColumnSizes(); + // this._initDefaultColumnSizes(); this._initResizeObserver(); this._updateResizeHandlersSize(); @@ -574,18 +640,6 @@ export class VscodeTable extends VscElement { this.requestUpdate(); } - private _resizeColumns(resizeBodyCells = true) { - const widths = this._columnResizeController.columnWidths; - - const headerCells = this._getHeaderCells(); - headerCells.forEach((h, i) => (h.style.width = `${widths[i]}%`)); - - if (resizeBodyCells) { - const firstRowCells = this._getCellsOfFirstRow(); - firstRowCells.forEach((c, i) => (c.style.width = `${widths[i]}%`)); - } - } - private _handleSplitterPointerDown(event: PointerEvent) { event.stopPropagation(); @@ -636,9 +690,23 @@ export class VscodeTable extends VscElement { if (value) { this._columnResizeController.setColumnMinWidthAt(columnIndex, value); + this._updateColumnWidths(); } }; + private _handlePreferredColumnWidthChange = ( + _event: VscTableChangePreferredColumnWidthEvent + ) => { + this._updateColumnWidths(); + }; + + private _handleTableBodySlotChange() { + this._cellsOfFirstRow = []; + this._updateBodyColumnWidths(); + } + + //#endregion + override render(): TemplateResult { const splitterPositions = this._columnResizeController.splitterPositions; @@ -691,7 +759,11 @@ export class VscodeTable extends VscElement {
- +
${sashes}