From 9fcd6c81889fc9dfe98e3d38346fe6b4268957e8 Mon Sep 17 00:00:00 2001 From: Jason Hyde Date: Tue, 6 Oct 2026 15:57:11 +0200 Subject: [PATCH] keep dragged table column widths and let the task column resize --- CHANGELOG.md | 9 +++++++++ packages/ui/src/views/snapshotTable.ts | 4 +++- src/views/table/TableRenderer.ts | 28 ++++++++++++++++++++------ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83eb38e0..17846300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- The task name column in the table can now be resized + +### Fixed + +- Fixed table columns shifting left instead of widening when dragging a column edge to the right +- Fixed resized table columns snapping back narrower when the table is wider than the view + ## [2.6.0] - 2026-10-06 ### Highlights diff --git a/packages/ui/src/views/snapshotTable.ts b/packages/ui/src/views/snapshotTable.ts index 454ab2a0..5d9baea0 100644 --- a/packages/ui/src/views/snapshotTable.ts +++ b/packages/ui/src/views/snapshotTable.ts @@ -113,7 +113,9 @@ export function renderSnapshotTable(container: HTMLElement, model: ViewModel): H for (const id of columns) { const th = hrow.createEl('th', { text: viewFieldLabel(id, customFields) }) const width = columnWidth(model.fields, id) - th.setCssStyles({ width: width === undefined ? 'auto' : `${width}px` }) + const titleMin = id === 'title' ? model.fields.table?.widths?.title : undefined + if (width !== undefined) th.setCssStyles({ width: `${width}px`, minWidth: `${width}px` }) + else th.setCssStyles({ width: 'auto', minWidth: titleMin === undefined ? '' : `${titleMin}px` }) const rank = model.sort.findIndex((rule) => rule.key === id) if (rank >= 0) { const indicator = th.createSpan({ diff --git a/src/views/table/TableRenderer.ts b/src/views/table/TableRenderer.ts index d116b7d8..c5afdc53 100644 --- a/src/views/table/TableRenderer.ts +++ b/src/views/table/TableRenderer.ts @@ -78,6 +78,8 @@ export interface TableContext { /** A column dragged narrower than this hides instead. */ const HIDE_BELOW = 24 +/** Matches the title cell's `min-width` in table.css. */ +const TITLE_MIN_WIDTH = 180 export function renderTable(ctx: TableContext): void { const wrapper = ctx.container.createDiv('pm-table-wrapper') @@ -134,7 +136,9 @@ export function renderTable(ctx: TableContext): void { const label = viewFieldLabel(id, customFields) const th = hrow.createEl('th') const width = columnWidth(ctx.fields, id) - th.setCssStyles({ width: width === undefined ? 'auto' : `${width}px` }) + const titleMin = id === 'title' ? ctx.fields.table?.widths?.title : undefined + if (width !== undefined) th.setCssStyles({ width: `${width}px`, minWidth: `${width}px` }) + else th.setCssStyles({ width: 'auto', minWidth: titleMin === undefined ? '' : `${titleMin}px` }) const key = SORT_KEYS.find((k) => k === id) if (key) { th.addClass('pm-table-th-sortable') @@ -160,7 +164,7 @@ export function renderTable(ctx: TableContext): void { } else { th.setText(label) } - if (width !== undefined) attachColumnResize(th, id, ctx) + attachColumnResize(th, id, ctx) } paintSortIndicators() @@ -187,7 +191,8 @@ export function renderTable(ctx: TableContext): void { /** * Dragging a header's right edge sets that column's width in the view; letting go below - * `HIDE_BELOW` hides the column instead. + * `HIDE_BELOW` hides the column instead. The title's width is only a minimum, since it + * fills whatever the other columns leave, and it never hides. */ function attachColumnResize(th: HTMLElement, id: string, ctx: TableContext): void { th.addClass('pm-table-th-resizable') @@ -197,12 +202,19 @@ function attachColumnResize(th: HTMLElement, id: string, ctx: TableContext): voi down.preventDefault() down.stopPropagation() handle.setPointerCapture(down.pointerId) + const table = th.closest('table') + const headers = Array.from(th.parentElement?.children ?? []).filter((cell) => cell.instanceOf(HTMLElement)) + const startStyles = headers.map((cell) => ({ width: cell.style.width, minWidth: cell.style.minWidth })) + const startTableWidth = table?.offsetWidth ?? 0 + for (const cell of headers) cell.setCssStyles({ width: `${cell.offsetWidth}px`, minWidth: `${cell.offsetWidth}px` }) const startWidth = th.offsetWidth + const isTitle = id === 'title' let width = startWidth th.addClass('is-resizing') const move = (e: PointerEvent): void => { - width = Math.max(0, Math.round(startWidth + e.clientX - down.clientX)) - th.setCssStyles({ width: `${width}px` }) + width = Math.max(isTitle ? TITLE_MIN_WIDTH : 0, Math.round(startWidth + e.clientX - down.clientX)) + th.setCssStyles({ width: `${width}px`, minWidth: `${width}px` }) + table?.setCssStyles({ width: `${startTableWidth + width - startWidth}px` }) th.toggleClass('is-hiding', width < HIDE_BELOW) } const up = (): void => { @@ -210,11 +222,15 @@ function attachColumnResize(th: HTMLElement, id: string, ctx: TableContext): voi handle.removeEventListener('pointerup', up) handle.removeEventListener('pointercancel', up) th.removeClass('is-resizing', 'is-hiding') + headers.forEach((cell, i) => cell.setCssStyles(startStyles[i])) + table?.setCssStyles({ width: '' }) if (width === startWidth) return const list = (ctx.fields.table ??= { visible: [...ctx.columns] }) - if (width < HIDE_BELOW) { + if (!isTitle && width < HIDE_BELOW) { list.visible = ctx.columns.filter((column) => column !== id) if (list.widths) Reflect.deleteProperty(list.widths, id) + } else if (isTitle && width === TITLE_MIN_WIDTH) { + if (list.widths) Reflect.deleteProperty(list.widths, id) } else { list.widths = { ...list.widths, [id]: width } }