argus-table 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +66 -2
- package/dist/_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.js +8 -0
- package/dist/argus-table.js +517 -0
- package/dist/argus-table.js.map +1 -0
- package/dist/cdn/argus-table.js +4569 -0
- package/dist/cdn/argus-table.js.map +1 -0
- package/dist/controllers/pin-scroll-controller.js +78 -0
- package/dist/controllers/pin-scroll-controller.js.map +1 -0
- package/dist/controllers/resize-drag.js +44 -0
- package/dist/controllers/resize-drag.js.map +1 -0
- package/dist/controllers/table-controller.js +24 -0
- package/dist/controllers/table-controller.js.map +1 -0
- package/dist/controllers/virtual-controller.js +83 -0
- package/dist/controllers/virtual-controller.js.map +1 -0
- package/dist/core/src/columns/resolve-columns.js +62 -0
- package/dist/core/src/columns/resolve-columns.js.map +1 -0
- package/dist/core/src/state/controllable.js +31 -0
- package/dist/core/src/state/controllable.js.map +1 -0
- package/dist/core/src/table/create-table.js +185 -0
- package/dist/core/src/table/create-table.js.map +1 -0
- package/dist/core/src/virtual/row-pool.js +64 -0
- package/dist/core/src/virtual/row-pool.js.map +1 -0
- package/dist/custom-elements.json +1191 -0
- package/dist/events.js +22 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +3 -0
- package/dist/render/body.js +48 -0
- package/dist/render/body.js.map +1 -0
- package/dist/render/cells.js +27 -0
- package/dist/render/cells.js.map +1 -0
- package/dist/render/content.js +35 -0
- package/dist/render/content.js.map +1 -0
- package/dist/render/header.js +61 -0
- package/dist/render/header.js.map +1 -0
- package/dist/styles/base.js +11 -0
- package/dist/styles/base.js.map +1 -0
- package/package.json +62 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/controllers/pin-scroll-controller.ts
|
|
2
|
+
/** How long after the last sideways scroll pinned cells stay sticky. */
|
|
3
|
+
var SETTLE_MS = 150;
|
|
4
|
+
/**
|
|
5
|
+
* Keeps pinned body cells in place without making them sticky while the table
|
|
6
|
+
* only scrolls up and down.
|
|
7
|
+
*
|
|
8
|
+
* Safari gives every sticky element a compositing layer of its own and moves
|
|
9
|
+
* each one on every scroll frame; with a sticky cell per pinned column per row
|
|
10
|
+
* that is dozens of layers, and vertical scrolling stutters (ADR 0001, "Pinned
|
|
11
|
+
* columns in Safari"). At rest, pinned body cells are plain cells shifted by the
|
|
12
|
+
* horizontal scroll offset (`--_sx`, and `--_max-x` for the end side), which
|
|
13
|
+
* only changes when the table scrolls sideways. While it does, the scroller
|
|
14
|
+
* carries `data-hscroll` and the cells are sticky, so they don't lag behind
|
|
15
|
+
* the scroll; once it settles they go back to the transform, at the same place.
|
|
16
|
+
* It also sizes the pinned edges' shadow overlays (`.pin-shadow`) to the
|
|
17
|
+
* scroller's visible area.
|
|
18
|
+
*/
|
|
19
|
+
var PinScrollController = class {
|
|
20
|
+
#scroller;
|
|
21
|
+
#attached;
|
|
22
|
+
#left = 0;
|
|
23
|
+
#timer;
|
|
24
|
+
#resize;
|
|
25
|
+
constructor(host, scroller) {
|
|
26
|
+
this.#scroller = scroller;
|
|
27
|
+
host.addController(this);
|
|
28
|
+
}
|
|
29
|
+
hostConnected() {
|
|
30
|
+
this.#attach();
|
|
31
|
+
}
|
|
32
|
+
hostUpdated() {
|
|
33
|
+
this.#attach();
|
|
34
|
+
}
|
|
35
|
+
hostDisconnected() {
|
|
36
|
+
this.#attached?.removeEventListener("scroll", this.#onScroll);
|
|
37
|
+
this.#resize?.disconnect();
|
|
38
|
+
clearTimeout(this.#timer);
|
|
39
|
+
this.#attached = void 0;
|
|
40
|
+
}
|
|
41
|
+
/** Re-reads how far the table scrolls sideways: call when column widths change. */
|
|
42
|
+
measure() {
|
|
43
|
+
const s = this.#attached;
|
|
44
|
+
if (!s) return;
|
|
45
|
+
s.style.setProperty("--_max-x", `${String(Math.max(0, s.scrollWidth - s.clientWidth))}px`);
|
|
46
|
+
for (const shadow of s.parentNode?.querySelectorAll(":scope > .pin-shadow") ?? []) {
|
|
47
|
+
shadow.style.setProperty("--_view-h", `${String(s.clientHeight)}px`);
|
|
48
|
+
shadow.style.setProperty("--_sb-x", `${String(s.offsetWidth - s.clientWidth)}px`);
|
|
49
|
+
}
|
|
50
|
+
this.#left = s.scrollLeft;
|
|
51
|
+
s.style.setProperty("--_sx", `${String(this.#left)}px`);
|
|
52
|
+
}
|
|
53
|
+
#attach() {
|
|
54
|
+
const s = this.#scroller();
|
|
55
|
+
if (!s || s === this.#attached) return;
|
|
56
|
+
this.#attached = s;
|
|
57
|
+
s.addEventListener("scroll", this.#onScroll, { passive: true });
|
|
58
|
+
this.#resize ??= new ResizeObserver(() => {
|
|
59
|
+
this.measure();
|
|
60
|
+
});
|
|
61
|
+
this.#resize.observe(s);
|
|
62
|
+
this.measure();
|
|
63
|
+
}
|
|
64
|
+
#onScroll = () => {
|
|
65
|
+
const s = this.#attached;
|
|
66
|
+
if (!s || s.scrollLeft === this.#left) return;
|
|
67
|
+
this.#left = s.scrollLeft;
|
|
68
|
+
s.toggleAttribute("data-hscroll", true);
|
|
69
|
+
clearTimeout(this.#timer);
|
|
70
|
+
this.#timer = setTimeout(() => {
|
|
71
|
+
this.measure();
|
|
72
|
+
s.removeAttribute("data-hscroll");
|
|
73
|
+
}, SETTLE_MS);
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export { PinScrollController };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=pin-scroll-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pin-scroll-controller.js","names":[],"sources":["../../src/controllers/pin-scroll-controller.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\n\n/** How long after the last sideways scroll pinned cells stay sticky. */\nconst SETTLE_MS = 150;\n\n/**\n * Keeps pinned body cells in place without making them sticky while the table\n * only scrolls up and down.\n *\n * Safari gives every sticky element a compositing layer of its own and moves\n * each one on every scroll frame; with a sticky cell per pinned column per row\n * that is dozens of layers, and vertical scrolling stutters (ADR 0001, \"Pinned\n * columns in Safari\"). At rest, pinned body cells are plain cells shifted by the\n * horizontal scroll offset (`--_sx`, and `--_max-x` for the end side), which\n * only changes when the table scrolls sideways. While it does, the scroller\n * carries `data-hscroll` and the cells are sticky, so they don't lag behind\n * the scroll; once it settles they go back to the transform, at the same place.\n * It also sizes the pinned edges' shadow overlays (`.pin-shadow`) to the\n * scroller's visible area.\n */\nexport class PinScrollController implements ReactiveController {\n readonly #scroller: () => HTMLElement | null;\n #attached: HTMLElement | undefined;\n #left = 0;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #resize: ResizeObserver | undefined;\n\n constructor(host: ReactiveControllerHost, scroller: () => HTMLElement | null) {\n this.#scroller = scroller;\n host.addController(this);\n }\n\n hostConnected(): void {\n // After a move in the DOM; before the first render there is no scroller yet.\n this.#attach();\n }\n\n hostUpdated(): void {\n this.#attach();\n }\n\n hostDisconnected(): void {\n this.#attached?.removeEventListener('scroll', this.#onScroll);\n this.#resize?.disconnect();\n clearTimeout(this.#timer);\n this.#attached = undefined;\n }\n\n /** Re-reads how far the table scrolls sideways: call when column widths change. */\n measure(): void {\n const s = this.#attached;\n if (!s) return;\n s.style.setProperty('--_max-x', `${String(Math.max(0, s.scrollWidth - s.clientWidth))}px`);\n // The edge shadows sit over the scroller, clear of its scrollbars.\n for (const shadow of s.parentNode?.querySelectorAll<HTMLElement>(':scope > .pin-shadow') ??\n []) {\n shadow.style.setProperty('--_view-h', `${String(s.clientHeight)}px`);\n shadow.style.setProperty('--_sb-x', `${String(s.offsetWidth - s.clientWidth)}px`);\n }\n this.#left = s.scrollLeft;\n s.style.setProperty('--_sx', `${String(this.#left)}px`);\n }\n\n #attach(): void {\n const s = this.#scroller();\n if (!s || s === this.#attached) return;\n this.#attached = s;\n s.addEventListener('scroll', this.#onScroll, { passive: true });\n this.#resize ??= new ResizeObserver(() => {\n this.measure();\n });\n this.#resize.observe(s);\n this.measure();\n }\n\n readonly #onScroll = () => {\n const s = this.#attached;\n if (!s || s.scrollLeft === this.#left) return;\n this.#left = s.scrollLeft;\n s.toggleAttribute('data-hscroll', true);\n clearTimeout(this.#timer);\n this.#timer = setTimeout(() => {\n this.measure();\n s.removeAttribute('data-hscroll');\n }, SETTLE_MS);\n };\n}\n"],"mappings":";;AAGA,IAAM,YAAY;;;;;;;;;;;;;;;;AAiBlB,IAAa,sBAAb,MAA+D;CAC7D;CACA;CACA,QAAQ;CACR;CACA;CAEA,YAAY,MAA8B,UAAoC;EAC5E,KAAK,YAAY;EACjB,KAAK,cAAc,IAAI;CACzB;CAEA,gBAAsB;EAEpB,KAAK,QAAQ;CACf;CAEA,cAAoB;EAClB,KAAK,QAAQ;CACf;CAEA,mBAAyB;EACvB,KAAK,WAAW,oBAAoB,UAAU,KAAK,SAAS;EAC5D,KAAK,SAAS,WAAW;EACzB,aAAa,KAAK,MAAM;EACxB,KAAK,YAAY,KAAA;CACnB;;CAGA,UAAgB;EACd,MAAM,IAAI,KAAK;EACf,IAAI,CAAC,GAAG;EACR,EAAE,MAAM,YAAY,YAAY,GAAG,OAAO,KAAK,IAAI,GAAG,EAAE,cAAc,EAAE,WAAW,CAAC,EAAE,GAAG;EAEzF,KAAK,MAAM,UAAU,EAAE,YAAY,iBAA8B,sBAAsB,KACrF,CAAC,GAAG;GACJ,OAAO,MAAM,YAAY,aAAa,GAAG,OAAO,EAAE,YAAY,EAAE,GAAG;GACnE,OAAO,MAAM,YAAY,WAAW,GAAG,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,GAAG;EAClF;EACA,KAAK,QAAQ,EAAE;EACf,EAAE,MAAM,YAAY,SAAS,GAAG,OAAO,KAAK,KAAK,EAAE,GAAG;CACxD;CAEA,UAAgB;EACd,MAAM,IAAI,KAAK,UAAU;EACzB,IAAI,CAAC,KAAK,MAAM,KAAK,WAAW;EAChC,KAAK,YAAY;EACjB,EAAE,iBAAiB,UAAU,KAAK,WAAW,EAAE,SAAS,KAAK,CAAC;EAC9D,KAAK,YAAY,IAAI,qBAAqB;GACxC,KAAK,QAAQ;EACf,CAAC;EACD,KAAK,QAAQ,QAAQ,CAAC;EACtB,KAAK,QAAQ;CACf;CAEA,kBAA2B;EACzB,MAAM,IAAI,KAAK;EACf,IAAI,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO;EACvC,KAAK,QAAQ,EAAE;EACf,EAAE,gBAAgB,gBAAgB,IAAI;EACtC,aAAa,KAAK,MAAM;EACxB,KAAK,SAAS,iBAAiB;GAC7B,KAAK,QAAQ;GACb,EAAE,gBAAgB,cAAc;EAClC,GAAG,SAAS;CACd;AACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/controllers/resize-drag.ts
|
|
2
|
+
/**
|
|
3
|
+
* One pointer drag on a resize handle. The pointer is captured, so the drag keeps
|
|
4
|
+
* working outside the handle, and moves are batched to one `onWidth` call per
|
|
5
|
+
* animation frame (TanStack's `onChange` mode: the width updates while dragging).
|
|
6
|
+
*
|
|
7
|
+
* `direction` is 1 in LTR and -1 in RTL: the handle sits on the inline-end edge,
|
|
8
|
+
* so moving toward the inline end always grows the column.
|
|
9
|
+
*/
|
|
10
|
+
function startResizeDrag(handle, down, startWidth, direction, onWidth) {
|
|
11
|
+
const startX = down.clientX;
|
|
12
|
+
let frame = 0;
|
|
13
|
+
let pending;
|
|
14
|
+
const flush = () => {
|
|
15
|
+
frame = 0;
|
|
16
|
+
if (pending !== void 0) onWidth(pending);
|
|
17
|
+
pending = void 0;
|
|
18
|
+
};
|
|
19
|
+
const move = (event) => {
|
|
20
|
+
pending = startWidth + (event.clientX - startX) * direction;
|
|
21
|
+
frame ||= requestAnimationFrame(flush);
|
|
22
|
+
};
|
|
23
|
+
const end = (event) => {
|
|
24
|
+
if (event.type === "pointerup") move(event);
|
|
25
|
+
if (frame) cancelAnimationFrame(frame);
|
|
26
|
+
flush();
|
|
27
|
+
handle.removeAttribute("data-active");
|
|
28
|
+
handle.removeEventListener("pointermove", move);
|
|
29
|
+
handle.removeEventListener("pointerup", end);
|
|
30
|
+
handle.removeEventListener("pointercancel", end);
|
|
31
|
+
if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId);
|
|
32
|
+
};
|
|
33
|
+
down.preventDefault();
|
|
34
|
+
try {
|
|
35
|
+
handle.setPointerCapture(down.pointerId);
|
|
36
|
+
} catch {}
|
|
37
|
+
handle.setAttribute("data-active", "");
|
|
38
|
+
handle.addEventListener("pointermove", move);
|
|
39
|
+
handle.addEventListener("pointerup", end);
|
|
40
|
+
handle.addEventListener("pointercancel", end);
|
|
41
|
+
}
|
|
42
|
+
export { startResizeDrag };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=resize-drag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize-drag.js","names":[],"sources":["../../src/controllers/resize-drag.ts"],"sourcesContent":["/**\n * One pointer drag on a resize handle. The pointer is captured, so the drag keeps\n * working outside the handle, and moves are batched to one `onWidth` call per\n * animation frame (TanStack's `onChange` mode: the width updates while dragging).\n *\n * `direction` is 1 in LTR and -1 in RTL: the handle sits on the inline-end edge,\n * so moving toward the inline end always grows the column.\n */\nexport function startResizeDrag(\n handle: HTMLElement,\n down: PointerEvent,\n startWidth: number,\n direction: 1 | -1,\n onWidth: (width: number) => void,\n): void {\n const startX = down.clientX;\n let frame = 0;\n let pending: number | undefined;\n\n const flush = () => {\n frame = 0;\n if (pending !== undefined) onWidth(pending);\n pending = undefined;\n };\n const move = (event: PointerEvent) => {\n pending = startWidth + (event.clientX - startX) * direction;\n frame ||= requestAnimationFrame(flush);\n };\n const end = (event: PointerEvent) => {\n if (event.type === 'pointerup') move(event);\n if (frame) cancelAnimationFrame(frame);\n flush();\n handle.removeAttribute('data-active');\n handle.removeEventListener('pointermove', move);\n handle.removeEventListener('pointerup', end);\n handle.removeEventListener('pointercancel', end);\n if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId);\n };\n\n down.preventDefault();\n try {\n handle.setPointerCapture(down.pointerId);\n } catch {\n // Synthetic or already released pointers can't be captured; moves on the handle still work.\n }\n handle.setAttribute('data-active', '');\n handle.addEventListener('pointermove', move);\n handle.addEventListener('pointerup', end);\n handle.addEventListener('pointercancel', end);\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,gBACd,QACA,MACA,YACA,WACA,SACM;CACN,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ;CACZ,IAAI;CAEJ,MAAM,cAAc;EAClB,QAAQ;EACR,IAAI,YAAY,KAAA,GAAW,QAAQ,OAAO;EAC1C,UAAU,KAAA;CACZ;CACA,MAAM,QAAQ,UAAwB;EACpC,UAAU,cAAc,MAAM,UAAU,UAAU;EAClD,UAAU,sBAAsB,KAAK;CACvC;CACA,MAAM,OAAO,UAAwB;EACnC,IAAI,MAAM,SAAS,aAAa,KAAK,KAAK;EAC1C,IAAI,OAAO,qBAAqB,KAAK;EACrC,MAAM;EACN,OAAO,gBAAgB,aAAa;EACpC,OAAO,oBAAoB,eAAe,IAAI;EAC9C,OAAO,oBAAoB,aAAa,GAAG;EAC3C,OAAO,oBAAoB,iBAAiB,GAAG;EAC/C,IAAI,OAAO,kBAAkB,MAAM,SAAS,GAAG,OAAO,sBAAsB,MAAM,SAAS;CAC7F;CAEA,KAAK,eAAe;CACpB,IAAI;EACF,OAAO,kBAAkB,KAAK,SAAS;CACzC,QAAQ,CAER;CACA,OAAO,aAAa,eAAe,EAAE;CACrC,OAAO,iBAAiB,eAAe,IAAI;CAC3C,OAAO,iBAAiB,aAAa,GAAG;CACxC,OAAO,iBAAiB,iBAAiB,GAAG;AAC9C"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createTableModel } from "../core/src/table/create-table.js";
|
|
2
|
+
// src/controllers/table-controller.ts
|
|
3
|
+
/**
|
|
4
|
+
* Owns the table model (core's TanStack adapter). The element pushes its
|
|
5
|
+
* properties in from `willUpdate`; render functions read `model`. It needs no
|
|
6
|
+
* lifecycle hooks yet: the model is pull-based and owns no state.
|
|
7
|
+
*/
|
|
8
|
+
var TableController = class {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.model = createTableModel({
|
|
11
|
+
columns: [],
|
|
12
|
+
data: [],
|
|
13
|
+
getRowId: void 0,
|
|
14
|
+
sorting: [],
|
|
15
|
+
columnSizing: {}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
update(options) {
|
|
19
|
+
this.model.update(options);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export { TableController };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=table-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-controller.js","names":[],"sources":["../../src/controllers/table-controller.ts"],"sourcesContent":["import { createTableModel, type TableModel, type TableModelOptions } from '@argus-table/core';\nimport type { CellContent } from '../types.js';\n\n/**\n * Owns the table model (core's TanStack adapter). The element pushes its\n * properties in from `willUpdate`; render functions read `model`. It needs no\n * lifecycle hooks yet: the model is pull-based and owns no state.\n */\nexport class TableController<T> {\n readonly model: TableModel<T, CellContent>;\n\n constructor() {\n this.model = createTableModel<T, CellContent>({\n columns: [],\n data: [],\n getRowId: undefined,\n sorting: [],\n columnSizing: {},\n });\n }\n\n update(options: Partial<TableModelOptions<T, CellContent>>): void {\n this.model.update(options);\n }\n}\n"],"mappings":";;;;;;;AAQA,IAAa,kBAAb,MAAgC;CAG9B,cAAc;EACZ,KAAK,QAAQ,iBAAiC;GAC5C,SAAS,CAAC;GACV,MAAM,CAAC;GACP,UAAU,KAAA;GACV,SAAS,CAAC;GACV,cAAc,CAAC;EACjB,CAAC;CACH;CAEA,OAAO,SAA2D;EAChE,KAAK,MAAM,OAAO,OAAO;CAC3B;AACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { RowPool } from "../core/src/virtual/row-pool.js";
|
|
2
|
+
import { VirtualizerController } from "@tanstack/lit-virtual";
|
|
3
|
+
// src/controllers/virtual-controller.ts
|
|
4
|
+
/**
|
|
5
|
+
* Row virtualization (TanStack Virtual) plus the row pool: which rows render,
|
|
6
|
+
* where, and which pool slot's DOM each one uses. The virtualizer registers itself
|
|
7
|
+
* with the host and requests updates on scroll.
|
|
8
|
+
*/
|
|
9
|
+
var VirtualController = class {
|
|
10
|
+
#virtual;
|
|
11
|
+
#pool = new RowPool();
|
|
12
|
+
#config = {
|
|
13
|
+
count: 0,
|
|
14
|
+
rowHeight: 40,
|
|
15
|
+
overscan: 6,
|
|
16
|
+
rowIdAt: String
|
|
17
|
+
};
|
|
18
|
+
constructor(host, getScrollElement) {
|
|
19
|
+
this.#virtual = new VirtualizerController(host, {
|
|
20
|
+
count: 0,
|
|
21
|
+
getScrollElement,
|
|
22
|
+
estimateSize: () => this.#config.rowHeight,
|
|
23
|
+
overscan: this.#config.overscan
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
configure(next) {
|
|
27
|
+
const prev = this.#config;
|
|
28
|
+
this.#config = next;
|
|
29
|
+
if (next.count !== prev.count || next.overscan !== prev.overscan || next.rowHeight !== prev.rowHeight) {
|
|
30
|
+
this.#virtual.setOptions({
|
|
31
|
+
count: next.count,
|
|
32
|
+
overscan: next.overscan
|
|
33
|
+
});
|
|
34
|
+
if (next.rowHeight !== prev.rowHeight) this.#virtual.getVirtualizer().measure();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Computes the rendered window and updates the pool. Call once per render.
|
|
39
|
+
*/
|
|
40
|
+
computeWindow() {
|
|
41
|
+
const v = this.#virtual.getVirtualizer();
|
|
42
|
+
const items = v.getVirtualItems();
|
|
43
|
+
const byId = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const item of items) byId.set(this.#config.rowIdAt(item.index), {
|
|
45
|
+
index: item.index,
|
|
46
|
+
start: item.start
|
|
47
|
+
});
|
|
48
|
+
const { keep } = this.#config;
|
|
49
|
+
this.#pool.pin(keep?.id);
|
|
50
|
+
if (keep && !byId.has(keep.id)) byId.set(keep.id, {
|
|
51
|
+
index: keep.index,
|
|
52
|
+
start: keep.index * this.#config.rowHeight
|
|
53
|
+
});
|
|
54
|
+
const rows = [];
|
|
55
|
+
for (const { id, slot } of this.#pool.update(byId.keys())) {
|
|
56
|
+
const item = byId.get(id);
|
|
57
|
+
if (item) rows.push({
|
|
58
|
+
slot,
|
|
59
|
+
...item
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const range = v.range;
|
|
63
|
+
return {
|
|
64
|
+
rows,
|
|
65
|
+
totalSize: v.getTotalSize(),
|
|
66
|
+
range: range ? {
|
|
67
|
+
start: range.startIndex,
|
|
68
|
+
end: range.endIndex
|
|
69
|
+
} : null
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Number of pool slots created so far: bounded by the most rows rendered at once. */
|
|
73
|
+
get poolSize() {
|
|
74
|
+
return this.#pool.size;
|
|
75
|
+
}
|
|
76
|
+
/** `'start'` puts the row at the top; `'auto'` scrolls only as far as needed to show it. */
|
|
77
|
+
scrollToIndex(index, align = "start") {
|
|
78
|
+
this.#virtual.getVirtualizer().scrollToIndex(index, { align });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export { VirtualController };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=virtual-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virtual-controller.js","names":[],"sources":["../../src/controllers/virtual-controller.ts"],"sourcesContent":["import type { ReactiveControllerHost } from 'lit';\nimport { VirtualizerController } from '@tanstack/lit-virtual';\nimport { RowPool } from '@argus-table/core';\n\n/** A rendered body row: its pool slot, its index in the row model and its offset. */\nexport interface WindowRow {\n slot: number;\n index: number;\n start: number;\n}\n\nexport interface VirtualWindow {\n /** Rendered rows, in pool order (ADR 0001 addendum). */\n rows: WindowRow[];\n /** Height of all rows in px. */\n totalSize: number;\n /** Visible rows, without overscan; `null` before the first layout. */\n range: { start: number; end: number } | null;\n}\n\nexport interface VirtualConfig {\n count: number;\n rowHeight: number;\n overscan: number;\n /** Row id at an index of the row model; pool slots follow row ids. */\n rowIdAt: (index: number) => string;\n /**\n * The row to keep rendered outside the range (the one holding the grid's tab\n * stop), so its DOM and focus survive scrolling. `undefined`: none.\n */\n keep?: { id: string; index: number } | undefined;\n}\n\n/**\n * Row virtualization (TanStack Virtual) plus the row pool: which rows render,\n * where, and which pool slot's DOM each one uses. The virtualizer registers itself\n * with the host and requests updates on scroll.\n */\nexport class VirtualController {\n readonly #virtual: VirtualizerController<HTMLElement, Element>;\n readonly #pool = new RowPool();\n #config: VirtualConfig = { count: 0, rowHeight: 40, overscan: 6, rowIdAt: String };\n\n constructor(host: ReactiveControllerHost, getScrollElement: () => HTMLElement | null) {\n this.#virtual = new VirtualizerController<HTMLElement, Element>(host, {\n count: 0,\n getScrollElement,\n estimateSize: () => this.#config.rowHeight,\n overscan: this.#config.overscan,\n });\n }\n\n configure(next: VirtualConfig): void {\n const prev = this.#config;\n this.#config = next;\n if (\n next.count !== prev.count ||\n next.overscan !== prev.overscan ||\n next.rowHeight !== prev.rowHeight\n ) {\n this.#virtual.setOptions({ count: next.count, overscan: next.overscan });\n if (next.rowHeight !== prev.rowHeight) this.#virtual.getVirtualizer().measure();\n }\n }\n\n /**\n * Computes the rendered window and updates the pool. Call once per render.\n */\n computeWindow(): VirtualWindow {\n const v = this.#virtual.getVirtualizer();\n const items = v.getVirtualItems();\n const byId = new Map<string, { index: number; start: number }>();\n for (const item of items) {\n byId.set(this.#config.rowIdAt(item.index), { index: item.index, start: item.start });\n }\n const { keep } = this.#config;\n this.#pool.pin(keep?.id);\n if (keep && !byId.has(keep.id)) {\n // Fixed row heights: the virtualizer would place it here too.\n byId.set(keep.id, { index: keep.index, start: keep.index * this.#config.rowHeight });\n }\n const rows: WindowRow[] = [];\n for (const { id, slot } of this.#pool.update(byId.keys())) {\n const item = byId.get(id);\n if (item) rows.push({ slot, ...item });\n }\n const range = v.range;\n return {\n rows,\n totalSize: v.getTotalSize(),\n range: range ? { start: range.startIndex, end: range.endIndex } : null,\n };\n }\n\n /** Number of pool slots created so far: bounded by the most rows rendered at once. */\n get poolSize(): number {\n return this.#pool.size;\n }\n\n /** `'start'` puts the row at the top; `'auto'` scrolls only as far as needed to show it. */\n scrollToIndex(index: number, align: 'start' | 'auto' = 'start'): void {\n this.#virtual.getVirtualizer().scrollToIndex(index, { align });\n }\n}\n"],"mappings":";;;;;;;;AAsCA,IAAa,oBAAb,MAA+B;CAC7B;CACA,QAAiB,IAAI,QAAQ;CAC7B,UAAyB;EAAE,OAAO;EAAG,WAAW;EAAI,UAAU;EAAG,SAAS;CAAO;CAEjF,YAAY,MAA8B,kBAA4C;EACpF,KAAK,WAAW,IAAI,sBAA4C,MAAM;GACpE,OAAO;GACP;GACA,oBAAoB,KAAK,QAAQ;GACjC,UAAU,KAAK,QAAQ;EACzB,CAAC;CACH;CAEA,UAAU,MAA2B;EACnC,MAAM,OAAO,KAAK;EAClB,KAAK,UAAU;EACf,IACE,KAAK,UAAU,KAAK,SACpB,KAAK,aAAa,KAAK,YACvB,KAAK,cAAc,KAAK,WACxB;GACA,KAAK,SAAS,WAAW;IAAE,OAAO,KAAK;IAAO,UAAU,KAAK;GAAS,CAAC;GACvE,IAAI,KAAK,cAAc,KAAK,WAAW,KAAK,SAAS,eAAe,CAAC,CAAC,QAAQ;EAChF;CACF;;;;CAKA,gBAA+B;EAC7B,MAAM,IAAI,KAAK,SAAS,eAAe;EACvC,MAAM,QAAQ,EAAE,gBAAgB;EAChC,MAAM,uBAAO,IAAI,IAA8C;EAC/D,KAAK,MAAM,QAAQ,OACjB,KAAK,IAAI,KAAK,QAAQ,QAAQ,KAAK,KAAK,GAAG;GAAE,OAAO,KAAK;GAAO,OAAO,KAAK;EAAM,CAAC;EAErF,MAAM,EAAE,SAAS,KAAK;EACtB,KAAK,MAAM,IAAI,MAAM,EAAE;EACvB,IAAI,QAAQ,CAAC,KAAK,IAAI,KAAK,EAAE,GAE3B,KAAK,IAAI,KAAK,IAAI;GAAE,OAAO,KAAK;GAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ;EAAU,CAAC;EAErF,MAAM,OAAoB,CAAC;EAC3B,KAAK,MAAM,EAAE,IAAI,UAAU,KAAK,MAAM,OAAO,KAAK,KAAK,CAAC,GAAG;GACzD,MAAM,OAAO,KAAK,IAAI,EAAE;GACxB,IAAI,MAAM,KAAK,KAAK;IAAE;IAAM,GAAG;GAAK,CAAC;EACvC;EACA,MAAM,QAAQ,EAAE;EAChB,OAAO;GACL;GACA,WAAW,EAAE,aAAa;GAC1B,OAAO,QAAQ;IAAE,OAAO,MAAM;IAAY,KAAK,MAAM;GAAS,IAAI;EACpE;CACF;;CAGA,IAAI,WAAmB;EACrB,OAAO,KAAK,MAAM;CACpB;;CAGA,cAAc,OAAe,QAA0B,SAAe;EACpE,KAAK,SAAS,eAAe,CAAC,CAAC,cAAc,OAAO,EAAE,MAAM,CAAC;CAC/D;AACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
var pathCache = /* @__PURE__ */ new Map();
|
|
2
|
+
/**
|
|
3
|
+
* Reads a dotted property path such as `'customer.name'`. Returns `undefined`
|
|
4
|
+
* as soon as a step is `null` or `undefined`, like optional chaining.
|
|
5
|
+
*/
|
|
6
|
+
function readPath(source, path) {
|
|
7
|
+
let keys = pathCache.get(path);
|
|
8
|
+
if (!keys) {
|
|
9
|
+
keys = path.split(".");
|
|
10
|
+
pathCache.set(path, keys);
|
|
11
|
+
}
|
|
12
|
+
let value = source;
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
if (value === null || value === void 0) return void 0;
|
|
15
|
+
value = value[key];
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function finite(value) {
|
|
20
|
+
return value !== void 0 && Number.isFinite(value) ? value : void 0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes `ColumnDef`s: checks ids, applies defaults and clamps widths.
|
|
24
|
+
*
|
|
25
|
+
* @throws if an id is empty or used twice, because state and events are keyed by id.
|
|
26
|
+
*/
|
|
27
|
+
function resolveColumns(defs) {
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
return defs.map((def, index) => {
|
|
30
|
+
if (typeof def.id !== "string" || def.id === "") throw new Error(`argus-table: column at index ${String(index)} needs a non-empty string id.`);
|
|
31
|
+
if (seen.has(def.id)) throw new Error(`argus-table: column id '${def.id}' is used more than once.`);
|
|
32
|
+
seen.add(def.id);
|
|
33
|
+
const minWidth = Math.max(0, finite(def.minWidth) ?? 40);
|
|
34
|
+
const maxWidth = Math.max(minWidth, finite(def.maxWidth) ?? Number.POSITIVE_INFINITY);
|
|
35
|
+
const clamp = (px) => Math.min(maxWidth, Math.max(minWidth, px));
|
|
36
|
+
const pin = def.pin === "start" || def.pin === "end" ? def.pin : void 0;
|
|
37
|
+
const { width } = def;
|
|
38
|
+
const flex = pin === void 0 && typeof width === "object" && Number.isFinite(width.flex) && width.flex > 0 ? width.flex : void 0;
|
|
39
|
+
const base = flex !== void 0 ? minWidth : clamp(finite(width) ?? 150);
|
|
40
|
+
const { accessor } = def;
|
|
41
|
+
const getValue = typeof accessor === "function" ? accessor : typeof accessor === "string" ? (row) => readPath(row, accessor) : () => void 0;
|
|
42
|
+
return {
|
|
43
|
+
id: def.id,
|
|
44
|
+
index,
|
|
45
|
+
header: def.header,
|
|
46
|
+
cell: def.cell,
|
|
47
|
+
getValue,
|
|
48
|
+
hasAccessor: accessor !== void 0,
|
|
49
|
+
width: base,
|
|
50
|
+
flex,
|
|
51
|
+
minWidth,
|
|
52
|
+
maxWidth,
|
|
53
|
+
pin,
|
|
54
|
+
sortable: def.sortable === true,
|
|
55
|
+
resizable: def.resizable !== false,
|
|
56
|
+
align: def.align ?? "start"
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export { readPath, resolveColumns };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=resolve-columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-columns.js","names":[],"sources":["../../../../../core/src/columns/resolve-columns.ts"],"sourcesContent":["import type { ColumnAlign, ColumnDef, ColumnPin } from '../types.js';\n\n/** Width used when a column sets none. */\nexport const DEFAULT_WIDTH = 150;\n/** Lower bound used when a column sets no `minWidth`. */\nexport const DEFAULT_MIN_WIDTH = 40;\n\n/** A column after defaults, bounds and the accessor have been resolved. */\nexport interface ResolvedColumn<TData, TContent> {\n id: string;\n /** Position in the `columns` array. */\n index: number;\n header: ColumnDef<TData, TContent>['header'];\n cell: ColumnDef<TData, TContent>['cell'];\n /** Reads the cell value; returns `undefined` when the column has no accessor. */\n getValue: (row: TData, index: number) => unknown;\n hasAccessor: boolean;\n /** Base width in px. For a flex column this is its minimum track size. */\n width: number;\n /** Share of free space for `{ flex }` columns, otherwise `undefined`. Pinned columns never flex. */\n flex: number | undefined;\n minWidth: number;\n maxWidth: number;\n pin: ColumnPin | undefined;\n sortable: boolean;\n resizable: boolean;\n align: ColumnAlign;\n}\n\nconst pathCache = new Map<string, readonly string[]>();\n\n/**\n * Reads a dotted property path such as `'customer.name'`. Returns `undefined`\n * as soon as a step is `null` or `undefined`, like optional chaining.\n */\nexport function readPath(source: unknown, path: string): unknown {\n let keys = pathCache.get(path);\n if (!keys) {\n keys = path.split('.');\n pathCache.set(path, keys);\n }\n let value: unknown = source;\n for (const key of keys) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[key];\n }\n return value;\n}\n\nfunction finite(value: number | undefined): number | undefined {\n return value !== undefined && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Normalizes `ColumnDef`s: checks ids, applies defaults and clamps widths.\n *\n * @throws if an id is empty or used twice, because state and events are keyed by id.\n */\nexport function resolveColumns<TData, TContent>(\n defs: readonly ColumnDef<TData, TContent>[],\n): ResolvedColumn<TData, TContent>[] {\n const seen = new Set<string>();\n return defs.map((def, index) => {\n if (typeof def.id !== 'string' || def.id === '') {\n throw new Error(`argus-table: column at index ${String(index)} needs a non-empty string id.`);\n }\n if (seen.has(def.id)) {\n throw new Error(`argus-table: column id '${def.id}' is used more than once.`);\n }\n seen.add(def.id);\n\n const minWidth = Math.max(0, finite(def.minWidth) ?? DEFAULT_MIN_WIDTH);\n const maxWidth = Math.max(minWidth, finite(def.maxWidth) ?? Number.POSITIVE_INFINITY);\n const clamp = (px: number) => Math.min(maxWidth, Math.max(minWidth, px));\n\n const pin = def.pin === 'start' || def.pin === 'end' ? def.pin : undefined;\n const { width } = def;\n // Pinned columns need a known width for their sticky offsets, so they never flex.\n const flex =\n pin === undefined &&\n typeof width === 'object' &&\n Number.isFinite(width.flex) &&\n width.flex > 0\n ? width.flex\n : undefined;\n const base = flex !== undefined ? minWidth : clamp(finite(width as number) ?? DEFAULT_WIDTH);\n\n const { accessor } = def;\n const getValue: ResolvedColumn<TData, TContent>['getValue'] =\n typeof accessor === 'function'\n ? accessor\n : typeof accessor === 'string'\n ? (row) => readPath(row, accessor)\n : () => undefined;\n\n return {\n id: def.id,\n index,\n header: def.header,\n cell: def.cell,\n getValue,\n hasAccessor: accessor !== undefined,\n width: base,\n flex,\n minWidth,\n maxWidth,\n pin,\n sortable: def.sortable === true,\n resizable: def.resizable !== false,\n align: def.align ?? 'start',\n };\n });\n}\n"],"mappings":"AA6BA,IAAM,4BAAY,IAAI,IAA+B;;;;;AAMrD,SAAgB,SAAS,QAAiB,MAAuB;CAC/D,IAAI,OAAO,UAAU,IAAI,IAAI;CAC7B,IAAI,CAAC,MAAM;EACT,OAAO,KAAK,MAAM,GAAG;EACrB,UAAU,IAAI,MAAM,IAAI;CAC1B;CACA,IAAI,QAAiB;CACrB,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAA;EAClD,QAAS,MAAkC;CAC7C;CACA,OAAO;AACT;AAEA,SAAS,OAAO,OAA+C;CAC7D,OAAO,UAAU,KAAA,KAAa,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACjE;;;;;;AAOA,SAAgB,eACd,MACmC;CACnC,MAAM,uBAAO,IAAI,IAAY;CAC7B,OAAO,KAAK,KAAK,KAAK,UAAU;EAC9B,IAAI,OAAO,IAAI,OAAO,YAAY,IAAI,OAAO,IAC3C,MAAM,IAAI,MAAM,gCAAgC,OAAO,KAAK,EAAE,8BAA8B;EAE9F,IAAI,KAAK,IAAI,IAAI,EAAE,GACjB,MAAM,IAAI,MAAM,2BAA2B,IAAI,GAAG,0BAA0B;EAE9E,KAAK,IAAI,IAAI,EAAE;EAEf,MAAM,WAAW,KAAK,IAAI,GAAG,OAAO,IAAI,QAAQ,KAAA,EAAsB;EACtE,MAAM,WAAW,KAAK,IAAI,UAAU,OAAO,IAAI,QAAQ,KAAK,OAAO,iBAAiB;EACpF,MAAM,SAAS,OAAe,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,EAAE,CAAC;EAEvE,MAAM,MAAM,IAAI,QAAQ,WAAW,IAAI,QAAQ,QAAQ,IAAI,MAAM,KAAA;EACjE,MAAM,EAAE,UAAU;EAElB,MAAM,OACJ,QAAQ,KAAA,KACR,OAAO,UAAU,YACjB,OAAO,SAAS,MAAM,IAAI,KAC1B,MAAM,OAAO,IACT,MAAM,OACN,KAAA;EACN,MAAM,OAAO,SAAS,KAAA,IAAY,WAAW,MAAM,OAAO,KAAe,KAAA,GAAkB;EAE3F,MAAM,EAAE,aAAa;EACrB,MAAM,WACJ,OAAO,aAAa,aAChB,WACA,OAAO,aAAa,YACjB,QAAQ,SAAS,KAAK,QAAQ,UACzB,KAAA;EAEd,OAAO;GACL,IAAI,IAAI;GACR;GACA,QAAQ,IAAI;GACZ,MAAM,IAAI;GACV;GACA,aAAa,aAAa,KAAA;GAC1B,OAAO;GACP;GACA;GACA;GACA;GACA,UAAU,IAAI,aAAa;GAC3B,WAAW,IAAI,cAAc;GAC7B,OAAO,IAAI,SAAS;EACtB;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ../core/src/state/controllable.ts
|
|
2
|
+
function controllable(initial) {
|
|
3
|
+
let value = initial;
|
|
4
|
+
let controlled = false;
|
|
5
|
+
return {
|
|
6
|
+
get value() {
|
|
7
|
+
return value;
|
|
8
|
+
},
|
|
9
|
+
get controlled() {
|
|
10
|
+
return controlled;
|
|
11
|
+
},
|
|
12
|
+
set(next) {
|
|
13
|
+
if (next === void 0) {
|
|
14
|
+
controlled = false;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
controlled = true;
|
|
18
|
+
value = next;
|
|
19
|
+
},
|
|
20
|
+
request(next) {
|
|
21
|
+
if (!controlled) value = next;
|
|
22
|
+
return {
|
|
23
|
+
value: next,
|
|
24
|
+
applied: !controlled
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export { controllable };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=controllable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controllable.js","names":[],"sources":["../../../../../core/src/state/controllable.ts"],"sourcesContent":["/**\n * State that works like a form input: uncontrolled until the host sets it,\n * controlled from then on. See `docs/plan/code-architecture/04-state-and-events.md`.\n *\n * - **Uncontrolled:** `request()` applies the change. The caller then fires the change event.\n * - **Controlled:** `request()` changes nothing and returns the proposed value for the\n * event. The value only changes when the host calls `set()` again.\n * - `set(undefined)` hands ownership back to the table and keeps the last value.\n */\nexport interface Controllable<T> {\n /** The current value. */\n readonly value: T;\n /** `true` once the host has set a value (and not handed it back with `undefined`). */\n readonly controlled: boolean;\n /** Called when the host sets the property. */\n set(value: T | undefined): void;\n /**\n * Called when the table wants to change the state, for example after a header click.\n * Returns the proposed value and whether it was applied.\n */\n request(next: T): { value: T; applied: boolean };\n}\n\nexport function controllable<T>(initial: T): Controllable<T> {\n let value = initial;\n let controlled = false;\n return {\n get value() {\n return value;\n },\n get controlled() {\n return controlled;\n },\n set(next) {\n if (next === undefined) {\n controlled = false;\n return;\n }\n controlled = true;\n value = next;\n },\n request(next) {\n if (!controlled) value = next;\n return { value: next, applied: !controlled };\n },\n };\n}\n"],"mappings":";AAuBA,SAAgB,aAAgB,SAA6B;CAC3D,IAAI,QAAQ;CACZ,IAAI,aAAa;CACjB,OAAO;EACL,IAAI,QAAQ;GACV,OAAO;EACT;EACA,IAAI,aAAa;GACf,OAAO;EACT;EACA,IAAI,MAAM;GACR,IAAI,SAAS,KAAA,GAAW;IACtB,aAAa;IACb;GACF;GACA,aAAa;GACb,QAAQ;EACV;EACA,QAAQ,MAAM;GACZ,IAAI,CAAC,YAAY,QAAQ;GACzB,OAAO;IAAE,OAAO;IAAM,SAAS,CAAC;GAAW;EAC7C;CACF;AACF"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { resolveColumns } from "../columns/resolve-columns.js";
|
|
2
|
+
import { columnPinningFeature, columnSizingFeature, constructTable, createSortedRowModel, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures } from "@tanstack/table-core";
|
|
3
|
+
import { storeReactivityBindings } from "@tanstack/table-core/store-reactivity-bindings";
|
|
4
|
+
// ../core/src/table/create-table.ts
|
|
5
|
+
var features = tableFeatures({
|
|
6
|
+
coreReactivityFeature: storeReactivityBindings(),
|
|
7
|
+
columnPinningFeature,
|
|
8
|
+
columnSizingFeature,
|
|
9
|
+
rowSortingFeature,
|
|
10
|
+
sortedRowModel: createSortedRowModel(),
|
|
11
|
+
sortFns: {
|
|
12
|
+
alphanumeric: sortFn_alphanumeric,
|
|
13
|
+
basic: sortFn_basic,
|
|
14
|
+
datetime: sortFn_datetime,
|
|
15
|
+
text: sortFn_text
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
var defaultRowId = (_row, index) => String(index);
|
|
19
|
+
function toTanStack(column) {
|
|
20
|
+
return {
|
|
21
|
+
id: column.id,
|
|
22
|
+
accessorFn: column.getValue,
|
|
23
|
+
size: column.width,
|
|
24
|
+
minSize: column.minWidth,
|
|
25
|
+
maxSize: column.maxWidth,
|
|
26
|
+
enableSorting: column.sortable,
|
|
27
|
+
sortUndefined: "last"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates the table model over TanStack Table. It owns no state: sorting and sizing
|
|
32
|
+
* come in through `update()`, and `nextSorting()`/`nextSizing()` only propose
|
|
33
|
+
* changes, so the caller decides (controlled or uncontrolled) whether to apply them.
|
|
34
|
+
*/
|
|
35
|
+
function createTableModel(initial) {
|
|
36
|
+
let options = initial;
|
|
37
|
+
let resolved = resolveColumns(options.columns);
|
|
38
|
+
let byId = new Map(resolved.map((c) => [c.id, c]));
|
|
39
|
+
let tanStackColumns = resolved.map(toTanStack);
|
|
40
|
+
let views;
|
|
41
|
+
const rowViews = /* @__PURE__ */ new WeakMap();
|
|
42
|
+
let indexById;
|
|
43
|
+
const tableOptions = () => ({
|
|
44
|
+
features,
|
|
45
|
+
columns: tanStackColumns,
|
|
46
|
+
data: options.data,
|
|
47
|
+
getRowId: options.getRowId ?? defaultRowId,
|
|
48
|
+
state: {
|
|
49
|
+
sorting: options.sorting,
|
|
50
|
+
columnSizing: options.columnSizing,
|
|
51
|
+
columnPinning: {
|
|
52
|
+
start: resolved.filter((c) => c.pin === "start").map((c) => c.id),
|
|
53
|
+
end: resolved.filter((c) => c.pin === "end").map((c) => c.id)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
onSortingChange: () => void 0,
|
|
57
|
+
onColumnSizingChange: () => void 0,
|
|
58
|
+
onColumnPinningChange: () => void 0
|
|
59
|
+
});
|
|
60
|
+
const table = constructTable(tableOptions());
|
|
61
|
+
const rows = () => table.getRowModel().rows;
|
|
62
|
+
const buildViews = () => {
|
|
63
|
+
const start = table.getStartLeafColumns();
|
|
64
|
+
const center = table.getCenterLeafColumns();
|
|
65
|
+
const end = table.getEndLeafColumns();
|
|
66
|
+
const sorting = options.sorting;
|
|
67
|
+
const view = (column, colIndex, offset, pinEdge) => {
|
|
68
|
+
const def = byId.get(column.id);
|
|
69
|
+
if (!def) throw new Error(`argus-table: unknown column '${column.id}'.`);
|
|
70
|
+
const sort = sorting.find((s) => s.id === column.id);
|
|
71
|
+
return {
|
|
72
|
+
id: def.id,
|
|
73
|
+
colIndex,
|
|
74
|
+
pin: def.pin,
|
|
75
|
+
offset,
|
|
76
|
+
pinEdge,
|
|
77
|
+
width: column.getSize(),
|
|
78
|
+
flex: options.columnSizing[def.id] === void 0 ? def.flex : void 0,
|
|
79
|
+
minWidth: def.minWidth,
|
|
80
|
+
maxWidth: def.maxWidth,
|
|
81
|
+
sortable: def.sortable,
|
|
82
|
+
resizable: def.resizable,
|
|
83
|
+
align: def.align,
|
|
84
|
+
sorted: sort ? sort.desc ? "desc" : "asc" : false,
|
|
85
|
+
header: def.header,
|
|
86
|
+
cell: def.cell,
|
|
87
|
+
hasAccessor: def.hasAccessor
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
return [
|
|
91
|
+
...start.map((c, i) => view(c, i + 1, c.getStart("start"), i === start.length - 1)),
|
|
92
|
+
...center.map((c, i) => view(c, start.length + i + 1, c.getStart("center"), false)),
|
|
93
|
+
...end.map((c, i) => view(c, start.length + center.length + i + 1, c.getAfter("end"), i === 0))
|
|
94
|
+
];
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
update(next) {
|
|
98
|
+
const merged = {
|
|
99
|
+
...options,
|
|
100
|
+
...next
|
|
101
|
+
};
|
|
102
|
+
if (!Object.keys(next).some((key) => next[key] !== options[key])) return;
|
|
103
|
+
if (next.columns && next.columns !== options.columns) {
|
|
104
|
+
resolved = resolveColumns(next.columns);
|
|
105
|
+
byId = new Map(resolved.map((c) => [c.id, c]));
|
|
106
|
+
tanStackColumns = resolved.map(toTanStack);
|
|
107
|
+
}
|
|
108
|
+
options = merged;
|
|
109
|
+
views = void 0;
|
|
110
|
+
table.setOptions((prev) => ({
|
|
111
|
+
...prev,
|
|
112
|
+
...tableOptions()
|
|
113
|
+
}));
|
|
114
|
+
},
|
|
115
|
+
get columns() {
|
|
116
|
+
return views ??= buildViews();
|
|
117
|
+
},
|
|
118
|
+
get rowCount() {
|
|
119
|
+
return rows().length;
|
|
120
|
+
},
|
|
121
|
+
get totalWidth() {
|
|
122
|
+
return table.getTotalSize();
|
|
123
|
+
},
|
|
124
|
+
rowAt(index) {
|
|
125
|
+
const row = rows()[index];
|
|
126
|
+
if (!row) return void 0;
|
|
127
|
+
let view = rowViews.get(row);
|
|
128
|
+
if (view?.index !== index) {
|
|
129
|
+
view = {
|
|
130
|
+
id: row.id,
|
|
131
|
+
index,
|
|
132
|
+
original: row.original
|
|
133
|
+
};
|
|
134
|
+
rowViews.set(row, view);
|
|
135
|
+
}
|
|
136
|
+
return view;
|
|
137
|
+
},
|
|
138
|
+
indexOf(rowId) {
|
|
139
|
+
const current = rows();
|
|
140
|
+
if (indexById?.rows !== current) indexById = {
|
|
141
|
+
rows: current,
|
|
142
|
+
map: new Map(current.map((r, i) => [r.id, i]))
|
|
143
|
+
};
|
|
144
|
+
return indexById.map.get(rowId);
|
|
145
|
+
},
|
|
146
|
+
valueAt(index, columnId) {
|
|
147
|
+
return rows()[index]?.getValue(columnId);
|
|
148
|
+
},
|
|
149
|
+
cellContext(index, columnId) {
|
|
150
|
+
const row = rows()[index];
|
|
151
|
+
if (!row || !byId.has(columnId)) return void 0;
|
|
152
|
+
return {
|
|
153
|
+
row: row.original,
|
|
154
|
+
rowId: row.id,
|
|
155
|
+
index,
|
|
156
|
+
columnId,
|
|
157
|
+
value: row.getValue(columnId)
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
nextSorting(columnId) {
|
|
161
|
+
if (byId.get(columnId)?.sortable !== true) return void 0;
|
|
162
|
+
const current = options.sorting.find((s) => s.id === columnId);
|
|
163
|
+
if (!current) return [{
|
|
164
|
+
id: columnId,
|
|
165
|
+
desc: false
|
|
166
|
+
}];
|
|
167
|
+
return current.desc ? [] : [{
|
|
168
|
+
id: columnId,
|
|
169
|
+
desc: true
|
|
170
|
+
}];
|
|
171
|
+
},
|
|
172
|
+
nextSizing(columnId, width) {
|
|
173
|
+
const column = byId.get(columnId);
|
|
174
|
+
if (!column?.resizable || !Number.isFinite(width)) return void 0;
|
|
175
|
+
const clamped = Math.round(Math.min(column.maxWidth, Math.max(column.minWidth, width)));
|
|
176
|
+
return {
|
|
177
|
+
...options.columnSizing,
|
|
178
|
+
[columnId]: clamped
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export { createTableModel };
|
|
184
|
+
|
|
185
|
+
//# sourceMappingURL=create-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-table.js","names":[],"sources":["../../../../../core/src/table/create-table.ts"],"sourcesContent":["import {\n columnPinningFeature,\n columnSizingFeature,\n constructTable,\n createSortedRowModel,\n rowSortingFeature,\n sortFn_alphanumeric,\n sortFn_basic,\n sortFn_datetime,\n sortFn_text,\n tableFeatures,\n type Column,\n type ColumnDef as TanStackColumnDef,\n type Row,\n type Table,\n type TableOptions,\n} from '@tanstack/table-core';\nimport { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings';\nimport { resolveColumns, type ResolvedColumn } from '../columns/resolve-columns.js';\nimport type {\n CellContext,\n ColumnAlign,\n ColumnDef,\n ColumnPin,\n ColumnSizingState,\n SortState,\n} from '../types.js';\n\n// The TanStack adapter. Everything TanStack stays in this module: callers only see\n// the view types below, so TanStack can be upgraded or replaced without touching\n// `argus-table`'s public API.\n\nconst features = tableFeatures({\n // Vanilla (framework-free) reactivity; required by `constructTable`.\n coreReactivityFeature: storeReactivityBindings(),\n columnPinningFeature,\n columnSizingFeature,\n rowSortingFeature,\n sortedRowModel: createSortedRowModel(),\n // Registered by name so `sortFn: 'auto'` can pick one by value type.\n sortFns: {\n alphanumeric: sortFn_alphanumeric,\n basic: sortFn_basic,\n datetime: sortFn_datetime,\n text: sortFn_text,\n },\n});\ntype Features = typeof features;\ntype AnyRow = Record<string, unknown>;\n\n/** Sort direction of a column, or `false` when it isn't sorted. */\nexport type SortDirection = 'asc' | 'desc' | false;\n\n/** A column as the renderer sees it, in visual order: start-pinned, scrolling, end-pinned. */\nexport interface ColumnView<TData, TContent> {\n id: string;\n /** 1-based position in visual order, for `aria-colindex`. */\n colIndex: number;\n pin: ColumnPin | undefined;\n /**\n * Sticky inset in px: from the start edge for `'start'` columns, from the end edge\n * for `'end'` columns. For scrolling columns, the start within the scrolling area.\n */\n offset: number;\n /** `true` on the pinned column next to the scrolling area (one per pinned side). */\n pinEdge: boolean;\n /** Current width in px (a resized width wins over the column's own). */\n width: number;\n /** Share of free space, or `undefined` for a fixed width. A resized column stops flexing. */\n flex: number | undefined;\n minWidth: number;\n maxWidth: number;\n sortable: boolean;\n resizable: boolean;\n align: ColumnAlign;\n sorted: SortDirection;\n header: ColumnDef<TData, TContent>['header'];\n cell: ColumnDef<TData, TContent>['cell'];\n hasAccessor: boolean;\n}\n\n/** A row of the current (sorted) row model. */\nexport interface RowView<TData> {\n id: string;\n /** Index in the current row model, 0-based. */\n index: number;\n original: TData;\n}\n\nexport interface TableModelOptions<TData, TContent> {\n columns: readonly ColumnDef<TData, TContent>[];\n data: readonly TData[];\n /** Row id function. Default: the row's index in `data`, as a string. */\n getRowId: ((row: TData, index: number) => string) | undefined;\n /** Current sort state; its owner is the caller (see `controllable`). */\n sorting: SortState;\n /** Current resized widths; its owner is the caller. */\n columnSizing: ColumnSizingState;\n}\n\nexport interface TableModel<TData, TContent> {\n /** Updates any subset of the options. Unchanged references are ignored. */\n update(options: Partial<TableModelOptions<TData, TContent>>): void;\n /** Columns in visual order. */\n readonly columns: readonly ColumnView<TData, TContent>[];\n /** Number of rows in the current row model. */\n readonly rowCount: number;\n /** Sum of all column widths in px (flex columns count their minimum). */\n readonly totalWidth: number;\n /** The row at an index of the current row model. */\n rowAt(index: number): RowView<TData> | undefined;\n /** The index of a row id in the current row model (after sorting), or `undefined`. */\n indexOf(rowId: string): number | undefined;\n /** The value a column reads from the row at `index`. */\n valueAt(index: number, columnId: string): unknown;\n /** What a column's `cell` function receives for the row at `index`. */\n cellContext(index: number, columnId: string): CellContext<TData> | undefined;\n /**\n * The sort state that clicking this column's header would produce:\n * asc → desc → unsorted, one column at a time. `undefined` if it can't sort.\n */\n nextSorting(columnId: string): SortState | undefined;\n /**\n * The sizing state after setting a column to `width` px, clamped to its bounds.\n * `undefined` if the column can't be resized.\n */\n nextSizing(columnId: string, width: number): ColumnSizingState | undefined;\n}\n\nconst defaultRowId = (_row: unknown, index: number) => String(index);\n\nfunction toTanStack<TData, TContent>(\n column: ResolvedColumn<TData, TContent>,\n): TanStackColumnDef<Features, AnyRow> {\n return {\n id: column.id,\n accessorFn: column.getValue as (row: AnyRow, index: number) => unknown,\n size: column.width,\n minSize: column.minWidth,\n maxSize: column.maxWidth,\n enableSorting: column.sortable,\n sortUndefined: 'last',\n };\n}\n\n/**\n * Creates the table model over TanStack Table. It owns no state: sorting and sizing\n * come in through `update()`, and `nextSorting()`/`nextSizing()` only propose\n * changes, so the caller decides (controlled or uncontrolled) whether to apply them.\n */\nexport function createTableModel<TData, TContent>(\n initial: TableModelOptions<TData, TContent>,\n): TableModel<TData, TContent> {\n let options = initial;\n let resolved = resolveColumns(options.columns);\n let byId = new Map(resolved.map((c) => [c.id, c]));\n // Kept stable between updates: a new array would make TanStack rebuild columns and rows.\n let tanStackColumns = resolved.map(toTanStack);\n let views: ColumnView<TData, TContent>[] | undefined;\n const rowViews = new WeakMap<Row<Features, AnyRow>, RowView<TData>>();\n // Built lazily per row model (sorting or new data gives a new array).\n let indexById: { rows: readonly Row<Features, AnyRow>[]; map: Map<string, number> } | undefined;\n\n const tableOptions = (): TableOptions<Features, AnyRow> => ({\n features,\n columns: tanStackColumns,\n data: options.data as AnyRow[],\n getRowId: (options.getRowId ?? defaultRowId) as (row: AnyRow, index: number) => string,\n state: {\n sorting: options.sorting,\n columnSizing: options.columnSizing,\n columnPinning: {\n start: resolved.filter((c) => c.pin === 'start').map((c) => c.id),\n end: resolved.filter((c) => c.pin === 'end').map((c) => c.id),\n },\n },\n // State is owned by the caller; TanStack's own setters must not change it.\n onSortingChange: () => undefined,\n onColumnSizingChange: () => undefined,\n onColumnPinningChange: () => undefined,\n });\n\n const table: Table<Features, AnyRow> = constructTable(tableOptions());\n\n const rows = () => table.getRowModel().rows;\n\n const buildViews = (): ColumnView<TData, TContent>[] => {\n const start = table.getStartLeafColumns();\n const center = table.getCenterLeafColumns();\n const end = table.getEndLeafColumns();\n const sorting = options.sorting;\n const view = (\n column: Column<Features, AnyRow>,\n colIndex: number,\n offset: number,\n pinEdge: boolean,\n ): ColumnView<TData, TContent> => {\n const def = byId.get(column.id);\n if (!def) throw new Error(`argus-table: unknown column '${column.id}'.`);\n const sort = sorting.find((s) => s.id === column.id);\n return {\n id: def.id,\n colIndex,\n pin: def.pin,\n offset,\n pinEdge,\n width: column.getSize(),\n flex: options.columnSizing[def.id] === undefined ? def.flex : undefined,\n minWidth: def.minWidth,\n maxWidth: def.maxWidth,\n sortable: def.sortable,\n resizable: def.resizable,\n align: def.align,\n sorted: sort ? (sort.desc ? 'desc' : 'asc') : false,\n header: def.header,\n cell: def.cell,\n hasAccessor: def.hasAccessor,\n };\n };\n return [\n ...start.map((c, i) => view(c, i + 1, c.getStart('start'), i === start.length - 1)),\n ...center.map((c, i) => view(c, start.length + i + 1, c.getStart('center'), false)),\n ...end.map((c, i) =>\n view(c, start.length + center.length + i + 1, c.getAfter('end'), i === 0),\n ),\n ];\n };\n\n return {\n update(next) {\n const merged = { ...options, ...next };\n const changed = (Object.keys(next) as (keyof typeof next)[]).some(\n (key) => next[key] !== options[key],\n );\n if (!changed) return;\n if (next.columns && next.columns !== options.columns) {\n resolved = resolveColumns(next.columns);\n byId = new Map(resolved.map((c) => [c.id, c]));\n tanStackColumns = resolved.map(toTanStack);\n }\n options = merged;\n views = undefined;\n table.setOptions((prev) => ({ ...prev, ...tableOptions() }));\n },\n\n get columns() {\n return (views ??= buildViews());\n },\n\n get rowCount() {\n return rows().length;\n },\n\n get totalWidth() {\n return table.getTotalSize();\n },\n\n rowAt(index) {\n const row = rows()[index];\n if (!row) return undefined;\n let view = rowViews.get(row);\n if (view?.index !== index) {\n view = { id: row.id, index, original: row.original as TData };\n rowViews.set(row, view);\n }\n return view;\n },\n\n indexOf(rowId) {\n const current = rows();\n if (indexById?.rows !== current) {\n indexById = { rows: current, map: new Map(current.map((r, i) => [r.id, i])) };\n }\n return indexById.map.get(rowId);\n },\n\n valueAt(index, columnId) {\n return rows()[index]?.getValue(columnId);\n },\n\n cellContext(index, columnId) {\n const row = rows()[index];\n if (!row || !byId.has(columnId)) return undefined;\n return {\n row: row.original as TData,\n rowId: row.id,\n index,\n columnId,\n value: row.getValue(columnId),\n };\n },\n\n nextSorting(columnId) {\n if (byId.get(columnId)?.sortable !== true) return undefined;\n const current = options.sorting.find((s) => s.id === columnId);\n if (!current) return [{ id: columnId, desc: false }];\n return current.desc ? [] : [{ id: columnId, desc: true }];\n },\n\n nextSizing(columnId, width) {\n const column = byId.get(columnId);\n if (!column?.resizable || !Number.isFinite(width)) return undefined;\n const clamped = Math.round(Math.min(column.maxWidth, Math.max(column.minWidth, width)));\n return { ...options.columnSizing, [columnId]: clamped };\n },\n };\n}\n"],"mappings":"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"}
|