@iyulab/flex-table 0.34.0 → 0.34.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.
- package/CHANGELOG.md +29 -0
- package/README.md +27 -1
- package/dist/{flex-table-D4W1C_QA.js → flex-table-hkmpmDxR.js} +17 -11
- package/dist/flex-table.d.ts +14 -0
- package/dist/flex-table.js +1 -1
- package/dist/react.js +1 -1
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.34.1] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fallback literals for `--u-txt-color-weak` follow `@iyulab/components` 1.40.0.** They are
|
|
8
|
+
only used when that stylesheet is not loaded; with it, nothing changes here.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A column pinned to the right was placed against the scrolled content instead of the
|
|
13
|
+
viewport, so it sat outside the visible area exactly when a pin is needed.** Cells are
|
|
14
|
+
positioned absolutely inside a box as wide as the whole row, so the emitted
|
|
15
|
+
`right: ${-scrollLeft + offset}` measured from the content's right edge rather than the
|
|
16
|
+
viewport's: at `scrollLeft: 0` the column rendered past the right border of the scroll area
|
|
17
|
+
and never came into view. The negative offset also pushed the cell beyond that box, which
|
|
18
|
+
extended the scrollable width — measured in Chromium, `scrollWidth` grew 968 -> 1160 -> 1543
|
|
19
|
+
across three scroll steps, so the horizontal scrollbar could not reach its own end.
|
|
20
|
+
|
|
21
|
+
Right pins are now expressed the same way left pins already were, as a `left` offset that
|
|
22
|
+
tracks the viewport, clamped to the column's natural position. A right-pinned column stays
|
|
23
|
+
against the right edge of the scroll area at every scroll offset, several of them stack in
|
|
24
|
+
column order, and a table whose columns fit without overflowing leaves the column where it
|
|
25
|
+
naturally sits rather than inventing a scrollable area. Header, body and footer cells all
|
|
26
|
+
take the same path.
|
|
27
|
+
|
|
28
|
+
`position: sticky` — the usual way to build a pinned column — is not available in this
|
|
29
|
+
layout: every cell is absolutely positioned for horizontal virtualization, and a sticky box
|
|
30
|
+
needs a flow position to stick relative to.
|
|
31
|
+
|
|
3
32
|
## [0.34.0] - 2026-09-08
|
|
4
33
|
|
|
5
34
|
### Fixed
|
package/README.md
CHANGED
|
@@ -19,7 +19,8 @@ npm install @iyulab/flex-table
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
21
|
```html
|
|
22
|
-
|
|
22
|
+
<!-- The table is its own scroll container, so give it a height — see Sizing below. -->
|
|
23
|
+
<flex-table id="table" style="height: 400px" row-height="32" show-row-numbers></flex-table>
|
|
23
24
|
|
|
24
25
|
<script type="module">
|
|
25
26
|
import '@iyulab/flex-table';
|
|
@@ -67,6 +68,31 @@ npm install @iyulab/flex-table
|
|
|
67
68
|
- **React Wrapper** — `@iyulab/flex-table/react` subpath for idiomatic React usage
|
|
68
69
|
- **ARIA** — `role="grid"`, `aria-sort`, `aria-selected`, `aria-readonly`, `aria-invalid`, `aria-rowcount`, `aria-colcount`
|
|
69
70
|
|
|
71
|
+
## Sizing
|
|
72
|
+
|
|
73
|
+
The table is its own scroll container (`overflow: auto` on the host) and it measures the viewport
|
|
74
|
+
from the host's own height. **Give it a height** — directly, or through a constrained parent:
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
flex-table { height: 400px; }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
/* or let a flex parent constrain it */
|
|
82
|
+
.page { height: 100%; display: flex; flex-direction: column; }
|
|
83
|
+
.page flex-table { flex: 1 1 auto; min-height: 0; }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
⚠ **Without a height constraint the virtual scroll has nothing to virtualise against.** The body
|
|
87
|
+
carries the full content height, so an unconstrained host grows to match it and the viewport
|
|
88
|
+
measurement ends up equal to the content — every row is rendered. Measured with 1,000 rows and no
|
|
89
|
+
height: the element becomes ~32,000px tall and all 1,000 rows are in the DOM. The same data in a
|
|
90
|
+
300px-tall host renders only the visible window (well under a fifth of the rows). That is the
|
|
91
|
+
difference between the "100,000+ rows" claim in Features and a page that stalls.
|
|
92
|
+
|
|
93
|
+
Small fixed data sets are fine unconstrained — the point is that the virtual-scroll guarantee is a
|
|
94
|
+
guarantee about a *constrained* host. `height-model.browser.test.ts` pins both sides of this.
|
|
95
|
+
|
|
70
96
|
## Properties
|
|
71
97
|
|
|
72
98
|
| Property | Attribute | Type | Default | Description |
|
|
@@ -41,12 +41,12 @@ var l = r`
|
|
|
41
41
|
--ft-active-color: var(--u-primary-color, #1976D2);
|
|
42
42
|
--ft-selection-bg: var(--u-primary-bg-color, #E3F2FD);
|
|
43
43
|
--ft-bool-color: var(--u-primary-color, #1976D2);
|
|
44
|
-
--ft-sort-indicator-color: var(--u-txt-color-weak, #
|
|
44
|
+
--ft-sort-indicator-color: var(--u-txt-color-weak, #616161);
|
|
45
45
|
--ft-editor-bg: var(--u-input-bg-color, #FFFFFF);
|
|
46
46
|
/* #999 was 2.85 against --ft-bg (#fff); AA needs 4.5. The dark value (#9aa0a6 on
|
|
47
47
|
#1e1e2e = 6.31) was already fine, so this was a light-only defect. #5f6368 is the
|
|
48
48
|
same grey the sort indicator uses, which keeps the palette to one family. */
|
|
49
|
-
--ft-empty-color: var(--u-txt-color-weak, #
|
|
49
|
+
--ft-empty-color: var(--u-txt-color-weak, #616161);
|
|
50
50
|
|
|
51
51
|
/* 상태 색 — 종전에는 규칙 안에 리터럴로 박혀 있어 소비자가 덮을 경로가 없었고
|
|
52
52
|
테마도 따라오지 않았다(다크에서 라이트용 값 그대로).
|
|
@@ -85,9 +85,9 @@ var l = r`
|
|
|
85
85
|
--ft-active-color: var(--u-primary-color, #1976D2);
|
|
86
86
|
--ft-selection-bg: var(--u-primary-bg-color, #E3F2FD);
|
|
87
87
|
--ft-bool-color: var(--u-primary-color, #1976D2);
|
|
88
|
-
--ft-sort-indicator-color: var(--u-txt-color-weak, #
|
|
88
|
+
--ft-sort-indicator-color: var(--u-txt-color-weak, #616161);
|
|
89
89
|
--ft-editor-bg: var(--u-input-bg-color, #FFFFFF);
|
|
90
|
-
--ft-empty-color: var(--u-txt-color-weak, #
|
|
90
|
+
--ft-empty-color: var(--u-txt-color-weak, #616161);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -106,9 +106,9 @@ var l = r`
|
|
|
106
106
|
--ft-active-color: var(--u-primary-color, #1976D2);
|
|
107
107
|
--ft-selection-bg: var(--u-primary-bg-color, #E3F2FD);
|
|
108
108
|
--ft-bool-color: var(--u-primary-color, #1976D2);
|
|
109
|
-
--ft-sort-indicator-color: var(--u-txt-color-weak, #
|
|
109
|
+
--ft-sort-indicator-color: var(--u-txt-color-weak, #616161);
|
|
110
110
|
--ft-editor-bg: var(--u-input-bg-color, #FFFFFF);
|
|
111
|
-
--ft-empty-color: var(--u-txt-color-weak, #
|
|
111
|
+
--ft-empty-color: var(--u-txt-color-weak, #616161);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/* --- Layout --- */
|
|
@@ -1497,7 +1497,7 @@ async function K(e) {
|
|
|
1497
1497
|
};
|
|
1498
1498
|
}
|
|
1499
1499
|
//#endregion
|
|
1500
|
-
//#region \0@oxc-project+runtime@0.
|
|
1500
|
+
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
|
|
1501
1501
|
function q(e, t, n, r) {
|
|
1502
1502
|
var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
|
|
1503
1503
|
if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
|
|
@@ -2787,6 +2787,12 @@ var J = {
|
|
|
2787
2787
|
for (let r = t.length - 1; r > e; r--) t[r].pinned === "right" && (n += this._getColWidth(t[r]));
|
|
2788
2788
|
return n;
|
|
2789
2789
|
}
|
|
2790
|
+
_getPinnedRightLeft(e) {
|
|
2791
|
+
let t = this.visibleColumns, n = this._colLeftOffsets[e] ?? 0;
|
|
2792
|
+
if (this._viewportWidth <= 0) return n;
|
|
2793
|
+
let r = this._getColWidth(t[e]), i = this._scrollLeft + this._viewportWidth - this._getPinnedRight(e) - r;
|
|
2794
|
+
return Math.min(n, i);
|
|
2795
|
+
}
|
|
2790
2796
|
_renderHeaderCell(e, t) {
|
|
2791
2797
|
let n = e.sortable !== !1, r = this._sortCriteria.find((t) => t.key === e.key), o = this._sortCriteria.length > 1 ? this._sortCriteria.findIndex((t) => t.key === e.key) : -1, s = this._filters.some((t) => t.key === e.key), c = e.pinned === "left", l = e.pinned === "right", u = [
|
|
2792
2798
|
"ft-header-cell",
|
|
@@ -2795,7 +2801,7 @@ var J = {
|
|
|
2795
2801
|
e.headerAlign === "center" ? "ft-header-align-center" : "",
|
|
2796
2802
|
e.headerAlign === "end" ? "ft-header-align-end" : ""
|
|
2797
2803
|
].filter(Boolean).join(" "), d = n ? r ? r.direction === "asc" ? "ascending" : "descending" : "none" : void 0, f = this._getColWidth(e), p = this.headerHeight, m = this._colLeftOffsets[t] ?? 0, h;
|
|
2798
|
-
h = c ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(t)}px; width: ${f}px; height: ${p}px; z-index: 4;` : l ? `position: absolute; top: 0;
|
|
2804
|
+
h = c ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(t)}px; width: ${f}px; height: ${p}px; z-index: 4;` : l ? `position: absolute; top: 0; left: ${this._getPinnedRightLeft(t)}px; width: ${f}px; height: ${p}px; z-index: 4;` : `left: ${m}px; width: ${f}px; height: ${p}px;`;
|
|
2799
2805
|
let g = this._colDrag?.active && this._colDrag.colIndex === t, _ = [...u.split(" "), g ? "ft-col-dragging" : ""].filter(Boolean).join(" "), v = this.columns, y = v.findIndex((t) => t.key === e.key), b = [];
|
|
2800
2806
|
if (y > 0) for (let e = y - 1; e >= 0 && v[e].hidden; e--) b.push(v[e]);
|
|
2801
2807
|
let x = b.length > 0;
|
|
@@ -3802,7 +3808,7 @@ var J = {
|
|
|
3802
3808
|
}
|
|
3803
3809
|
let { start: g, end: _ } = this.visibleRange, v = this._frozenRowCount, y = [];
|
|
3804
3810
|
for (let e = g; e < _; e++) y.push(this._renderRow(e, s, c, l, (e - v) * this.rowHeight));
|
|
3805
|
-
let b = this._renderFillHandle(), x = this._rowDragIndicatorY == null ? "" : i`<div class="ft-row-drop-indicator" style="top:${this._rowDragIndicatorY}px;width:${o
|
|
3811
|
+
let b = this._renderFillHandle(), x = this._rowDragIndicatorY == null ? "" : i`<div class="ft-row-drop-indicator" style="top:${this._rowDragIndicatorY}px;width:${o}px;"></div>`;
|
|
3806
3812
|
return i`
|
|
3807
3813
|
${t}
|
|
3808
3814
|
${n}
|
|
@@ -3852,7 +3858,7 @@ var J = {
|
|
|
3852
3858
|
let u = this.showRowNumbers ? i`<div class="ft-footer-cell ft-row-num"
|
|
3853
3859
|
style="position: absolute; top: 0; left: ${s + c}px; width: 48px; height: ${a}px; z-index: 2;"></div>` : "", d = [];
|
|
3854
3860
|
for (let e of n) {
|
|
3855
|
-
let t = r[e], n = this._getColWidth(t), o = t.pinned === "right" ? `position: absolute; top: 0;
|
|
3861
|
+
let t = r[e], n = this._getColWidth(t), o = t.pinned === "right" ? `position: absolute; top: 0; left: ${this._getPinnedRightLeft(e)}px; width: ${n}px; height: ${a}px; z-index: 2;` : `position: absolute; top: 0; left: ${s + this._getPinnedLeft(e)}px; width: ${n}px; height: ${a}px; z-index: 2;`;
|
|
3856
3862
|
d.push(i`
|
|
3857
3863
|
<div class="ft-footer-cell ft-pinned" style=${o}>
|
|
3858
3864
|
${this.footerData[t.key] ?? ""}</div>
|
|
@@ -3901,7 +3907,7 @@ var J = {
|
|
|
3901
3907
|
}
|
|
3902
3908
|
_renderCell(e, t, n, r) {
|
|
3903
3909
|
let o = this._activeCell?.row === n && this._activeCell?.col === r, s = this._editingCell?.row === n && this._editingCell?.col === r, c = this._selection.isInRange(n, r), l = t.pinned === "left", u = t.pinned === "right", d = l || u, f = this._getColWidth(t), p = this.rowHeight, h = this._colLeftOffsets[r] ?? 0, g;
|
|
3904
|
-
g = l ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(r)}px; width: ${f}px; height: ${p}px; z-index: 2;` : u ? `position: absolute; top: 0;
|
|
3910
|
+
g = l ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(r)}px; width: ${f}px; height: ${p}px; z-index: 2;` : u ? `position: absolute; top: 0; left: ${this._getPinnedRightLeft(r)}px; width: ${f}px; height: ${p}px; z-index: 2;` : `left: ${h}px; width: ${f}px; height: ${p}px;`;
|
|
3905
3911
|
let _ = !this._isCellEditable(t);
|
|
3906
3912
|
if (t.conditionalRules && t.conditionalRules.length > 0) {
|
|
3907
3913
|
let n = e[t.key], r = {};
|
package/dist/flex-table.d.ts
CHANGED
|
@@ -380,6 +380,20 @@ export declare class FlexTable extends LitElement {
|
|
|
380
380
|
private _getPinnedLeft;
|
|
381
381
|
/** Calculate cumulative right offset for a right-pinned column */
|
|
382
382
|
private _getPinnedRight;
|
|
383
|
+
/**
|
|
384
|
+
* X offset for a right-pinned column, in the same content coordinate space the
|
|
385
|
+
* left-pinned branch uses (origin = the row/header box, prefix columns included).
|
|
386
|
+
*
|
|
387
|
+
* A CSS `right` cannot express this. Cells are absolutely positioned inside a box
|
|
388
|
+
* whose width is the full row width, so a fixed `right` rides the content instead of
|
|
389
|
+
* the viewport, and a scroll-compensating negative `right` pushes the cell past that
|
|
390
|
+
* box — which extends the scrollable area, so every scroll widens it again.
|
|
391
|
+
*
|
|
392
|
+
* Sticking is therefore expressed as a `left` clamped to the column's natural offset:
|
|
393
|
+
* the column tracks the viewport's right edge while the content is scrolled past it,
|
|
394
|
+
* and rests in place once the viewport reaches it.
|
|
395
|
+
*/
|
|
396
|
+
private _getPinnedRightLeft;
|
|
383
397
|
private _renderHeaderCell;
|
|
384
398
|
private _onHeaderContextMenu;
|
|
385
399
|
private _showHiddenBefore;
|
package/dist/flex-table.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as e, i as t, n, r, t as i } from "./flex-table-
|
|
1
|
+
import { a as e, i as t, n, r, t as i } from "./flex-table-hkmpmDxR.js";
|
|
2
2
|
export { i as FlexTable, t as RowSelectionState, r as UndoStack, n as exportData, e as renderCell };
|
package/dist/react.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/flex-table",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"description": "A minimalist, input-centric data grid web component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/flex-table.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dev": "vite serve demo",
|
|
44
44
|
"build": "npm run typecheck && vite build && tsc --emitDeclarationOnly",
|
|
45
45
|
"build:demo": "vite build --config vite.config.demo.ts",
|
|
46
|
-
"typecheck": "tsc --noEmit",
|
|
46
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
47
47
|
"test": "vitest run",
|
|
48
48
|
"test:watch": "vitest",
|
|
49
49
|
"test:coverage": "vitest run --coverage",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"@types/react-dom": "^19.2.7",
|
|
92
92
|
"@typescript-eslint/eslint-plugin": "^8.64.0",
|
|
93
93
|
"@typescript-eslint/parser": "^8.64.0",
|
|
94
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
94
95
|
"eslint": "^10.1.0",
|
|
95
96
|
"eslint-plugin-lit": "^2.3.1",
|
|
96
97
|
"jsdom": "^29.1.1",
|
|
@@ -98,7 +99,7 @@
|
|
|
98
99
|
"react": "^19.2.7",
|
|
99
100
|
"react-dom": "^19.2.8",
|
|
100
101
|
"strip-literal": "^3.1.0",
|
|
101
|
-
"typescript": "^
|
|
102
|
+
"typescript": "^6.0.2",
|
|
102
103
|
"vite": "^8.1.4",
|
|
103
104
|
"vitest": "^4.1.10"
|
|
104
105
|
}
|