Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/marketing/src/constants/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ export interface ChangelogEntry {
link?: string;
}[];
}
export const v3_5_3: ChangelogEntry = {
version: "3.5.3",
date: "2026-05-09",
title: "Pinned & auto-expand resize fixes",
description: "Fixes for nested pinned headers, auto-expand resize math, and viewport-based width caps.",
changes: [
{
type: "bugfix",
description:
"Column drag treats nested headers under a pinned parent as pinned (section detection).",
},
{
type: "bugfix",
description:
"Auto-expand resize syncs leaf widths from the DOM and uses storage headers so drag math matches layout.",
},
{
type: "bugfix",
description:
"Pinned/main auto-expand width caps use the real pinned strip and main body viewports; positive growth clamps only when the section actually widens.",
},
],
};

export const v3_5_2: ChangelogEntry = {
version: "3.5.2",
date: "2026-05-03",
Expand Down Expand Up @@ -1653,6 +1677,7 @@ export const v1_4_4: ChangelogEntry = {

// Array of all changelog entries (newest first)
export const CHANGELOG_ENTRIES: ChangelogEntry[] = [
v3_5_3,
v3_5_2,
v3_4_2,
v3_4_0,
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-table/angular",
"version": "3.5.2",
"version": "3.5.3",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/types/angular/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "simple-table-core",
"version": "3.5.2",
"version": "3.5.3",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/src/index.d.ts",
Expand Down
28 changes: 22 additions & 6 deletions packages/core/src/managers/DragHandlerManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import HeaderObject, { Accessor } from "../types/HeaderObject";
import type { Pinned } from "../types/Pinned";
import { deepClone } from "../utils/generalUtils";
import PreviousValueTracker from "../hooks/previousValue";
import { validateFullHeaderTreeEssentialOrder } from "../utils/pinnedColumnUtils";
import { findParentHeader } from "../utils/collapseUtils";

const REVERT_TO_PREVIOUS_HEADERS_DELAY = 1500;

Expand Down Expand Up @@ -47,9 +49,23 @@ export const setSiblingArray = (
return headers;
};

export const getHeaderSection = (header: HeaderObject): "left" | "main" | "right" => {
if (header.pinned === "left") return "left";
if (header.pinned === "right") return "right";
/** Pinned side of the root column that owns this header (nested leaves inherit parent pin). */
const getRootPinnedForSection = (
header: HeaderObject,
rootHeaders: HeaderObject[],
): Pinned | undefined => {
if (header.pinned) return header.pinned;
const parent = findParentHeader(rootHeaders, header.accessor);
return parent ? getRootPinnedForSection(parent, rootHeaders) : undefined;
};

export const getHeaderSection = (
header: HeaderObject,
rootHeaders: HeaderObject[],
): "left" | "main" | "right" => {
const p = getRootPinnedForSection(header, rootHeaders);
if (p === "left") return "left";
if (p === "right") return "right";
return "main";
};

Expand Down Expand Up @@ -121,7 +137,7 @@ export function insertHeaderAcrossSections({
let emergencyBreak = false;

try {
const hoveredSection = getHeaderSection(hoveredHeader);
const hoveredSection = getHeaderSection(hoveredHeader, newHeaders);

const draggedIndex = newHeaders.findIndex((h) => h.accessor === draggedHeader.accessor);
const hoveredIndex = newHeaders.findIndex((h) => h.accessor === hoveredHeader.accessor);
Expand Down Expand Up @@ -213,8 +229,8 @@ export class DragHandlerManager {

const draggedHeader = this.draggedHeader;

const draggedSection = getHeaderSection(draggedHeader);
const hoveredSection = getHeaderSection(hoveredHeader);
const draggedSection = getHeaderSection(draggedHeader, this.config.headers);
const hoveredSection = getHeaderSection(hoveredHeader, this.config.headers);
const isCrossSectionDrag = draggedSection !== hoveredSection;

let newHeaders: HeaderObject[];
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/utils/headerCell/dragging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ export const attachDragHandlers = (
const draggedHeader = draggedHeaderRef.current;
if (!draggedHeader) return;

const draggedSection = getHeaderSection(draggedHeader);
const hoveredSection = getHeaderSection(header);
const draggedSection = getHeaderSection(draggedHeader, liveHeaders);
const hoveredSection = getHeaderSection(header, liveHeaders);
const isCrossSectionDrag = draggedSection !== hoveredSection;

let newHeaders: HeaderObject[];
Expand Down
90 changes: 60 additions & 30 deletions packages/core/src/utils/headerCell/resizing.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { TABLE_HEADER_CELL_WIDTH_DEFAULT } from "../../consts/general-consts";
import HeaderObject from "../../types/HeaderObject";
import HeaderObject, { Accessor } from "../../types/HeaderObject";
import { getCellId } from "../cellUtils";
import { calculateHeaderContentWidth, removeAllFractionalWidths } from "../headerWidthUtils";
import {
calculateHeaderContentWidth,
getAllVisibleLeafHeaders,
removeAllFractionalWidths,
} from "../headerWidthUtils";
import {
getHeaderIndexPath,
getSiblingArray,
setSiblingArray,
} from "../../managers/DragHandlerManager";
import {
applyColumnAutoFitWithAutoExpand,
handleResizeStart,
} from "../resizeUtils";
import { applyColumnAutoFitWithAutoExpand, handleResizeStart } from "../resizeUtils";
import { updateColumnWidthsInDOM } from "../resizeUtils/domUpdates";
import { HeaderRenderContext } from "./types";
import { addTrackedEventListener, throttle } from "./eventTracking";
Expand All @@ -21,14 +22,41 @@ const getStyleRoot = (context: HeaderRenderContext): ParentNode | null => {
return main.closest(".simple-table-root") ?? main;
};

const findHeaderInTree = (roots: HeaderObject[], accessor: Accessor): HeaderObject | undefined => {
for (const h of roots) {
if (h.accessor === accessor) return h;
if (h.children?.length) {
const found = findHeaderInTree(h.children, accessor);
if (found) return found;
}
}
return undefined;
};

/** Align storage `width` with painted layout so auto-expand resize math matches the viewport. */
const syncVisibleLeafWidthsFromDom = (
roots: HeaderObject[],
collapsedHeaders: Set<Accessor> | undefined,
): void => {
const leaves = getAllVisibleLeafHeaders(roots, collapsedHeaders);
for (const leaf of leaves) {
const cell = document.getElementById(
getCellId({ accessor: leaf.accessor, rowId: "header" }),
);
const w = cell?.offsetWidth;
if (w != null && w > 0) {
leaf.width = w;
}
}
};

export const createResizeHandle = (
header: HeaderObject,
context: HeaderRenderContext,
isLastMainAutoExpandColumn: boolean,
): HTMLElement | null => {
const { columnResizing } = context;
const isSelectionColumn =
header.isSelectionColumn && context.enableRowSelection;
const isSelectionColumn = header.isSelectionColumn && context.enableRowSelection;

if (!columnResizing || isSelectionColumn || isLastMainAutoExpandColumn) {
return null;
Expand All @@ -52,15 +80,27 @@ export const createResizeHandle = (
styleRoot: getStyleRoot(context),
});

/** Auto-expand: mutate canonical storage + DOM-synced widths. Otherwise: effective tree (matches main). */
const resolveResizeHeaders = (): { headers: HeaderObject[]; header: HeaderObject } => {
if (context.autoExpandColumns) {
const storage = context.getHeaders();
syncVisibleLeafWidthsFromDom(storage, context.collapsedHeaders);
const storageHeader = findHeaderInTree(storage, header.accessor) ?? header;
return { headers: storage, header: storageHeader };
}
return { headers: context.headers, header };
};

const performAutoFit = () => {
const headerCell = document.getElementById(
getCellId({ accessor: header.accessor, rowId: "header" }),
);

if (context.autoExpandColumns) {
const { headers: resizeHeaders, header: resizeHeader } = resolveResizeHeaders();
applyColumnAutoFitWithAutoExpand({
header,
headers: context.headers,
header: resizeHeader,
headers: resizeHeaders,
collapsedHeaders: context.collapsedHeaders,
containerWidth: context.containerWidth,
mainBodyRef: context.mainBodyRef,
Expand All @@ -69,7 +109,7 @@ export const createResizeHandle = (
getTargetLeafWidth: (leafHeader) =>
calculateHeaderContentWidth(leafHeader.accessor, measureOptions(leafHeader)),
});
const next = [...context.headers];
const next = [...resizeHeaders];
context.setHeaders(next);
if (context.onColumnWidthChange) {
context.onColumnWidthChange(next);
Expand All @@ -89,11 +129,7 @@ export const createResizeHandle = (
i === headerIndex ? { ...h, width: contentWidth } : h,
);

const updatedHeaders = setSiblingArray(
context.headers,
path,
updatedSiblings,
);
const updatedHeaders = setSiblingArray(context.headers, path, updatedSiblings);

updatedHeaders.forEach((h) => removeAllFractionalWidths(h));

Expand Down Expand Up @@ -131,14 +167,15 @@ export const createResizeHandle = (
)?.offsetWidth;

throttle(() => {
const { headers: resizeHeaders, header: resizeHeader } = resolveResizeHeaders();
handleResizeStart({
autoExpandColumns: context.autoExpandColumns,
collapsedHeaders: context.collapsedHeaders,
containerWidth: context.containerWidth,
event: event,
forceUpdate: context.forceUpdate,
header,
headers: context.headers,
header: resizeHeader,
headers: resizeHeaders,
mainBodyRef: context.mainBodyRef,
onColumnWidthChange: context.onColumnWidthChange,
pinnedLeftRef: context.pinnedLeftRef,
Expand All @@ -151,11 +188,7 @@ export const createResizeHandle = (
}, 10);
};

addTrackedEventListener(
resizeContainer,
"mousedown",
handleMouseDown as EventListener,
);
addTrackedEventListener(resizeContainer, "mousedown", handleMouseDown as EventListener);

const handleTouchStart = (event: Event) => {
const touchEvent = event as globalThis.TouchEvent;
Expand All @@ -164,14 +197,15 @@ export const createResizeHandle = (
)?.offsetWidth;

throttle(() => {
const { headers: resizeHeaders, header: resizeHeader } = resolveResizeHeaders();
handleResizeStart({
autoExpandColumns: context.autoExpandColumns,
collapsedHeaders: context.collapsedHeaders,
containerWidth: context.containerWidth,
event: touchEvent as any,
forceUpdate: context.forceUpdate,
header,
headers: context.headers,
header: resizeHeader,
headers: resizeHeaders,
mainBodyRef: context.mainBodyRef,
onColumnWidthChange: context.onColumnWidthChange,
pinnedLeftRef: context.pinnedLeftRef,
Expand All @@ -186,11 +220,7 @@ export const createResizeHandle = (

addTrackedEventListener(resizeContainer, "touchstart", handleTouchStart);

addTrackedEventListener(
resizeContainer,
"dblclick",
runAutoFitDebounced as EventListener,
);
addTrackedEventListener(resizeContainer, "dblclick", runAutoFitDebounced as EventListener);

return resizeContainer;
};
Loading
Loading