From a9eec08d8fffc6ed20c1d2bce9de1c671614918c Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 6 Jan 2026 14:18:54 +0100 Subject: [PATCH 01/11] Fix logger message --- src/includes/VscElement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/includes/VscElement.ts b/src/includes/VscElement.ts index a08762f8..13f8db00 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}`); } }; From 4ccc2ab83b1cfce83e5ccfe5627f38084b275a54 Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 6 Jan 2026 14:23:49 +0100 Subject: [PATCH 02/11] Calculate initial sizes from header attributes --- dev/vscode-table/shift-table-columns.html | 18 ++---- .../vscode-table-header-cell.ts | 19 ++++++ src/vscode-table/initial-column-widths.ts | 61 +++++++++++++++++++ src/vscode-table/vscode-table.ts | 48 +++++++++++++-- 4 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src/vscode-table/initial-column-widths.ts diff --git a/dev/vscode-table/shift-table-columns.html b/dev/vscode-table/shift-table-columns.html index 547e680a..a459b8a9 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 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 30b77b2e..29c42ccb 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 00000000..3cb09a97 --- /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 d8098c79..6ece3c1e 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 @@ -199,6 +203,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 { @@ -519,7 +527,9 @@ export class VscodeTable extends VscElement { private _onHeaderSlotChange() { 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; @@ -531,10 +541,28 @@ export class VscodeTable extends VscElement { 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 _onBodySlotChange() { - this._initDefaultColumnSizes(); + // this._initDefaultColumnSizes(); + this._updateBodyColumnWidths(); this._initResizeObserver(); this._updateResizeHandlersSize(); @@ -574,6 +602,12 @@ export class VscodeTable extends VscElement { this.requestUpdate(); } + 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; @@ -581,8 +615,7 @@ export class VscodeTable extends VscElement { headerCells.forEach((h, i) => (h.style.width = `${widths[i]}%`)); if (resizeBodyCells) { - const firstRowCells = this._getCellsOfFirstRow(); - firstRowCells.forEach((c, i) => (c.style.width = `${widths[i]}%`)); + this._updateBodyColumnWidths(); } } @@ -639,7 +672,14 @@ export class VscodeTable extends VscElement { } }; + private _handlePreferredColumnWidthChange = ( + event: VscTableChangePreferredColumnWidthEvent + ) => { + console.log(event); + }; + override render(): TemplateResult { + console.log('render'); const splitterPositions = this._columnResizeController.splitterPositions; const sashes = splitterPositions.map((val, index) => { From b03433e2f1735b314033c51d8c9918d2a626797e Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 6 Jan 2026 15:01:33 +0100 Subject: [PATCH 03/11] Update table when preferredWidth change --- src/vscode-table/vscode-table.ts | 35 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/vscode-table/vscode-table.ts b/src/vscode-table/vscode-table.ts index 6ece3c1e..384efc30 100644 --- a/src/vscode-table/vscode-table.ts +++ b/src/vscode-table/vscode-table.ts @@ -510,21 +510,7 @@ export class VscodeTable extends VscElement { this._activeSashElementIndex = -1; } - private _onDefaultSlotChange() { - this._assignedElements.forEach((el) => { - if (el.tagName.toLowerCase() === 'vscode-table-header') { - el.slot = 'header'; - return; - } - - if (el.tagName.toLowerCase() === 'vscode-table-body') { - el.slot = 'body'; - return; - } - }); - } - - private _onHeaderSlotChange() { + private _updateColumnWidths() { this._headerCells = this._queryHeaderCells(); const minWidths: Percent[] = []; const preferredWidths: Percent[] = []; @@ -560,6 +546,24 @@ export class VscodeTable extends VscElement { this._resizeColumns(true); } + private _onDefaultSlotChange() { + this._assignedElements.forEach((el) => { + if (el.tagName.toLowerCase() === 'vscode-table-header') { + el.slot = 'header'; + return; + } + + if (el.tagName.toLowerCase() === 'vscode-table-body') { + el.slot = 'body'; + return; + } + }); + } + + private _onHeaderSlotChange() { + this._updateColumnWidths(); + } + private _onBodySlotChange() { // this._initDefaultColumnSizes(); this._updateBodyColumnWidths(); @@ -676,6 +680,7 @@ export class VscodeTable extends VscElement { event: VscTableChangePreferredColumnWidthEvent ) => { console.log(event); + this._updateColumnWidths(); }; override render(): TemplateResult { From 98c642696af69049336e29cfce1ccd6164491d17 Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 6 Jan 2026 23:26:30 +0100 Subject: [PATCH 04/11] Update table when minWidth changed --- src/vscode-table/vscode-table.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vscode-table/vscode-table.ts b/src/vscode-table/vscode-table.ts index 384efc30..2c79443b 100644 --- a/src/vscode-table/vscode-table.ts +++ b/src/vscode-table/vscode-table.ts @@ -673,6 +673,7 @@ export class VscodeTable extends VscElement { if (value) { this._columnResizeController.setColumnMinWidthAt(columnIndex, value); + this._updateColumnWidths(); } }; From cebae580394e9f450e592589e31f358d499c8c23 Mon Sep 17 00:00:00 2001 From: bendera Date: Tue, 6 Jan 2026 23:44:22 +0100 Subject: [PATCH 05/11] Refactor --- src/vscode-table/vscode-table.ts | 58 +++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/vscode-table/vscode-table.ts b/src/vscode-table/vscode-table.ts index 2c79443b..e342ab2c 100644 --- a/src/vscode-table/vscode-table.ts +++ b/src/vscode-table/vscode-table.ts @@ -39,6 +39,8 @@ import {calculateInitialWidths, Column} from './initial-column-widths.js'; export class VscodeTable extends VscElement { static override styles = styles; + //#region properties + /** @internal */ @property({reflect: true}) override role = 'table'; @@ -132,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; @@ -196,6 +202,10 @@ export class VscodeTable extends VscElement { private _columnResizeController = new ColumnResizeController(this); + //#endregion + + //#region lifecycle methods + constructor() { super(); @@ -243,6 +253,10 @@ export class VscodeTable extends VscElement { } } + //#endregion + + //#region private methods + private _memoizeComponentDimensions() { const cr = this.getBoundingClientRect(); @@ -546,6 +560,27 @@ export class VscodeTable extends VscElement { 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') { @@ -606,23 +641,6 @@ export class VscodeTable extends VscElement { this.requestUpdate(); } - 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(); - } - } - private _handleSplitterPointerDown(event: PointerEvent) { event.stopPropagation(); @@ -678,14 +696,14 @@ export class VscodeTable extends VscElement { }; private _handlePreferredColumnWidthChange = ( - event: VscTableChangePreferredColumnWidthEvent + _event: VscTableChangePreferredColumnWidthEvent ) => { - console.log(event); this._updateColumnWidths(); }; + //#endregion + override render(): TemplateResult { - console.log('render'); const splitterPositions = this._columnResizeController.splitterPositions; const sashes = splitterPositions.map((val, index) => { From d4b89d8faf45117638a060bfbbd874233d23c2d6 Mon Sep 17 00:00:00 2001 From: bendera Date: Wed, 7 Jan 2026 00:08:26 +0100 Subject: [PATCH 06/11] Add demo for #584 --- dev/vscode-table/shift-table-columns.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/dev/vscode-table/shift-table-columns.html b/dev/vscode-table/shift-table-columns.html index a459b8a9..8ffcbe02 100644 --- a/dev/vscode-table/shift-table-columns.html +++ b/dev/vscode-table/shift-table-columns.html @@ -95,6 +95,28 @@

Basic example

+ + +
From 27c60fe791db572923dc4317a838d2ff8d583d08 Mon Sep 17 00:00:00 2001 From: bendera Date: Wed, 7 Jan 2026 23:11:55 +0100 Subject: [PATCH 07/11] Update column widths in table body Update column widths in the table body when the default slot content changes. --- src/vscode-table-body/vscode-table-body.ts | 13 ++++++++++++- src/vscode-table/vscode-table.ts | 12 ++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/vscode-table-body/vscode-table-body.ts b/src/vscode-table-body/vscode-table-body.ts index 0d97f757..f2a74499 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/vscode-table.ts b/src/vscode-table/vscode-table.ts index e342ab2c..78bd0ffb 100644 --- a/src/vscode-table/vscode-table.ts +++ b/src/vscode-table/vscode-table.ts @@ -601,7 +601,6 @@ export class VscodeTable extends VscElement { private _onBodySlotChange() { // this._initDefaultColumnSizes(); - this._updateBodyColumnWidths(); this._initResizeObserver(); this._updateResizeHandlersSize(); @@ -701,6 +700,11 @@ export class VscodeTable extends VscElement { this._updateColumnWidths(); }; + private _handleTableBodySlotChange() { + this._cellsOfFirstRow = []; + this._updateBodyColumnWidths(); + } + //#endregion override render(): TemplateResult { @@ -755,7 +759,11 @@ export class VscodeTable extends VscElement {
- +
${sashes} From 96a133ed5ec95fad4c2fdb7547f7c226f98855bf Mon Sep 17 00:00:00 2001 From: bendera Date: Mon, 12 Jan 2026 00:24:09 +0100 Subject: [PATCH 08/11] wip --- dev/vscode-table/test-cases.html | 65 +++++++++++++++++++ package.json | 2 +- src/includes/test-helpers.ts | 30 +++++---- .../vscode-table-header-cell.ts | 2 +- src/vscode-table/vscode-table.test.ts | 33 +++++++++- src/vscode-table/vscode-table.ts | 12 +++- 6 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 dev/vscode-table/test-cases.html diff --git a/dev/vscode-table/test-cases.html b/dev/vscode-table/test-cases.html new file mode 100644 index 00000000..5232e867 --- /dev/null +++ b/dev/vscode-table/test-cases.html @@ -0,0 +1,65 @@ + + + + + + VSCode Elements + + + + + + + +

Component demo

+
+ + + + Col 1 + Col 2 + + + + cell 1 + cell 2 + + + + + + + + Col 1 + Col 2 + + + + cell 1 + cell 2 + + + + +
+ + diff --git a/package.json b/package.json index 5d3d6a43..39e07650 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "icons": "node scripts/generateIconList.js", "vscode-data": "wireit", "ncu": "ncu -u", - "prepare": "npx playwright install chromium --only-shell" + "prepare": "npx playwright install chromium" }, "wireit": { "analyze": { diff --git a/src/includes/test-helpers.ts b/src/includes/test-helpers.ts index 720ab6e6..b5261c75 100644 --- a/src/includes/test-helpers.ts +++ b/src/includes/test-helpers.ts @@ -85,26 +85,30 @@ export async function moveMouseOnElement( /** A testing utility that drags an element with the mouse. */ export async function dragElement( - /** The element to drag */ el: Element, - /** The horizontal distance to drag in pixels */ deltaX = 0, - /** The vertical distance to drag in pixels */ deltaY = 0, - callbacks: { - afterMouseDown?: () => void | Promise; - afterMouseMove?: () => void | Promise; - } = {} + steps = 8, + position: 'left' | 'right' | 'center' = 'center' ): Promise { - await moveMouseOnElement(el); - await sendMouse({type: 'down'}); + const start = determineMousePosition(el, position, 0, 0); - await callbacks.afterMouseDown?.(); + await sendMouse({ + type: 'move', + position: [start.clickX, start.clickY], + }); - const {clickX, clickY} = determineMousePosition(el, 'center', deltaX, deltaY); - await sendMouse({type: 'move', position: [clickX, clickY]}); + await sendMouse({type: 'down'}); - await callbacks.afterMouseMove?.(); + for (let i = 1; i <= steps; i++) { + await sendMouse({ + type: 'move', + position: [ + Math.round(start.clickX + (deltaX * i) / steps), + Math.round(start.clickY + (deltaY * i) / steps), + ], + }); + } await sendMouse({type: 'up'}); } 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 29c42ccb..59df1d39 100644 --- a/src/vscode-table-header-cell/vscode-table-header-cell.ts +++ b/src/vscode-table-header-cell/vscode-table-header-cell.ts @@ -25,7 +25,7 @@ export class VscodeTableHeaderCell extends VscElement { static override styles = styles; @property({attribute: 'min-width'}) - minWidth = '0'; + minWidth: string | undefined = undefined; @property({attribute: 'preferred-width'}) preferredWidth = 'auto'; diff --git a/src/vscode-table/vscode-table.test.ts b/src/vscode-table/vscode-table.test.ts index 73565d1f..94c9ab0e 100644 --- a/src/vscode-table/vscode-table.test.ts +++ b/src/vscode-table/vscode-table.test.ts @@ -1,6 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-expressions */ -import {$, dragElement} from '../includes/test-helpers.js'; +import {$, $$, dragElement} from '../includes/test-helpers.js'; import {VscodeTable} from './index.js'; +import '../vscode-table-body/vscode-table-body.js'; +import '../vscode-table-header/vscode-table-header.js'; +import '../vscode-table-row/vscode-table-row.js'; +import '../vscode-table-cell/vscode-table-cell.js'; +import '../vscode-table-header-cell/vscode-table-header-cell.js'; import {expect, fixture, html} from '@open-wc/testing'; describe('vscode-table', () => { @@ -35,4 +40,30 @@ describe('vscode-table', () => { expect(await testDrag()).not.to.throw; }); + + it.only('min column width', async () => { + const el = await fixture(html` + + + Col 1 + Col 2 + + + + cell 1 + cell 2 + + + + `); + + await dragElement($(el.shadowRoot!, '.sash.resizable'), -500, 0, 8, 'left'); + + const cells = $$('vscode-table-cell'); + + expect(cells[0].style.getPropertyValue('width')).to.equal('20%'); + expect(cells[1].style.getPropertyValue('width')).to.equal('80%'); + }); }); diff --git a/src/vscode-table/vscode-table.ts b/src/vscode-table/vscode-table.ts index 78bd0ffb..3a463cf7 100644 --- a/src/vscode-table/vscode-table.ts +++ b/src/vscode-table/vscode-table.ts @@ -539,16 +539,24 @@ export class VscodeTable extends VscElement { parseSizeAttributeToPercent(c.minWidth, this._componentW) ?? percent(0); this._columnResizeController.setColumnMinWidthAt(i, minWidth); + } else { + const minWidth = + parseSizeAttributeToPercent(this.minColumnWidth, 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) + ? parseSizeAttributeToPercent( + cell.preferredWidth ?? '0', + this._componentW + ) : cell.preferredWidth; const minWidth = parseSizeAttributeToPercent( - cell.minWidth, + cell.minWidth ?? '0', this._componentW ); From 661148b57275a98d90552cd8c829968e90404949 Mon Sep 17 00:00:00 2001 From: bendera Date: Sat, 17 Jan 2026 23:51:36 +0100 Subject: [PATCH 09/11] More fault-tolerant calculation --- src/includes/test-helpers.ts | 2 +- src/vscode-table/calculations.ts | 13 ++-- src/vscode-table/vscode-table.test.ts | 99 ++++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 17 deletions(-) diff --git a/src/includes/test-helpers.ts b/src/includes/test-helpers.ts index b5261c75..73bab355 100644 --- a/src/includes/test-helpers.ts +++ b/src/includes/test-helpers.ts @@ -88,7 +88,7 @@ export async function dragElement( el: Element, deltaX = 0, deltaY = 0, - steps = 8, + steps = 1, position: 'left' | 'right' | 'center' = 'center' ): Promise { const start = determineMousePosition(el, position, 0, 0); diff --git a/src/vscode-table/calculations.ts b/src/vscode-table/calculations.ts index ab7e5e58..8f7abdd5 100644 --- a/src/vscode-table/calculations.ts +++ b/src/vscode-table/calculations.ts @@ -37,14 +37,15 @@ export function calculateColumnWidths( let totalAvailable: Percent = percent(0); for (const i of shrinkingSide) { - const available = Math.max(0, result[i] - (minWidths.get(i) ?? 0)); + const min = minWidths.get(i) ?? percent(0); + const available = Math.max(0, result[i] - min); totalAvailable = percent(totalAvailable + available); } - // Abort if the requested delta cannot be fully satisfied - if (totalAvailable < remaining) { - return result; - } + const effectiveDelta = + totalAvailable < remaining ? totalAvailable : remaining; + + remaining = percent(effectiveDelta); // Shrink columns sequentially until the delta is fully consumed for (const i of shrinkingSide) { @@ -60,7 +61,7 @@ export function calculateColumnWidths( } // Apply the exact opposite delta to the growing side - let toAdd: Percent = percent(absDelta); + let toAdd: Percent = percent(effectiveDelta); for (const i of growingSide) { if (toAdd === 0) { diff --git a/src/vscode-table/vscode-table.test.ts b/src/vscode-table/vscode-table.test.ts index 94c9ab0e..912924e0 100644 --- a/src/vscode-table/vscode-table.test.ts +++ b/src/vscode-table/vscode-table.test.ts @@ -7,6 +7,7 @@ import '../vscode-table-row/vscode-table-row.js'; import '../vscode-table-cell/vscode-table-cell.js'; import '../vscode-table-header-cell/vscode-table-header-cell.js'; import {expect, fixture, html} from '@open-wc/testing'; +import {VscodeTableCell} from '../vscode-table-cell/vscode-table-cell.js'; describe('vscode-table', () => { it('is defined', () => { @@ -41,29 +42,107 @@ describe('vscode-table', () => { expect(await testDrag()).not.to.throw; }); - it.only('min column width', async () => { + it('resizes two columns when dragged left', async () => { const el = await fixture(html` - Col 1 + Col 1 Col 2 - cell 1 - cell 2 + A + B + + + + `); + + await dragElement($(el.shadowRoot!, '.sash.resizable'), -100, 0); + + const cells = $$('vscode-table-cell'); + + const w1 = parseFloat(cells[0].style.width); + const w2 = parseFloat(cells[1].style.width); + + expect(w1 + w2).to.equal(100); + expect(w1).to.be.lessThan(50); + expect(w2).to.be.greaterThan(50); + }); + + it('clamps resize at min-width', async () => { + const el = await fixture(html` + + + + Col 1 + + Col 2 + + + + A + B `); - await dragElement($(el.shadowRoot!, '.sash.resizable'), -500, 0, 8, 'left'); + // 500px table → min-width 100px = 20% + await dragElement($(el.shadowRoot!, '.sash.resizable'), -400, 0); + + const cells = $$('vscode-table-cell'); + + expect(cells[0].style.width).to.equal('20%'); + expect(cells[1].style.width).to.equal('80%'); + }); + + it('uses preferred-width as initial column width', async () => { + await fixture(html` + + + + Col 1 + + Col 2 + + + + A + B + + + + `); + + const cells = $$('vscode-table-cell'); + + expect(cells[0].style.width).to.equal('30%'); + expect(cells[1].style.width).to.equal('70%'); + }); + + it('min-width overrides preferred-width when preferred is too small', async () => { + await fixture(html` + + + + Col 1 + + Col 2 + + + + A + B + + + + `); - const cells = $$('vscode-table-cell'); + // 150px / 500px = 30% + const cells = $$('vscode-table-cell'); - expect(cells[0].style.getPropertyValue('width')).to.equal('20%'); - expect(cells[1].style.getPropertyValue('width')).to.equal('80%'); + expect(cells[0].style.width).to.equal('30%'); + expect(cells[1].style.width).to.equal('70%'); }); }); From c20d146539f343571af4c626a7dce6fbb00ad530 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 18 Jan 2026 00:08:05 +0100 Subject: [PATCH 10/11] Add test --- dev/vscode-table/test-cases.html | 22 ++++++++++++++ src/vscode-table/vscode-table.test.ts | 43 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/dev/vscode-table/test-cases.html b/dev/vscode-table/test-cases.html index 5232e867..1b30aef2 100644 --- a/dev/vscode-table/test-cases.html +++ b/dev/vscode-table/test-cases.html @@ -44,6 +44,7 @@

Component demo

+ @@ -60,6 +61,27 @@

Component demo

+ + + + + + Col A + + + Col B + + Col C + + + + A + B + C + + + + diff --git a/src/vscode-table/vscode-table.test.ts b/src/vscode-table/vscode-table.test.ts index 912924e0..f7c9865c 100644 --- a/src/vscode-table/vscode-table.test.ts +++ b/src/vscode-table/vscode-table.test.ts @@ -145,4 +145,47 @@ describe('vscode-table', () => { expect(cells[0].style.width).to.equal('30%'); expect(cells[1].style.width).to.equal('70%'); }); + + it('chains shrinking across multiple columns when min-width is hit', async () => { + const el = await fixture(html` + + + + Col A + + + Col B + + Col C + + + + A + B + C + + + + `); + + await dragElement($(el.shadowRoot!, '.sash.resizable'), -300, 0); + + const cells = $$('vscode-table-cell'); + + const wA = parseFloat(cells[0].style.width); + const wB = parseFloat(cells[1].style.width); + const wC = parseFloat(cells[2].style.width); + + // A should absorb the remaining shrink, but not go below its min-width + expect(wA).to.be.closeTo(16.67, 0.1); + + // B should be clamped at its min-width: 200 / 600 = 33.33% + expect(wB).to.be.closeTo(50, 0.1); + + // C grows by exactly what A lost + expect(wA + wB + wC).to.be.closeTo(100, 0.01); + + // Ensure actual chaining happened (A < initial) + expect(wA).to.be.lessThan(33.34); + }); }); From 4934af011e8b9c2421022ab496debe0801fc1a67 Mon Sep 17 00:00:00 2001 From: bendera Date: Sun, 18 Jan 2026 01:02:05 +0100 Subject: [PATCH 11/11] Add tests --- src/vscode-table/vscode-table.test.ts | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/vscode-table/vscode-table.test.ts b/src/vscode-table/vscode-table.test.ts index f7c9865c..f6325d25 100644 --- a/src/vscode-table/vscode-table.test.ts +++ b/src/vscode-table/vscode-table.test.ts @@ -188,4 +188,98 @@ describe('vscode-table', () => { // Ensure actual chaining happened (A < initial) expect(wA).to.be.lessThan(33.34); }); + + it('chains growing across multiple columns when right side hits min-width', async () => { + const el = await fixture(html` + + + Col A + Col B + + Col C + + + + + A + B + C + + + + `); + + // Drag splitter between B and C to the RIGHT + await dragElement($(el.shadowRoot!, '.sash.resizable'), +300, 0); + + const cells = $$('vscode-table-cell'); + + const wA = parseFloat(cells[0].style.width); + const wB = parseFloat(cells[1].style.width); + const wC = parseFloat(cells[2].style.width); + + // C should clamp at min-width: 150 / 600 = 25% + expect(wC).to.be.closeTo(25, 0.1); + + // A + B should grow + expect(wA + wB).to.be.greaterThan(66.6); + + // Sum invariant + expect(wA + wB + wC).to.be.closeTo(100, 0.01); + }); + + it('preserves column widths when table body is cleared and re-populated', async () => { + const el = await fixture(html` + + + + Col 1 + + Col 2 + + + + A + B + + + + `); + + await dragElement($(el.shadowRoot!, '.sash.resizable'), -200, 0); + + let cells = $$('vscode-table-cell'); + + const widthBefore = Array.from(cells).map((c) => + c.style.getPropertyValue('width') + ); + + // Sanity check: widths are actually set + expect(widthBefore[0]).to.not.equal(''); + expect(widthBefore[1]).to.not.equal(''); + + const body = el.querySelector('vscode-table-body')!; + body.innerHTML = ''; + + await el.updateComplete; + + body.appendChild( + document.createRange().createContextualFragment(` + + C + D + + `) + ); + + await el.updateComplete; + + cells = $$('vscode-table-cell'); + + const widthAfter = Array.from(cells).map((c) => + c.style.getPropertyValue('width') + ); + + expect(widthAfter).to.deep.equal(widthBefore); + }); });