vlist 2.0.0 → 2.0.2
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/README.github.md +2 -2
- package/README.md +2 -2
- package/dist/core/dom.d.ts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/pipeline.d.ts +2 -2
- package/dist/core/scroll.d.ts +1 -1
- package/dist/core/types.d.ts +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -28
- package/dist/internals.js +1 -60
- package/dist/plugins/scrollbar/controller.d.ts +3 -3
- package/dist/plugins/scrollbar/scrollbar.d.ts +2 -2
- package/dist/rendering/renderer.d.ts +2 -2
- package/dist/rendering/viewport.d.ts +1 -1
- package/dist/size.json +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/constants.js +0 -83
- package/dist/core/create.js +0 -740
- package/dist/core/dom.js +0 -47
- package/dist/core/hooks.js +0 -67
- package/dist/core/index.js +0 -13
- package/dist/core/pipeline.js +0 -307
- package/dist/core/pool.js +0 -42
- package/dist/core/scroll.js +0 -137
- package/dist/core/sizes.js +0 -6
- package/dist/core/state.js +0 -56
- package/dist/core/types.js +0 -7
- package/dist/core/velocity.js +0 -33
- package/dist/events/emitter.js +0 -60
- package/dist/events/index.js +0 -6
- package/dist/plugins/a11y/index.js +0 -1
- package/dist/plugins/a11y/plugin.js +0 -259
- package/dist/plugins/async/index.js +0 -12
- package/dist/plugins/async/manager.js +0 -568
- package/dist/plugins/async/placeholder.js +0 -154
- package/dist/plugins/async/plugin.js +0 -311
- package/dist/plugins/async/sparse.js +0 -540
- package/dist/plugins/autosize/index.js +0 -4
- package/dist/plugins/autosize/plugin.js +0 -185
- package/dist/plugins/grid/index.js +0 -5
- package/dist/plugins/grid/layout.js +0 -275
- package/dist/plugins/grid/plugin.js +0 -347
- package/dist/plugins/grid/renderer.js +0 -525
- package/dist/plugins/grid/types.js +0 -11
- package/dist/plugins/groups/async-bridge.js +0 -246
- package/dist/plugins/groups/index.js +0 -13
- package/dist/plugins/groups/layout.js +0 -294
- package/dist/plugins/groups/plugin.js +0 -571
- package/dist/plugins/groups/sticky.js +0 -255
- package/dist/plugins/groups/types.js +0 -12
- package/dist/plugins/masonry/index.js +0 -6
- package/dist/plugins/masonry/layout.js +0 -261
- package/dist/plugins/masonry/plugin.js +0 -381
- package/dist/plugins/masonry/renderer.js +0 -354
- package/dist/plugins/masonry/types.js +0 -9
- package/dist/plugins/page/index.js +0 -5
- package/dist/plugins/page/plugin.js +0 -166
- package/dist/plugins/scale/index.js +0 -4
- package/dist/plugins/scale/plugin.js +0 -507
- package/dist/plugins/scrollbar/controller.js +0 -574
- package/dist/plugins/scrollbar/index.js +0 -6
- package/dist/plugins/scrollbar/plugin.js +0 -93
- package/dist/plugins/scrollbar/scrollbar.js +0 -556
- package/dist/plugins/selection/index.js +0 -7
- package/dist/plugins/selection/plugin.js +0 -601
- package/dist/plugins/selection/state.js +0 -332
- package/dist/plugins/snapshots/index.js +0 -5
- package/dist/plugins/snapshots/plugin.js +0 -301
- package/dist/plugins/sortable/index.js +0 -6
- package/dist/plugins/sortable/plugin.js +0 -753
- package/dist/plugins/table/header.js +0 -501
- package/dist/plugins/table/index.js +0 -12
- package/dist/plugins/table/layout.js +0 -211
- package/dist/plugins/table/plugin.js +0 -391
- package/dist/plugins/table/renderer.js +0 -625
- package/dist/plugins/table/types.js +0 -12
- package/dist/plugins/transition/index.js +0 -5
- package/dist/plugins/transition/plugin.js +0 -405
- package/dist/rendering/aria.js +0 -23
- package/dist/rendering/index.js +0 -18
- package/dist/rendering/measured.js +0 -98
- package/dist/rendering/renderer.js +0 -586
- package/dist/rendering/scale.js +0 -267
- package/dist/rendering/scroll.js +0 -71
- package/dist/rendering/sizes.js +0 -193
- package/dist/rendering/sort.js +0 -65
- package/dist/rendering/viewport.js +0 -268
- package/dist/types.js +0 -5
- package/dist/utils/padding.js +0 -49
- package/dist/utils/stats.js +0 -124
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist/table - Header
|
|
3
|
-
* Manages the sticky header row that sits above the scrolling viewport.
|
|
4
|
-
*
|
|
5
|
-
* The header is a positioned DOM element inserted into the vlist root
|
|
6
|
-
* container (above the viewport, like the sticky group header). It contains
|
|
7
|
-
* one cell per column, each showing the column label. Resize handles are
|
|
8
|
-
* rendered at the right edge of each resizable column's header cell.
|
|
9
|
-
*
|
|
10
|
-
* Layout:
|
|
11
|
-
* .vlist (root, position: relative)
|
|
12
|
-
* ├── .vlist-table-header (position: absolute, top: 0, z-index: 5)
|
|
13
|
-
* │ ├── .vlist-table-header-cell [col 0]
|
|
14
|
-
* │ │ ├── .vlist-table-header-content (label)
|
|
15
|
-
* │ │ ├── .vlist-table-header-sort (sort indicator)
|
|
16
|
-
* │ │ └── .vlist-table-header-resize (drag handle)
|
|
17
|
-
* │ ├── .vlist-table-header-cell [col 1]
|
|
18
|
-
* │ │ └── ...
|
|
19
|
-
* │ └── ...
|
|
20
|
-
* └── .vlist-viewport (scrollable, top offset by headerHeight)
|
|
21
|
-
*
|
|
22
|
-
* Resize interaction:
|
|
23
|
-
* mousedown on handle → pointermove updates column width → pointerup commits
|
|
24
|
-
* During drag, a class is added to the root for cursor override.
|
|
25
|
-
*
|
|
26
|
-
* Sort interaction:
|
|
27
|
-
* click on a sortable header cell emits column:sort via the provided callback.
|
|
28
|
-
* The header renders a visual indicator (▲/▼) for the active sort column.
|
|
29
|
-
*
|
|
30
|
-
* Horizontal scroll sync:
|
|
31
|
-
* The header's scrollLeft is kept in sync with the viewport's scrollLeft
|
|
32
|
-
* via the `syncScroll` method, called from the feature's afterScroll hook.
|
|
33
|
-
*/
|
|
34
|
-
// =============================================================================
|
|
35
|
-
// Constants
|
|
36
|
-
// =============================================================================
|
|
37
|
-
/** Minimum drag distance (px) before resize is committed */
|
|
38
|
-
const MIN_DRAG_DELTA = 1;
|
|
39
|
-
/** Keyboard resize step in pixels */
|
|
40
|
-
const RESIZE_STEP = 10;
|
|
41
|
-
/** Sort indicator characters */
|
|
42
|
-
const SORT_ASC = "\u25B2"; // ▲
|
|
43
|
-
const SORT_DESC = "\u25BC"; // ▼
|
|
44
|
-
// =============================================================================
|
|
45
|
-
// Factory
|
|
46
|
-
// =============================================================================
|
|
47
|
-
/**
|
|
48
|
-
* Create a TableHeader instance.
|
|
49
|
-
*
|
|
50
|
-
* @param root - The vlist root element (.vlist)
|
|
51
|
-
* @param headerHeight - Height of the header row in pixels
|
|
52
|
-
* @param classPrefix - CSS class prefix (default: 'vlist')
|
|
53
|
-
* @param onResize - Callback when a column is resized (receives column index and new width)
|
|
54
|
-
* @param onSort - Callback when a sortable header is clicked
|
|
55
|
-
* @param onClick - Callback when any header cell is clicked
|
|
56
|
-
* @returns TableHeader instance
|
|
57
|
-
*/
|
|
58
|
-
export const createTableHeader = (root, headerHeight, classPrefix, onResize, onSort, onClick) => {
|
|
59
|
-
// =========================================================================
|
|
60
|
-
// DOM Setup
|
|
61
|
-
// =========================================================================
|
|
62
|
-
// Rowgroup wrapper — ensures the header row sits inside a proper
|
|
63
|
-
// ARIA rowgroup within the grid (dom.root has role="grid").
|
|
64
|
-
const rowgroup = document.createElement("div");
|
|
65
|
-
rowgroup.setAttribute("role", "rowgroup");
|
|
66
|
-
const element = document.createElement("div");
|
|
67
|
-
element.className = `${classPrefix}-table-header`;
|
|
68
|
-
element.setAttribute("role", "row");
|
|
69
|
-
element.setAttribute("aria-rowindex", "1");
|
|
70
|
-
// Only dynamic style — height comes from config
|
|
71
|
-
element.style.height = `${headerHeight}px`;
|
|
72
|
-
// Scroll container inside the header — this is what we scroll in sync
|
|
73
|
-
// with the viewport for horizontal scrolling.
|
|
74
|
-
const scrollContainer = document.createElement("div");
|
|
75
|
-
scrollContainer.className = `${classPrefix}-table-header-scroll`;
|
|
76
|
-
scrollContainer.setAttribute("role", "presentation");
|
|
77
|
-
element.appendChild(scrollContainer);
|
|
78
|
-
// Insert header rowgroup as first child of root (above viewport)
|
|
79
|
-
rowgroup.appendChild(element);
|
|
80
|
-
root.insertBefore(rowgroup, root.firstChild);
|
|
81
|
-
// Expose the header height as a CSS variable on the root so the custom
|
|
82
|
-
// scrollbar (if active) can offset its track below the header row.
|
|
83
|
-
// The viewport layout is handled entirely by CSS flex — no inline style needed.
|
|
84
|
-
root.style.setProperty('--vlist-table-header-height', `${headerHeight}px`);
|
|
85
|
-
// =========================================================================
|
|
86
|
-
// State
|
|
87
|
-
// =========================================================================
|
|
88
|
-
let cells = [];
|
|
89
|
-
let sortIndicators = [];
|
|
90
|
-
let isVisible = true;
|
|
91
|
-
let currentSortKey = null;
|
|
92
|
-
let currentSortDirection = "asc";
|
|
93
|
-
let currentLayout = null;
|
|
94
|
-
// Drag state
|
|
95
|
-
let isDragging = false;
|
|
96
|
-
let dragColumnIndex = -1;
|
|
97
|
-
let dragStartX = 0;
|
|
98
|
-
let dragStartWidth = 0;
|
|
99
|
-
// Keyboard focus state (roving tabindex)
|
|
100
|
-
let focusedCellIndex = 0;
|
|
101
|
-
// =========================================================================
|
|
102
|
-
// Cell Creation
|
|
103
|
-
// =========================================================================
|
|
104
|
-
/**
|
|
105
|
-
* Create a single header cell element for a resolved column.
|
|
106
|
-
*/
|
|
107
|
-
const createCell = (col, colIndex) => {
|
|
108
|
-
const cell = document.createElement("div");
|
|
109
|
-
cell.className = `${classPrefix}-table-header-cell`;
|
|
110
|
-
cell.setAttribute("role", "columnheader");
|
|
111
|
-
cell.setAttribute("aria-colindex", String(colIndex + 1));
|
|
112
|
-
cell.setAttribute("tabindex", colIndex === 0 ? "0" : "-1");
|
|
113
|
-
cell.dataset.columnKey = col.def.key;
|
|
114
|
-
// Alignment modifier class (left is the default — no class needed)
|
|
115
|
-
const align = col.def.align;
|
|
116
|
-
if (align === "center") {
|
|
117
|
-
cell.classList.add(`${classPrefix}-table-header-cell--center`);
|
|
118
|
-
}
|
|
119
|
-
else if (align === "right") {
|
|
120
|
-
cell.classList.add(`${classPrefix}-table-header-cell--right`);
|
|
121
|
-
}
|
|
122
|
-
// Content wrapper
|
|
123
|
-
const content = document.createElement("div");
|
|
124
|
-
content.className = `${classPrefix}-table-header-content`;
|
|
125
|
-
// Render label
|
|
126
|
-
const label = col.def.header
|
|
127
|
-
? col.def.header(col.def)
|
|
128
|
-
: col.def.label;
|
|
129
|
-
if (typeof label === "string") {
|
|
130
|
-
content.textContent = label;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
content.appendChild(label);
|
|
134
|
-
}
|
|
135
|
-
cell.appendChild(content);
|
|
136
|
-
// Sort indicator — only created for sortable columns (avoids unnecessary DOM nodes)
|
|
137
|
-
if (col.def.sortable) {
|
|
138
|
-
const sortIndicator = document.createElement("span");
|
|
139
|
-
sortIndicator.className = `${classPrefix}-table-header-sort`;
|
|
140
|
-
sortIndicator.setAttribute("aria-hidden", "true");
|
|
141
|
-
cell.appendChild(sortIndicator);
|
|
142
|
-
sortIndicators.push(sortIndicator);
|
|
143
|
-
cell.classList.add(`${classPrefix}-table-header-cell--sortable`);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
// Keep index alignment with columns array
|
|
147
|
-
sortIndicators.push(null);
|
|
148
|
-
}
|
|
149
|
-
// Resize handle (at right edge)
|
|
150
|
-
if (col.resizable) {
|
|
151
|
-
const handle = document.createElement("div");
|
|
152
|
-
handle.className = `${classPrefix}-table-header-resize`;
|
|
153
|
-
handle.dataset.resizeIndex = String(colIndex);
|
|
154
|
-
cell.appendChild(handle);
|
|
155
|
-
}
|
|
156
|
-
return cell;
|
|
157
|
-
};
|
|
158
|
-
// =========================================================================
|
|
159
|
-
// Build / Rebuild
|
|
160
|
-
// =========================================================================
|
|
161
|
-
/**
|
|
162
|
-
* Build all header cells from a layout.
|
|
163
|
-
*/
|
|
164
|
-
const rebuild = (layout) => {
|
|
165
|
-
currentLayout = layout;
|
|
166
|
-
// Clear existing cells
|
|
167
|
-
scrollContainer.textContent = "";
|
|
168
|
-
cells = [];
|
|
169
|
-
sortIndicators = [];
|
|
170
|
-
const columns = layout.columns;
|
|
171
|
-
for (let i = 0; i < columns.length; i++) {
|
|
172
|
-
const col = columns[i];
|
|
173
|
-
const cell = createCell(col, i);
|
|
174
|
-
cells.push(cell);
|
|
175
|
-
scrollContainer.appendChild(cell);
|
|
176
|
-
}
|
|
177
|
-
// Apply widths and offsets
|
|
178
|
-
update(layout);
|
|
179
|
-
// Restore sort indicator if active
|
|
180
|
-
if (currentSortKey) {
|
|
181
|
-
updateSort(currentSortKey, currentSortDirection);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
// =========================================================================
|
|
185
|
-
// Update (positions & widths)
|
|
186
|
-
// =========================================================================
|
|
187
|
-
/**
|
|
188
|
-
* Update header cell widths to match the layout.
|
|
189
|
-
* Called after layout.resolve() or after a resize.
|
|
190
|
-
*/
|
|
191
|
-
const update = (layout) => {
|
|
192
|
-
currentLayout = layout;
|
|
193
|
-
const columns = layout.columns;
|
|
194
|
-
// Set scroll container to total column width
|
|
195
|
-
scrollContainer.style.width = `${layout.totalWidth}px`;
|
|
196
|
-
for (let i = 0; i < cells.length && i < columns.length; i++) {
|
|
197
|
-
const cell = cells[i];
|
|
198
|
-
const col = columns[i];
|
|
199
|
-
cell.style.width = `${col.width}px`;
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
// =========================================================================
|
|
203
|
-
// Sort Indicator
|
|
204
|
-
// =========================================================================
|
|
205
|
-
/**
|
|
206
|
-
* Update the sort indicator on header cells.
|
|
207
|
-
*
|
|
208
|
-
* @param key - Column key to show sort on, or null to clear
|
|
209
|
-
* @param direction - Sort direction
|
|
210
|
-
*/
|
|
211
|
-
const updateSort = (key, direction) => {
|
|
212
|
-
currentSortKey = key;
|
|
213
|
-
currentSortDirection = direction;
|
|
214
|
-
if (!currentLayout)
|
|
215
|
-
return;
|
|
216
|
-
const columns = currentLayout.columns;
|
|
217
|
-
for (let i = 0; i < sortIndicators.length && i < columns.length; i++) {
|
|
218
|
-
const indicator = sortIndicators[i];
|
|
219
|
-
if (!indicator)
|
|
220
|
-
continue;
|
|
221
|
-
const col = columns[i];
|
|
222
|
-
if (col.def.key === key) {
|
|
223
|
-
indicator.textContent = direction === "asc" ? SORT_ASC : SORT_DESC;
|
|
224
|
-
indicator.style.opacity = "0.7";
|
|
225
|
-
cells[i].setAttribute("aria-sort", direction === "asc" ? "ascending" : "descending");
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
indicator.textContent = "";
|
|
229
|
-
indicator.style.opacity = "0";
|
|
230
|
-
cells[i].removeAttribute("aria-sort");
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
// =========================================================================
|
|
235
|
-
// Horizontal Scroll Sync
|
|
236
|
-
// =========================================================================
|
|
237
|
-
/**
|
|
238
|
-
* Synchronize header scroll position with the viewport.
|
|
239
|
-
* Call this from the feature's scroll handler.
|
|
240
|
-
*/
|
|
241
|
-
const syncScroll = (scrollLeft) => {
|
|
242
|
-
scrollContainer.style.transform = `translateX(${-scrollLeft}px)`;
|
|
243
|
-
};
|
|
244
|
-
// =========================================================================
|
|
245
|
-
// Resize Interaction (Pointer Events)
|
|
246
|
-
// =========================================================================
|
|
247
|
-
const onPointerDown = (e) => {
|
|
248
|
-
const target = e.target;
|
|
249
|
-
if (!target.dataset.resizeIndex)
|
|
250
|
-
return;
|
|
251
|
-
e.preventDefault();
|
|
252
|
-
e.stopPropagation();
|
|
253
|
-
dragColumnIndex = +target.dataset.resizeIndex;
|
|
254
|
-
if (!currentLayout)
|
|
255
|
-
return;
|
|
256
|
-
const col = currentLayout.getColumn(dragColumnIndex);
|
|
257
|
-
if (!col?.resizable)
|
|
258
|
-
return;
|
|
259
|
-
isDragging = true;
|
|
260
|
-
dragStartX = e.clientX;
|
|
261
|
-
dragStartWidth = col.width;
|
|
262
|
-
// Set cursor on the root to avoid flickering during drag
|
|
263
|
-
root.classList.add(`${classPrefix}--col-resizing`);
|
|
264
|
-
root.style.cursor = "col-resize";
|
|
265
|
-
// Highlight the active resize handle
|
|
266
|
-
target.classList.add(`${classPrefix}-table-header-resize--active`);
|
|
267
|
-
// Capture pointer for tracking outside the element
|
|
268
|
-
target.setPointerCapture(e.pointerId);
|
|
269
|
-
// Bind move and up handlers
|
|
270
|
-
target.addEventListener("pointermove", onPointerMove);
|
|
271
|
-
target.addEventListener("pointerup", onPointerUp);
|
|
272
|
-
target.addEventListener("pointercancel", onPointerUp);
|
|
273
|
-
};
|
|
274
|
-
const onPointerMove = (e) => {
|
|
275
|
-
if (!isDragging || !currentLayout)
|
|
276
|
-
return;
|
|
277
|
-
const delta = e.clientX - dragStartX;
|
|
278
|
-
if (Math.abs(delta) < MIN_DRAG_DELTA)
|
|
279
|
-
return;
|
|
280
|
-
const newWidth = Math.max(0, dragStartWidth + delta);
|
|
281
|
-
onResize(dragColumnIndex, newWidth);
|
|
282
|
-
};
|
|
283
|
-
const onPointerUp = (e) => {
|
|
284
|
-
if (!isDragging)
|
|
285
|
-
return;
|
|
286
|
-
const target = e.target;
|
|
287
|
-
// Remove move/up listeners
|
|
288
|
-
target.removeEventListener("pointermove", onPointerMove);
|
|
289
|
-
target.removeEventListener("pointerup", onPointerUp);
|
|
290
|
-
target.removeEventListener("pointercancel", onPointerUp);
|
|
291
|
-
// Release pointer capture
|
|
292
|
-
try {
|
|
293
|
-
target.releasePointerCapture(e.pointerId);
|
|
294
|
-
}
|
|
295
|
-
catch {
|
|
296
|
-
// Pointer may have been released already
|
|
297
|
-
}
|
|
298
|
-
// Reset drag handle highlight
|
|
299
|
-
target.classList.remove(`${classPrefix}-table-header-resize--active`);
|
|
300
|
-
// Reset cursor
|
|
301
|
-
root.classList.remove(`${classPrefix}--col-resizing`);
|
|
302
|
-
root.style.cursor = "";
|
|
303
|
-
isDragging = false;
|
|
304
|
-
dragColumnIndex = -1;
|
|
305
|
-
};
|
|
306
|
-
// =========================================================================
|
|
307
|
-
// Click Interaction (Sort + General)
|
|
308
|
-
// =========================================================================
|
|
309
|
-
const onCellClick = (e) => {
|
|
310
|
-
// Ignore clicks on resize handles
|
|
311
|
-
const target = e.target;
|
|
312
|
-
if (target.dataset.resizeIndex)
|
|
313
|
-
return;
|
|
314
|
-
if (isDragging)
|
|
315
|
-
return;
|
|
316
|
-
// Walk up to find the header cell
|
|
317
|
-
let cell = target;
|
|
318
|
-
while (cell && !cell.dataset.columnKey) {
|
|
319
|
-
cell = cell.parentElement;
|
|
320
|
-
// Don't walk outside the header
|
|
321
|
-
if (cell === element || cell === null)
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const key = cell.dataset.columnKey;
|
|
325
|
-
if (!currentLayout)
|
|
326
|
-
return;
|
|
327
|
-
// Find the column
|
|
328
|
-
const columns = currentLayout.columns;
|
|
329
|
-
let colIndex = -1;
|
|
330
|
-
for (let i = 0; i < columns.length; i++) {
|
|
331
|
-
if (columns[i].def.key === key) {
|
|
332
|
-
colIndex = i;
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
if (colIndex === -1)
|
|
337
|
-
return;
|
|
338
|
-
const col = columns[colIndex];
|
|
339
|
-
// Emit general click
|
|
340
|
-
if (onClick) {
|
|
341
|
-
onClick({ key, index: colIndex, event: e });
|
|
342
|
-
}
|
|
343
|
-
// Emit sort event for sortable columns
|
|
344
|
-
if (col.def.sortable && onSort) {
|
|
345
|
-
let direction;
|
|
346
|
-
if (currentSortKey === key) {
|
|
347
|
-
// Cycle: asc → desc → null
|
|
348
|
-
direction = currentSortDirection === "asc" ? "desc" : null;
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
direction = "asc";
|
|
352
|
-
}
|
|
353
|
-
onSort({ key, index: colIndex, direction });
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
// =========================================================================
|
|
357
|
-
// Keyboard Navigation (roving tabindex on header cells)
|
|
358
|
-
// =========================================================================
|
|
359
|
-
const moveFocusToCell = (index) => {
|
|
360
|
-
if (index < 0 || index >= cells.length)
|
|
361
|
-
return;
|
|
362
|
-
cells[focusedCellIndex]?.setAttribute("tabindex", "-1");
|
|
363
|
-
focusedCellIndex = index;
|
|
364
|
-
const cell = cells[focusedCellIndex];
|
|
365
|
-
cell.setAttribute("tabindex", "0");
|
|
366
|
-
cell.focus();
|
|
367
|
-
};
|
|
368
|
-
const onKeyDown = (e) => {
|
|
369
|
-
const key = e.key;
|
|
370
|
-
if (key === "ArrowRight") {
|
|
371
|
-
if (e.ctrlKey || e.metaKey) {
|
|
372
|
-
// Ctrl+Right: resize column wider
|
|
373
|
-
if (!currentLayout)
|
|
374
|
-
return;
|
|
375
|
-
const col = currentLayout.getColumn(focusedCellIndex);
|
|
376
|
-
if (col?.resizable) {
|
|
377
|
-
onResize(focusedCellIndex, col.width + RESIZE_STEP);
|
|
378
|
-
e.preventDefault();
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
// Move focus to next header cell
|
|
383
|
-
if (focusedCellIndex < cells.length - 1) {
|
|
384
|
-
moveFocusToCell(focusedCellIndex + 1);
|
|
385
|
-
e.preventDefault();
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
e.stopPropagation();
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (key === "ArrowLeft") {
|
|
392
|
-
if (e.ctrlKey || e.metaKey) {
|
|
393
|
-
// Ctrl+Left: resize column narrower
|
|
394
|
-
if (!currentLayout)
|
|
395
|
-
return;
|
|
396
|
-
const col = currentLayout.getColumn(focusedCellIndex);
|
|
397
|
-
if (col?.resizable) {
|
|
398
|
-
onResize(focusedCellIndex, col.width - RESIZE_STEP);
|
|
399
|
-
e.preventDefault();
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
// Move focus to previous header cell
|
|
404
|
-
if (focusedCellIndex > 0) {
|
|
405
|
-
moveFocusToCell(focusedCellIndex - 1);
|
|
406
|
-
e.preventDefault();
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
e.stopPropagation();
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
if (key === "Home") {
|
|
413
|
-
moveFocusToCell(0);
|
|
414
|
-
e.preventDefault();
|
|
415
|
-
e.stopPropagation();
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
if (key === "End") {
|
|
419
|
-
moveFocusToCell(cells.length - 1);
|
|
420
|
-
e.preventDefault();
|
|
421
|
-
e.stopPropagation();
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
if (key === "Enter" || key === " ") {
|
|
425
|
-
// Trigger sort on sortable columns
|
|
426
|
-
if (!currentLayout)
|
|
427
|
-
return;
|
|
428
|
-
const col = currentLayout.getColumn(focusedCellIndex);
|
|
429
|
-
if (col?.def.sortable && onSort) {
|
|
430
|
-
let direction;
|
|
431
|
-
if (currentSortKey === col.def.key) {
|
|
432
|
-
direction = currentSortDirection === "asc" ? "desc" : null;
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
direction = "asc";
|
|
436
|
-
}
|
|
437
|
-
onSort({ key: col.def.key, index: focusedCellIndex, direction });
|
|
438
|
-
e.preventDefault();
|
|
439
|
-
}
|
|
440
|
-
e.stopPropagation();
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (key === "ArrowDown") {
|
|
444
|
-
// Return focus to the grid body
|
|
445
|
-
root.focus();
|
|
446
|
-
e.preventDefault();
|
|
447
|
-
e.stopPropagation();
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
// =========================================================================
|
|
452
|
-
// Event Binding
|
|
453
|
-
// =========================================================================
|
|
454
|
-
element.addEventListener("pointerdown", onPointerDown);
|
|
455
|
-
element.addEventListener("click", onCellClick);
|
|
456
|
-
element.addEventListener("keydown", onKeyDown);
|
|
457
|
-
// =========================================================================
|
|
458
|
-
// Visibility
|
|
459
|
-
// =========================================================================
|
|
460
|
-
const show = () => {
|
|
461
|
-
if (isVisible)
|
|
462
|
-
return;
|
|
463
|
-
isVisible = true;
|
|
464
|
-
element.style.display = "";
|
|
465
|
-
};
|
|
466
|
-
const hide = () => {
|
|
467
|
-
if (!isVisible)
|
|
468
|
-
return;
|
|
469
|
-
isVisible = false;
|
|
470
|
-
element.style.display = "none";
|
|
471
|
-
};
|
|
472
|
-
// =========================================================================
|
|
473
|
-
// Destroy
|
|
474
|
-
// =========================================================================
|
|
475
|
-
const destroy = () => {
|
|
476
|
-
element.removeEventListener("pointerdown", onPointerDown);
|
|
477
|
-
element.removeEventListener("click", onCellClick);
|
|
478
|
-
element.removeEventListener("keydown", onKeyDown);
|
|
479
|
-
// Clear the CSS variable set during setup
|
|
480
|
-
root.style.removeProperty('--vlist-table-header-height');
|
|
481
|
-
rowgroup.remove();
|
|
482
|
-
cells = [];
|
|
483
|
-
sortIndicators = [];
|
|
484
|
-
currentLayout = null;
|
|
485
|
-
isVisible = false;
|
|
486
|
-
};
|
|
487
|
-
// =========================================================================
|
|
488
|
-
// Return
|
|
489
|
-
// =========================================================================
|
|
490
|
-
return {
|
|
491
|
-
element,
|
|
492
|
-
update,
|
|
493
|
-
updateSort,
|
|
494
|
-
rebuild,
|
|
495
|
-
show,
|
|
496
|
-
hide,
|
|
497
|
-
destroy,
|
|
498
|
-
// Extra method exposed for scroll sync (not in interface — cast in feature)
|
|
499
|
-
syncScroll,
|
|
500
|
-
};
|
|
501
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist - Table Domain
|
|
3
|
-
* Data table layout with columns, resizable headers, and cell rendering
|
|
4
|
-
*/
|
|
5
|
-
// v2 Plugin
|
|
6
|
-
export { table } from "./plugin";
|
|
7
|
-
// Layout
|
|
8
|
-
export { createTableLayout } from "./layout";
|
|
9
|
-
// Header
|
|
10
|
-
export { createTableHeader } from "./header";
|
|
11
|
-
// Renderer
|
|
12
|
-
export { createTableRenderer } from "./renderer";
|