simple-table-core 3.7.7 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,3 +11,10 @@ export declare const TABLE_HEADER_CELL_WIDTH_DEFAULT = 150;
11
11
  export declare const PINNED_BORDER_WIDTH = 1;
12
12
  export declare const CHART_COLUMN_TYPES: string[];
13
13
  export declare const OPTIMAL_CHART_COLUMN_WIDTH = 150;
14
+ export declare const AUTO_SIZE_HEAD_SAMPLE_SIZE = 25;
15
+ export declare const AUTO_SIZE_STRIDED_SAMPLE_SIZE = 75;
16
+ export declare const AUTO_SIZE_MAX_WIDTH = 500;
17
+ export declare const AUTO_SIZE_OUTLIER_PERCENTILE = 95;
18
+ export declare const AUTO_SIZE_OUTLIER_THRESHOLD = 0.5;
19
+ export declare const AUTO_SIZE_MIN_SAMPLE_FOR_CLIP = 20;
20
+ export declare const AUTO_SIZE_WIDTH_BUFFER = 2;
@@ -14,6 +14,12 @@ export declare class SimpleTableVanilla {
14
14
  private localRows;
15
15
  private headers;
16
16
  private essentialAccessors;
17
+ /** Accessors of leaf columns that should size to content (width:"auto" or autoSizeColumns). */
18
+ private autoSizeAccessors;
19
+ /** Accessors awaiting a content-fit measurement on the next render. */
20
+ private pendingAutoSize;
21
+ /** Guard against re-entrancy while the auto-size pass re-renders. */
22
+ private isAutoSizing;
17
23
  private currentPage;
18
24
  private scrollTop;
19
25
  private scrollDirection;
@@ -215,6 +221,34 @@ export declare class SimpleTableVanilla {
215
221
  private updateAriaLiveRegion;
216
222
  private getRenderContext;
217
223
  private getRenderState;
224
+ /** A leaf column that should size to content (declared with width:"auto"). */
225
+ private isAutoSizeLeaf;
226
+ private computeAutoSizeAccessors;
227
+ private getAutoSizeStyleRoot;
228
+ /**
229
+ * Rows that content-fit ("auto") measurement should sample. With client-side
230
+ * pagination only the current page is rendered, so fit columns to the page's
231
+ * rows rather than the entire dataset — otherwise off-page values inflate
232
+ * every auto column's width.
233
+ */
234
+ private getAutoSizeRows;
235
+ /** Immutably write measured pixel widths into the leaf headers. */
236
+ private applyMeasuredWidths;
237
+ /**
238
+ * Measure and apply content-fit widths for any pending "auto" columns, then
239
+ * re-render once at the final widths. Called at the end of render(); the
240
+ * measurement reads the just-rendered (provisional-width) DOM and the
241
+ * corrective render happens in the same synchronous task, so there is no
242
+ * visible width snap.
243
+ */
244
+ private maybeAutoSizeColumns;
245
+ /**
246
+ * Re-run the content-fit measurement for all auto-size columns. Useful for
247
+ * host frameworks (e.g. React) whose custom renderers mount asynchronously:
248
+ * calling this from a layout effect (pre-paint) re-measures once the real
249
+ * renderer DOM is present, so the column fits accurately without flicker.
250
+ */
251
+ refitAutoSizeColumns(): void;
218
252
  private render;
219
253
  update(config: Partial<SimpleTableConfig>): void;
220
254
  /** @deprecated Use {@link update} — same behavior. */
@@ -1,4 +1,4 @@
1
- import HeaderObject from "../types/HeaderObject";
1
+ import HeaderObject, { Accessor } from "../types/HeaderObject";
2
2
  interface AutoScaleConfig {
3
3
  autoExpandColumns: boolean;
4
4
  containerWidth: number;
@@ -8,6 +8,13 @@ interface AutoScaleConfig {
8
8
  current: HTMLDivElement | null;
9
9
  };
10
10
  isResizing?: boolean;
11
+ /**
12
+ * Currently collapsed header accessors. Auto-scale must only account for the
13
+ * leaf columns that are actually visible for the current collapsed state —
14
+ * otherwise it sums the widths of hidden child columns and under-expands,
15
+ * leaving empty space on the right.
16
+ */
17
+ collapsedHeaders?: Set<Accessor>;
11
18
  }
12
19
  type HeaderUpdateCallback = (headers: HeaderObject[]) => void;
13
20
  export declare const applyAutoScaleToHeaders: (headers: HeaderObject[], options: AutoScaleConfig) => HeaderObject[];
@@ -10,6 +10,12 @@ import { QuickFilterGetter } from "./QuickFilterTypes";
10
10
  export type Accessor = keyof Row | string;
11
11
  export type ColumnType = "string" | "number" | "boolean" | "date" | "enum" | "lineAreaChart" | "barChart" | "other";
12
12
  export type ShowWhen = "parentCollapsed" | "parentExpanded" | "always";
13
+ /**
14
+ * Controls what an auto-sized column (`width: "auto"`) fits to:
15
+ * - "content" (default): fit the wider of the header and the sampled cell content
16
+ * - "header": fit the header label only (ignore cell content)
17
+ */
18
+ export type AutoSizeMode = "content" | "header";
13
19
  export interface ChartOptions {
14
20
  min?: number;
15
21
  max?: number;
@@ -57,6 +63,13 @@ type HeaderObject = {
57
63
  accessor: Accessor;
58
64
  aggregation?: AggregationConfig;
59
65
  align?: "left" | "center" | "right";
66
+ /**
67
+ * When `width` is the string `"auto"`, the column is sized to fit its content
68
+ * on load (and re-fit when row data changes). `autoSizeMode` controls whether
69
+ * it fits the header + cells (`"content"`, default) or the header label only
70
+ * (`"header"`). `minWidth` / `maxWidth` clamp the computed width.
71
+ */
72
+ autoSizeMode?: AutoSizeMode;
60
73
  cellRenderer?: CellRenderer;
61
74
  chartOptions?: ChartOptions;
62
75
  children?: HeaderObject[];
@@ -92,6 +105,10 @@ type HeaderObject = {
92
105
  valueFormatter?: ValueFormatter;
93
106
  valueGetter?: ValueGetter;
94
107
  showWhen?: ShowWhen;
108
+ /**
109
+ * Column width. A number or px/fr/% string sets a fixed/proportional width.
110
+ * The special value `"auto"` sizes the column to fit its content (see `autoSizeMode`).
111
+ */
95
112
  width: number | string;
96
113
  maxWidth?: number | string;
97
114
  };
@@ -1,6 +1,6 @@
1
1
  import type Row from "../../types/Row";
2
2
  import type TableRow from "../../types/TableRow";
3
- import { AbsoluteBodyCell, CellRenderContext } from "./types";
3
+ import { AbsoluteBodyCell, CellRegistryEntry, CellRenderContext } from "./types";
4
4
  export interface CellLiveRef {
5
5
  row: Row;
6
6
  tableRow: TableRow;
@@ -8,6 +8,7 @@ export interface CellLiveRef {
8
8
  }
9
9
  export declare const cellLiveRefMap: WeakMap<HTMLElement, CellLiveRef>;
10
10
  export declare const untrackCellByRow: (rowId: string, cellElement: HTMLElement) => void;
11
+ export declare const unregisterCellFromRegistry: (cellElement: HTMLElement, cellRegistry?: Map<string, CellRegistryEntry>) => void;
11
12
  export declare const createBodyCellElement: (cell: AbsoluteBodyCell, context: CellRenderContext) => HTMLElement;
12
13
  export declare const updateBodyCellPosition: (cellElement: HTMLElement, cell: AbsoluteBodyCell) => void;
13
14
  export declare const updateBodyCellElement: (cellElement: HTMLElement, cell: AbsoluteBodyCell, context: CellRenderContext) => void;
@@ -1,4 +1,29 @@
1
1
  import HeaderObject, { Accessor } from "../types/HeaderObject";
2
+ /**
3
+ * Build the set of row indices to sample for auto-size measurement.
4
+ * Hybrid strategy: the first `headSize` rows (always include the top / initial
5
+ * viewport) plus `stridedSize` rows spread at an even stride across the rest of
6
+ * the dataset, so wide values deeper in the data are caught without scanning
7
+ * every row. Strided (not random) keeps the result deterministic across renders.
8
+ */
9
+ export declare const buildHybridSampleIndices: (rowCount: number, headSize: number, stridedSize: number) => number[];
10
+ /** Linear-interpolated percentile (0-100) of a numeric array. */
11
+ export declare const percentileOf: (values: number[], percentile: number) => number;
12
+ /**
13
+ * Choose a representative "content width" from sampled cell widths, clipping
14
+ * outliers only when they clearly stand apart from the bulk of the data.
15
+ *
16
+ * - Uses the true max when the column is uniform (max close to the percentile),
17
+ * so nothing legitimate gets truncated.
18
+ * - Falls back to the percentile width only when `max > p * (1 + threshold)` and
19
+ * there are enough samples to trust the percentile, so a single rogue 2000px
20
+ * row can't define the whole column.
21
+ */
22
+ export declare const selectContentWidthWithOutlierClip: (widths: number[], options?: {
23
+ percentile?: number;
24
+ threshold?: number;
25
+ minSampleForClip?: number;
26
+ }) => number;
2
27
  /**
3
28
  * Find all leaf headers (headers without children) in a header tree
4
29
  * Takes collapsed state into account - when a header is collapsed, only returns
@@ -9,6 +34,8 @@ export declare const findLeafHeaders: (header: HeaderObject, collapsedHeaders?:
9
34
  export declare const DEFAULT_FR_PX = 150;
10
35
  /** Default total table width used when normalizing fr/% if container width unknown */
11
36
  export declare const DEFAULT_TABLE_WIDTH = 800;
37
+ /** True when a header's width requests content-based auto-sizing (`width: "auto"`). */
38
+ export declare const isAutoWidth: (header: HeaderObject) => boolean;
12
39
  /**
13
40
  * Normalize header widths so that fr and % are converted to pixels.
14
41
  * Call this as soon as headers are received so the rest of the code can assume numeric widths.
@@ -52,7 +79,20 @@ export declare const calculateHeaderContentWidth: (accessor: Accessor, options?:
52
79
  rows?: any[];
53
80
  header?: HeaderObject;
54
81
  maxWidth?: number;
82
+ /** Leading rows always measured (default AUTO_SIZE_HEAD_SAMPLE_SIZE) */
83
+ headSampleSize?: number;
84
+ /** Rows sampled at an even stride across the rest (default AUTO_SIZE_STRIDED_SAMPLE_SIZE) */
85
+ stridedSampleSize?: number;
86
+ /** Back-compat: when set, used as the head sample size (strided defaults to 0) */
55
87
  sampleSize?: number;
88
+ /** Outlier clip percentile (default AUTO_SIZE_OUTLIER_PERCENTILE) */
89
+ outlierPercentile?: number;
90
+ /** Outlier clip threshold fraction (default AUTO_SIZE_OUTLIER_THRESHOLD) */
91
+ outlierThreshold?: number;
92
+ /** When "header", ignore cell content and fit the header label only */
93
+ autoSizeMode?: "content" | "header";
94
+ /** Theme passed to a custom cellRenderer during measurement */
95
+ theme?: any;
56
96
  /** Scope `.st-cell-content` font/padding sampling to this table instance */
57
97
  styleRoot?: ParentNode | null;
58
98
  }) => number;
@@ -131,6 +131,21 @@ export declare const CopySelectionWithoutHeaders: {
131
131
  canvasElement: HTMLElement;
132
132
  }) => Promise<void>;
133
133
  };
134
+ /**
135
+ * Regression: with `selectableCells` enabled, the cell mousedown handler used to
136
+ * `preventDefault()` and start a cell selection even when the press landed on an
137
+ * <a> rendered inside the cell. That cancelled the link's native default (href
138
+ * navigation) and forced a body re-render that swallowed the pending click.
139
+ * A link inside a cell must stay clickable and must not trigger cell selection.
140
+ */
141
+ export declare const LinkInsideCellStaysClickable: {
142
+ render: () => HTMLDivElement & {
143
+ _table?: import("../../src/index").SimpleTableVanilla | undefined;
144
+ };
145
+ play: ({ canvasElement }: {
146
+ canvasElement: HTMLElement;
147
+ }) => Promise<void>;
148
+ };
134
149
  export declare const ShiftArrowExtendsSelection: {
135
150
  render: () => HTMLDivElement & {
136
151
  _table?: import("../../src/index").SimpleTableVanilla | undefined;
@@ -109,6 +109,14 @@ export declare const ExcludeFromRenderNotInColumnEditor: {
109
109
  canvasElement: HTMLElement;
110
110
  }) => Promise<void>;
111
111
  };
112
+ export declare const ExcludeFromRenderDoesNotInflateRowWidth: {
113
+ render: () => HTMLDivElement & {
114
+ _table?: import("../../src/index").SimpleTableVanilla | undefined;
115
+ };
116
+ play: ({ canvasElement }: {
117
+ canvasElement: HTMLElement;
118
+ }) => Promise<void>;
119
+ };
112
120
  export declare const ColumnEditorCustomText: {
113
121
  render: () => HTMLDivElement & {
114
122
  _table?: import("../../src/index").SimpleTableVanilla | undefined;
@@ -80,6 +80,16 @@ export declare const AutoExpandGroupedExpandCollapse: {
80
80
  canvasElement: HTMLElement;
81
81
  }) => Promise<void>;
82
82
  };
83
+ export declare const AutoExpandCollapsibleColumnsFillContainer: {
84
+ tags: string[];
85
+ parameters: {
86
+ tags: string[];
87
+ };
88
+ render: () => HTMLElement;
89
+ play: ({ canvasElement }: {
90
+ canvasElement: HTMLElement;
91
+ }) => Promise<void>;
92
+ };
83
93
  export declare const AutoExpandWithNestedGrouping: {
84
94
  parameters: {
85
95
  tags: string[];
@@ -0,0 +1,258 @@
1
+ /**
2
+ * AUTO-SIZE COLUMNS TESTS
3
+ *
4
+ * Comprehensive coverage for content-fit columns (`width: "auto"`):
5
+ * - shrink / grow, per-column `maxWidth` cap and `minWidth` floor
6
+ * - `autoSizeMode: "header"`
7
+ * - custom (vanilla) renderers, valueFormatter, valueGetter, chart columns
8
+ * - outliers (single giant value clipped) vs. a legitimate wide minority (kept)
9
+ * - very large datasets (50k rows) with bounded sampling
10
+ * - re-fit on data change, sort stability, empty data, mixed/pinned layouts
11
+ * - a no-flicker stability guard
12
+ * - an exhaustive deterministic unit block for the sampling/percentile/outlier helpers
13
+ *
14
+ * Width assertions rely on real layout, so these run under the Storybook
15
+ * test-runner (Playwright). The helper unit block is layout-independent.
16
+ */
17
+ import { SimpleTableVanilla } from "../../src/index";
18
+ import type { Meta } from "@storybook/html";
19
+ declare const meta: Meta;
20
+ export default meta;
21
+ export declare const AutoWidthShrinksNarrowColumn: {
22
+ parameters: {
23
+ tags: string[];
24
+ };
25
+ render: () => HTMLDivElement & {
26
+ _table?: SimpleTableVanilla | undefined;
27
+ };
28
+ play: ({ canvasElement }: {
29
+ canvasElement: HTMLElement;
30
+ }) => Promise<void>;
31
+ };
32
+ export declare const AutoWidthGrowsForLongContent: {
33
+ parameters: {
34
+ tags: string[];
35
+ };
36
+ render: () => HTMLDivElement & {
37
+ _table?: SimpleTableVanilla | undefined;
38
+ };
39
+ play: ({ canvasElement }: {
40
+ canvasElement: HTMLElement;
41
+ }) => Promise<void>;
42
+ };
43
+ export declare const AutoWidthRespectsMaxWidth: {
44
+ parameters: {
45
+ tags: string[];
46
+ };
47
+ render: () => HTMLDivElement & {
48
+ _table?: SimpleTableVanilla | undefined;
49
+ };
50
+ play: ({ canvasElement }: {
51
+ canvasElement: HTMLElement;
52
+ }) => Promise<void>;
53
+ };
54
+ export declare const AutoWidthRespectsMinWidth: {
55
+ parameters: {
56
+ tags: string[];
57
+ };
58
+ render: () => HTMLDivElement & {
59
+ _table?: SimpleTableVanilla | undefined;
60
+ };
61
+ play: ({ canvasElement }: {
62
+ canvasElement: HTMLElement;
63
+ }) => Promise<void>;
64
+ };
65
+ export declare const AutoSizeHeaderModeIgnoresCells: {
66
+ parameters: {
67
+ tags: string[];
68
+ };
69
+ render: () => HTMLDivElement & {
70
+ _table?: SimpleTableVanilla | undefined;
71
+ };
72
+ play: ({ canvasElement }: {
73
+ canvasElement: HTMLElement;
74
+ }) => Promise<void>;
75
+ };
76
+ export declare const AutoSizeMeasuresCustomRenderer: {
77
+ parameters: {
78
+ tags: string[];
79
+ };
80
+ render: () => HTMLDivElement & {
81
+ _table?: SimpleTableVanilla | undefined;
82
+ };
83
+ play: ({ canvasElement }: {
84
+ canvasElement: HTMLElement;
85
+ }) => Promise<void>;
86
+ };
87
+ export declare const AutoSizeMeasuresFormattedValue: {
88
+ parameters: {
89
+ tags: string[];
90
+ };
91
+ render: () => HTMLDivElement & {
92
+ _table?: SimpleTableVanilla | undefined;
93
+ };
94
+ play: ({ canvasElement }: {
95
+ canvasElement: HTMLElement;
96
+ }) => Promise<void>;
97
+ };
98
+ export declare const AutoSizeMeasuresValueGetter: {
99
+ parameters: {
100
+ tags: string[];
101
+ };
102
+ render: () => HTMLDivElement & {
103
+ _table?: SimpleTableVanilla | undefined;
104
+ };
105
+ play: ({ canvasElement }: {
106
+ canvasElement: HTMLElement;
107
+ }) => Promise<void>;
108
+ };
109
+ export declare const AutoSizeChartColumnUsesChartMinimum: {
110
+ parameters: {
111
+ tags: string[];
112
+ };
113
+ render: () => HTMLDivElement & {
114
+ _table?: SimpleTableVanilla | undefined;
115
+ };
116
+ play: ({ canvasElement }: {
117
+ canvasElement: HTMLElement;
118
+ }) => Promise<void>;
119
+ };
120
+ export declare const AutoSizeClipsSingleOutlier: {
121
+ parameters: {
122
+ tags: string[];
123
+ };
124
+ render: () => HTMLDivElement & {
125
+ _table?: SimpleTableVanilla | undefined;
126
+ };
127
+ play: ({ canvasElement }: {
128
+ canvasElement: HTMLElement;
129
+ }) => Promise<void>;
130
+ };
131
+ export declare const AutoSizeKeepsLegitimateWideMinority: {
132
+ parameters: {
133
+ tags: string[];
134
+ };
135
+ render: () => HTMLDivElement & {
136
+ _table?: SimpleTableVanilla | undefined;
137
+ };
138
+ play: ({ canvasElement }: {
139
+ canvasElement: HTMLElement;
140
+ }) => Promise<void>;
141
+ };
142
+ export declare const AutoSizeClipsFewOutliers: {
143
+ parameters: {
144
+ tags: string[];
145
+ };
146
+ render: () => HTMLDivElement & {
147
+ _table?: SimpleTableVanilla | undefined;
148
+ };
149
+ play: ({ canvasElement }: {
150
+ canvasElement: HTMLElement;
151
+ }) => Promise<void>;
152
+ };
153
+ export declare const AutoSize50kUniformShort: {
154
+ parameters: {
155
+ tags: string[];
156
+ };
157
+ render: () => HTMLDivElement & {
158
+ _table?: SimpleTableVanilla | undefined;
159
+ };
160
+ play: ({ canvasElement }: {
161
+ canvasElement: HTMLElement;
162
+ }) => Promise<void>;
163
+ };
164
+ export declare const AutoSize50kDeepOutlierStaysCompact: {
165
+ parameters: {
166
+ tags: string[];
167
+ };
168
+ render: () => HTMLDivElement & {
169
+ _table?: SimpleTableVanilla | undefined;
170
+ };
171
+ play: ({ canvasElement }: {
172
+ canvasElement: HTMLElement;
173
+ }) => Promise<void>;
174
+ };
175
+ export declare const AutoSize50kWidespreadLongGrows: {
176
+ parameters: {
177
+ tags: string[];
178
+ };
179
+ render: () => HTMLDivElement & {
180
+ _table?: SimpleTableVanilla | undefined;
181
+ };
182
+ play: ({ canvasElement }: {
183
+ canvasElement: HTMLElement;
184
+ }) => Promise<void>;
185
+ };
186
+ export declare const AutoSizeRefitsOnDataChange: {
187
+ parameters: {
188
+ tags: string[];
189
+ };
190
+ render: () => HTMLDivElement & {
191
+ _table?: SimpleTableVanilla | undefined;
192
+ };
193
+ play: ({ canvasElement }: {
194
+ canvasElement: HTMLElement;
195
+ }) => Promise<void>;
196
+ };
197
+ export declare const AutoSizeStableAcrossSort: {
198
+ parameters: {
199
+ tags: string[];
200
+ };
201
+ render: () => HTMLDivElement & {
202
+ _table?: SimpleTableVanilla | undefined;
203
+ };
204
+ play: ({ canvasElement }: {
205
+ canvasElement: HTMLElement;
206
+ }) => Promise<void>;
207
+ };
208
+ export declare const AutoSizeEmptyDataFitsHeader: {
209
+ parameters: {
210
+ tags: string[];
211
+ };
212
+ render: () => HTMLDivElement & {
213
+ _table?: SimpleTableVanilla | undefined;
214
+ };
215
+ play: ({ canvasElement }: {
216
+ canvasElement: HTMLElement;
217
+ }) => Promise<void>;
218
+ };
219
+ export declare const AutoSizeMixedColumnsLayout: {
220
+ parameters: {
221
+ tags: string[];
222
+ };
223
+ render: () => HTMLDivElement & {
224
+ _table?: SimpleTableVanilla | undefined;
225
+ };
226
+ play: ({ canvasElement }: {
227
+ canvasElement: HTMLElement;
228
+ }) => Promise<void>;
229
+ };
230
+ export declare const AutoSizePinnedColumn: {
231
+ parameters: {
232
+ tags: string[];
233
+ };
234
+ render: () => HTMLDivElement & {
235
+ _table?: SimpleTableVanilla | undefined;
236
+ };
237
+ play: ({ canvasElement }: {
238
+ canvasElement: HTMLElement;
239
+ }) => Promise<void>;
240
+ };
241
+ export declare const AutoSizeNoFlicker: {
242
+ parameters: {
243
+ tags: string[];
244
+ };
245
+ render: () => HTMLDivElement & {
246
+ _table?: SimpleTableVanilla | undefined;
247
+ };
248
+ play: ({ canvasElement }: {
249
+ canvasElement: HTMLElement;
250
+ }) => Promise<void>;
251
+ };
252
+ export declare const SamplingAndOutlierHelpers: {
253
+ parameters: {
254
+ tags: string[];
255
+ };
256
+ render: () => HTMLDivElement;
257
+ play: () => Promise<void>;
258
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DEFERRED HEADERS TESTS
3
+ * Regression tests for populating `defaultHeaders` after mount (the table
4
+ * mounts with an empty header array, then `update({ defaultHeaders })` supplies
5
+ * the real columns — e.g. from an async fetch / useEffect).
6
+ *
7
+ * The header band must become visible once the columns arrive. Previously the
8
+ * DimensionManager kept maxHeaderDepth/calculatedHeaderHeight at 0 (computed
9
+ * from the empty mount-time headers), so the header row rendered at 0px height
10
+ * even though the body and the header cells existed.
11
+ */
12
+ import type { Meta } from "@storybook/html";
13
+ import { SimpleTableVanilla } from "../../src/index";
14
+ declare const meta: Meta;
15
+ export default meta;
16
+ export declare const PopulateHeadersAfterMount: {
17
+ render: () => HTMLDivElement & {
18
+ _table?: SimpleTableVanilla | undefined;
19
+ };
20
+ play: ({ canvasElement }: {
21
+ canvasElement: HTMLElement;
22
+ }) => Promise<void>;
23
+ };