@ornery/ui-grid-vanilla 0.1.8 → 0.1.10
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/demo/index.html +3 -3
- package/demo/main.ts +1 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +252 -22
- package/dist/index.mjs +252 -22
- package/package.json +2 -2
- package/src/grid-controller.ts +29 -2
- package/src/index.integration.test.ts +31 -0
- package/src/index.test.ts +1 -1
- package/src/ui-grid-standalone.element.ts +291 -27
package/dist/index.mjs
CHANGED
|
@@ -97,6 +97,7 @@ var VanillaGridController = class {
|
|
|
97
97
|
expandedTreeRows = {};
|
|
98
98
|
pinnedColumns = {};
|
|
99
99
|
columnOrder = [];
|
|
100
|
+
columnWidthOverrides = {};
|
|
100
101
|
currentPage = 1;
|
|
101
102
|
pageSize = 0;
|
|
102
103
|
editingCell = null;
|
|
@@ -505,6 +506,24 @@ var VanillaGridController = class {
|
|
|
505
506
|
});
|
|
506
507
|
this.refresh();
|
|
507
508
|
}
|
|
509
|
+
canResizeColumns() {
|
|
510
|
+
return this.options.enableColumnResizing !== false;
|
|
511
|
+
}
|
|
512
|
+
setColumnWidthOverride(columnName, widthPx) {
|
|
513
|
+
const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
|
|
514
|
+
this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
|
|
515
|
+
this.refresh();
|
|
516
|
+
}
|
|
517
|
+
/** Returns the grid-template-columns string as if `columnName` had the given width,
|
|
518
|
+
* without triggering a full refresh. Used for smooth drag-resize DOM updates. */
|
|
519
|
+
buildTemplateColumnsWithOverride(columnName, widthPx) {
|
|
520
|
+
const widthStr = `${Math.max(88, Math.round(widthPx))}px`;
|
|
521
|
+
return buildGridTemplateColumns(
|
|
522
|
+
this.visibleColumns.map(
|
|
523
|
+
(c) => c.name === columnName ? { ...c, width: widthStr } : c
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
}
|
|
508
527
|
isGroupingEnabled() {
|
|
509
528
|
return isGridGroupingEnabled(this.options);
|
|
510
529
|
}
|
|
@@ -623,9 +642,13 @@ var VanillaGridController = class {
|
|
|
623
642
|
}
|
|
624
643
|
refresh() {
|
|
625
644
|
const orderedColumns = orderVisibleColumns(this.options.columnDefs, this.columnOrder);
|
|
645
|
+
const applyWidthOverrides = (columns) => columns.map((col) => {
|
|
646
|
+
const override = this.columnWidthOverrides[col.name];
|
|
647
|
+
return override == null ? col : { ...col, width: override };
|
|
648
|
+
});
|
|
626
649
|
const pinnedEntries = Object.entries(this.pinnedColumns);
|
|
627
650
|
if (pinnedEntries.length === 0) {
|
|
628
|
-
this.visibleColumns = orderedColumns;
|
|
651
|
+
this.visibleColumns = applyWidthOverrides(orderedColumns);
|
|
629
652
|
} else {
|
|
630
653
|
const byName = new Map(orderedColumns.map((column) => [column.name, column]));
|
|
631
654
|
const pinnedLeft = pinnedEntries.filter((entry) => entry[1] === "left").map((entry) => byName.get(entry[0])).filter((column) => column !== void 0);
|
|
@@ -633,7 +656,7 @@ var VanillaGridController = class {
|
|
|
633
656
|
const middleColumns = orderedColumns.filter(
|
|
634
657
|
(column) => this.pinnedColumns[column.name] === void 0
|
|
635
658
|
);
|
|
636
|
-
this.visibleColumns = [...pinnedLeft, ...middleColumns, ...pinnedRight];
|
|
659
|
+
this.visibleColumns = applyWidthOverrides([...pinnedLeft, ...middleColumns, ...pinnedRight]);
|
|
637
660
|
}
|
|
638
661
|
this.pipeline = defaultGridEngine.buildPipeline({
|
|
639
662
|
options: this.options,
|
|
@@ -821,6 +844,7 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
821
844
|
draggedColumnName = null;
|
|
822
845
|
dropTargetColumnName = null;
|
|
823
846
|
scrollPosition = 0;
|
|
847
|
+
horizontalScrollPosition = 0;
|
|
824
848
|
scrollFrame = null;
|
|
825
849
|
suppressScrollEvent = false;
|
|
826
850
|
lastVirtualStartIndex = -1;
|
|
@@ -830,6 +854,9 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
830
854
|
benchmarkAverage = "\u2014";
|
|
831
855
|
skipNextRender = false;
|
|
832
856
|
dataFrame = null;
|
|
857
|
+
pendingPatchedRowIds = null;
|
|
858
|
+
pendingDataRefreshMode = null;
|
|
859
|
+
lastScrollActivityAt = 0;
|
|
833
860
|
static get observedAttributes() {
|
|
834
861
|
return [
|
|
835
862
|
// Scalar attributes
|
|
@@ -875,10 +902,14 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
875
902
|
];
|
|
876
903
|
}
|
|
877
904
|
get options() {
|
|
878
|
-
|
|
905
|
+
if (this.activeOptions !== null) {
|
|
906
|
+
return this.buildEffectiveOptions(this.activeOptions);
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
879
909
|
id: "__ui-grid-pending__",
|
|
880
910
|
data: [],
|
|
881
|
-
columnDefs: []
|
|
911
|
+
columnDefs: [],
|
|
912
|
+
...this.attributeOptions
|
|
882
913
|
};
|
|
883
914
|
}
|
|
884
915
|
set options(value) {
|
|
@@ -949,6 +980,9 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
949
980
|
const enableColumnMoving = this.parseBooleanAttribute("enable-column-moving");
|
|
950
981
|
if (enableColumnMoving !== void 0)
|
|
951
982
|
this.attributeOptions.enableColumnMoving = enableColumnMoving;
|
|
983
|
+
const enableColumnResizing = this.parseBooleanAttribute("enable-column-resizing");
|
|
984
|
+
if (enableColumnResizing !== void 0)
|
|
985
|
+
this.attributeOptions.enableColumnResizing = enableColumnResizing;
|
|
952
986
|
const enableCellEdit = this.parseBooleanAttribute("enable-cell-edit");
|
|
953
987
|
if (enableCellEdit !== void 0) this.attributeOptions.enableCellEdit = enableCellEdit;
|
|
954
988
|
const enableCellEditOnFocus = this.parseBooleanAttribute("enable-cell-edit-on-focus");
|
|
@@ -1001,8 +1035,15 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1001
1035
|
);
|
|
1002
1036
|
if (paginationPageSizes !== void 0)
|
|
1003
1037
|
this.attributeOptions.paginationPageSizes = paginationPageSizes;
|
|
1004
|
-
if (this.activeOptions) {
|
|
1038
|
+
if (this.activeOptions !== null) {
|
|
1005
1039
|
this.ensureController(this.buildEffectiveOptions(this.activeOptions));
|
|
1040
|
+
} else {
|
|
1041
|
+
this.ensureController({
|
|
1042
|
+
id: "__ui-grid-pending__",
|
|
1043
|
+
data: [],
|
|
1044
|
+
columnDefs: [],
|
|
1045
|
+
...this.attributeOptions
|
|
1046
|
+
});
|
|
1006
1047
|
}
|
|
1007
1048
|
}
|
|
1008
1049
|
parseBooleanAttribute(name) {
|
|
@@ -1190,6 +1231,13 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1190
1231
|
this.activeOptions = { ...this.activeOptions, enableColumnMoving: value };
|
|
1191
1232
|
this.ensureController(this.buildEffectiveOptions(this.activeOptions));
|
|
1192
1233
|
}
|
|
1234
|
+
get enableColumnResizing() {
|
|
1235
|
+
return this.options.enableColumnResizing ?? true;
|
|
1236
|
+
}
|
|
1237
|
+
set enableColumnResizing(value) {
|
|
1238
|
+
this.activeOptions = { ...this.activeOptions, enableColumnResizing: value };
|
|
1239
|
+
this.ensureController(this.buildEffectiveOptions(this.activeOptions));
|
|
1240
|
+
}
|
|
1193
1241
|
get enableCellEdit() {
|
|
1194
1242
|
return this.options.enableCellEdit ?? false;
|
|
1195
1243
|
}
|
|
@@ -1314,18 +1362,49 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1314
1362
|
if (!controller) {
|
|
1315
1363
|
return;
|
|
1316
1364
|
}
|
|
1365
|
+
const previousSnapshot = this.snapshot;
|
|
1317
1366
|
this.skipNextRender = true;
|
|
1318
1367
|
controller.refreshData(rows);
|
|
1319
1368
|
this.skipNextRender = false;
|
|
1369
|
+
const { mode, changedRowIds } = this.classifyDataRefresh(previousSnapshot, this.snapshot);
|
|
1370
|
+
if (mode === null) {
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
this.pendingDataRefreshMode = mode;
|
|
1374
|
+
this.pendingPatchedRowIds = changedRowIds;
|
|
1320
1375
|
if (this.dataFrame !== null) {
|
|
1321
1376
|
cancelAnimationFrame(this.dataFrame);
|
|
1322
1377
|
}
|
|
1323
1378
|
this.dataFrame = requestAnimationFrame(() => {
|
|
1324
|
-
this.
|
|
1325
|
-
this.patchCells();
|
|
1379
|
+
this.flushPendingDataRefresh();
|
|
1326
1380
|
});
|
|
1327
1381
|
}
|
|
1328
|
-
|
|
1382
|
+
flushPendingDataRefresh() {
|
|
1383
|
+
this.dataFrame = null;
|
|
1384
|
+
const mode = this.pendingDataRefreshMode;
|
|
1385
|
+
if (mode === null) {
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (Date.now() - this.lastScrollActivityAt < 80) {
|
|
1389
|
+
this.dataFrame = requestAnimationFrame(() => {
|
|
1390
|
+
this.flushPendingDataRefresh();
|
|
1391
|
+
});
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const changedRowIds = this.pendingPatchedRowIds;
|
|
1395
|
+
this.pendingDataRefreshMode = null;
|
|
1396
|
+
this.pendingPatchedRowIds = null;
|
|
1397
|
+
if (mode === "patch") {
|
|
1398
|
+
this.patchCells(changedRowIds ?? void 0);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
if (mode === "virtual") {
|
|
1402
|
+
this.renderVirtualBody();
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
this.render();
|
|
1406
|
+
}
|
|
1407
|
+
patchCells(changedRowIds) {
|
|
1329
1408
|
const snapshot = this.snapshot;
|
|
1330
1409
|
if (!snapshot) {
|
|
1331
1410
|
return;
|
|
@@ -1335,30 +1414,37 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1335
1414
|
return;
|
|
1336
1415
|
}
|
|
1337
1416
|
const rowMap = /* @__PURE__ */ new Map();
|
|
1417
|
+
const rowIndexMap = /* @__PURE__ */ new Map();
|
|
1338
1418
|
for (const row of snapshot.pipeline.visibleRows) {
|
|
1339
1419
|
rowMap.set(row.id, row);
|
|
1420
|
+
rowIndexMap.set(row.id, rowIndexMap.size);
|
|
1340
1421
|
}
|
|
1341
1422
|
const colMap = /* @__PURE__ */ new Map();
|
|
1423
|
+
const templateMarkupMap = /* @__PURE__ */ new Map();
|
|
1342
1424
|
for (const col of snapshot.visibleColumns) {
|
|
1343
1425
|
colMap.set(col.name, col);
|
|
1426
|
+
templateMarkupMap.set(col.name, this.getTemplateMarkup(this.cellSlotName(col)));
|
|
1344
1427
|
}
|
|
1345
1428
|
const cells = root.querySelectorAll("[data-row][data-column]");
|
|
1346
1429
|
for (const cell of cells) {
|
|
1347
1430
|
const rowId = cell.dataset["row"];
|
|
1348
1431
|
const colName = cell.dataset["column"];
|
|
1349
1432
|
if (!rowId || !colName) continue;
|
|
1433
|
+
if (changedRowIds && !changedRowIds.has(rowId)) continue;
|
|
1350
1434
|
const row = rowMap.get(rowId);
|
|
1351
1435
|
const column = colMap.get(colName);
|
|
1352
1436
|
if (!row || !column) continue;
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1437
|
+
if (cell.classList.contains("cell-editing")) continue;
|
|
1438
|
+
const cellContent = cell.querySelector(".cell-content");
|
|
1439
|
+
if (!cellContent) continue;
|
|
1440
|
+
const newContent = this.renderCellTemplateFromMarkup(
|
|
1441
|
+
row,
|
|
1442
|
+
column,
|
|
1443
|
+
rowIndexMap.get(rowId) ?? 0,
|
|
1444
|
+
templateMarkupMap.get(colName) ?? null
|
|
1445
|
+
);
|
|
1446
|
+
if (cellContent.innerHTML !== newContent) {
|
|
1447
|
+
cellContent.innerHTML = newContent;
|
|
1362
1448
|
}
|
|
1363
1449
|
}
|
|
1364
1450
|
}
|
|
@@ -1572,11 +1658,51 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1572
1658
|
this.controller.setPageSize(value);
|
|
1573
1659
|
}
|
|
1574
1660
|
});
|
|
1661
|
+
root.addEventListener("mousedown", (event) => {
|
|
1662
|
+
const target = event.target;
|
|
1663
|
+
if (!target || !this.controller) return;
|
|
1664
|
+
const resizer = target.closest(".column-resizer[data-column]");
|
|
1665
|
+
if (!resizer || !this.controller.canResizeColumns()) return;
|
|
1666
|
+
const columnName = resizer.dataset["column"];
|
|
1667
|
+
if (!columnName) return;
|
|
1668
|
+
const headerCell = resizer.closest(".header-cell");
|
|
1669
|
+
if (!headerCell) return;
|
|
1670
|
+
event.preventDefault();
|
|
1671
|
+
event.stopPropagation();
|
|
1672
|
+
const startX = event.clientX;
|
|
1673
|
+
const startWidth = headerCell.getBoundingClientRect().width;
|
|
1674
|
+
let lastWidth = startWidth;
|
|
1675
|
+
const handleMove = (moveEvent) => {
|
|
1676
|
+
lastWidth = Math.max(88, startWidth + (moveEvent.clientX - startX));
|
|
1677
|
+
const newTemplate = this.controller.buildTemplateColumnsWithOverride(columnName, lastWidth);
|
|
1678
|
+
const root2 = this.shadowRoot ?? this;
|
|
1679
|
+
root2.querySelectorAll(".header-grid, .filter-grid, .body-grid").forEach((el) => {
|
|
1680
|
+
el.style.gridTemplateColumns = newTemplate;
|
|
1681
|
+
});
|
|
1682
|
+
};
|
|
1683
|
+
const handleUp = () => {
|
|
1684
|
+
window.removeEventListener("mousemove", handleMove);
|
|
1685
|
+
window.removeEventListener("mouseup", handleUp);
|
|
1686
|
+
this.controller.setColumnWidthOverride(columnName, lastWidth);
|
|
1687
|
+
};
|
|
1688
|
+
window.addEventListener("mousemove", handleMove);
|
|
1689
|
+
window.addEventListener("mouseup", handleUp);
|
|
1690
|
+
});
|
|
1575
1691
|
root.addEventListener("dblclick", (event) => {
|
|
1576
1692
|
const target = event.target;
|
|
1577
1693
|
if (!target || !this.controller) {
|
|
1578
1694
|
return;
|
|
1579
1695
|
}
|
|
1696
|
+
const resizer = target.closest(".column-resizer[data-column]");
|
|
1697
|
+
if (resizer && this.controller.canResizeColumns()) {
|
|
1698
|
+
const columnName2 = resizer.dataset["column"];
|
|
1699
|
+
if (columnName2) {
|
|
1700
|
+
event.preventDefault();
|
|
1701
|
+
event.stopPropagation();
|
|
1702
|
+
this.controller.setColumnWidthOverride(columnName2, this.measureAutoColumnWidth(columnName2));
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1580
1706
|
const cell = target.closest(".body-cell");
|
|
1581
1707
|
if (!cell) {
|
|
1582
1708
|
return;
|
|
@@ -1710,7 +1836,9 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1710
1836
|
if (!target?.classList.contains("grid-table") || this.suppressScrollEvent) {
|
|
1711
1837
|
return;
|
|
1712
1838
|
}
|
|
1839
|
+
this.lastScrollActivityAt = Date.now();
|
|
1713
1840
|
this.scrollPosition = target.scrollTop;
|
|
1841
|
+
this.horizontalScrollPosition = target.scrollLeft;
|
|
1714
1842
|
if (!this.snapshot?.pipeline.virtualizationEnabled) {
|
|
1715
1843
|
return;
|
|
1716
1844
|
}
|
|
@@ -1791,6 +1919,7 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1791
1919
|
const previousGridTable = root.querySelector(".grid-table");
|
|
1792
1920
|
if (previousGridTable && !this.suppressScrollEvent) {
|
|
1793
1921
|
this.scrollPosition = previousGridTable.scrollTop;
|
|
1922
|
+
this.horizontalScrollPosition = previousGridTable.scrollLeft;
|
|
1794
1923
|
}
|
|
1795
1924
|
if (!snapshot) {
|
|
1796
1925
|
root.innerHTML = `<style>${GRID_CORE_CSS}</style><section class="grid-shell ui-grid-shell"><div class="empty-state ui-grid-no-row-overlay"><strong>No grid options provided.</strong></div></section>`;
|
|
@@ -1837,12 +1966,18 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1837
1966
|
const bodyContent = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
|
|
1838
1967
|
const body = snapshot.pipeline.displayItems.length > 0 ? virtualizationEnabled ? `<div class="grid-virtual-spacer" style="height:${totalVirtualHeight}px"><div class="body-grid ui-grid-canvas grid-virtual-body" style="grid-template-columns:${templateColumns};top:${virtualOffset}px">${bodyContent}</div></div>` : `<div class="body-grid ui-grid-canvas" style="grid-template-columns:${templateColumns}">${bodyContent}</div>` : `<div class="empty-state ui-grid-no-row-overlay"><strong>${escapeHtml(options.emptyMessage ?? labels.emptyHeading)}</strong><p>${escapeHtml(labels.emptyDescription)}</p></div>`;
|
|
1839
1968
|
const pagination = paginationEnabled && showPagination ? this.renderPagination(snapshot) : "";
|
|
1840
|
-
const
|
|
1969
|
+
const hasViewportScroll = virtualizationEnabled || options.viewportHeight !== void 0;
|
|
1970
|
+
const gridTableStyle = `${hasViewportScroll ? `height:${viewportHeight}px;overflow-y:auto;` : ""}--ui-grid-header-sticky-top:${headerStickyTop}px;`;
|
|
1841
1971
|
root.innerHTML = `<style>${GRID_CORE_CSS}</style>${slotRegistry}<section class="grid-frame ui-grid" role="grid" aria-label="${escapeHtml(options.title ?? "Data grid")}"><div class="grid-table ui-grid-contents-wrapper" style="${gridTableStyle}"><div class="header-grid ui-grid-header ui-grid-header-canvas" style="grid-template-columns:${templateColumns}">${header}</div>${filterRow}${body}</div>${pagination}</section>`;
|
|
1842
1972
|
const gridTable = root.querySelector(".grid-table");
|
|
1843
|
-
if (gridTable && this.scrollPosition > 0
|
|
1973
|
+
if (gridTable && (this.scrollPosition > 0 || this.horizontalScrollPosition > 0)) {
|
|
1844
1974
|
this.suppressScrollEvent = true;
|
|
1845
|
-
|
|
1975
|
+
if (this.scrollPosition > 0) {
|
|
1976
|
+
gridTable.scrollTop = this.scrollPosition;
|
|
1977
|
+
}
|
|
1978
|
+
if (this.horizontalScrollPosition > 0) {
|
|
1979
|
+
gridTable.scrollLeft = this.horizontalScrollPosition;
|
|
1980
|
+
}
|
|
1846
1981
|
requestAnimationFrame(() => {
|
|
1847
1982
|
this.suppressScrollEvent = false;
|
|
1848
1983
|
});
|
|
@@ -1893,8 +2028,10 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1893
2028
|
const sortIconKey = sortDirection === SORT_DIRECTIONS2.asc ? "sortAsc" : sortDirection === SORT_DIRECTIONS2.desc ? "sortDesc" : "sortNone";
|
|
1894
2029
|
const draggable = canGridMoveColumns2(options) ? 'draggable="true"' : "";
|
|
1895
2030
|
const pinLabel = isPinned ? this.snapshot?.labels.unpin ?? "Unpin" : this.snapshot?.labels.pinColumn ?? "Pin";
|
|
2031
|
+
const canResize = controller.canResizeColumns();
|
|
1896
2032
|
const headerValue = escapeHtml(formatGridHeaderDisplayValue(buildGridHeaderContext(column)));
|
|
1897
|
-
|
|
2033
|
+
const resizerHtml = canResize ? `<button type="button" class="column-resizer" data-action="resize" data-column="${escapeHtml(column.name)}" aria-label="Resize ${escapeHtml(headerValue)} column" title="Drag to resize, double-click to auto fit"></button>` : "";
|
|
2034
|
+
return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span>${resizerHtml}</div>`;
|
|
1898
2035
|
}
|
|
1899
2036
|
renderFilterCell(column) {
|
|
1900
2037
|
const value = this.snapshot?.activeFilters[column.name] ?? "";
|
|
@@ -1910,6 +2047,23 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1910
2047
|
const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
|
|
1911
2048
|
return `<label class="${classes}" style="${stickyStyle}"><input class="ui-grid-filter-input" data-role="filter" data-column="${escapeHtml(column.name)}" placeholder="${escapeHtml(controller.filterPlaceholder(column))}" value="${escapeHtml(value)}" ${canFilter ? "" : "disabled"}></label>`;
|
|
1912
2049
|
}
|
|
2050
|
+
measureAutoColumnWidth(columnName) {
|
|
2051
|
+
const root = this.shadowRoot;
|
|
2052
|
+
if (root == null) return 176;
|
|
2053
|
+
const escaped = CSS.escape ? CSS.escape(columnName) : columnName.replace(/([\\".#:[\](){}+~> ])/g, "\\$1");
|
|
2054
|
+
const selectors = [
|
|
2055
|
+
`.header-cell[data-column="${escaped}"]`,
|
|
2056
|
+
`.filter-cell[data-column="${escaped}"]`,
|
|
2057
|
+
`.body-cell[data-column="${escaped}"] .cell-shell`
|
|
2058
|
+
];
|
|
2059
|
+
let maxWidth = 0;
|
|
2060
|
+
for (const selector of selectors) {
|
|
2061
|
+
for (const el of root.querySelectorAll(selector)) {
|
|
2062
|
+
maxWidth = Math.max(maxWidth, el.scrollWidth);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return maxWidth + 12;
|
|
2066
|
+
}
|
|
1913
2067
|
renderDisplayItem(item, displayIndex) {
|
|
1914
2068
|
if (!this.snapshot || !this.controller) {
|
|
1915
2069
|
return "";
|
|
@@ -1954,7 +2108,7 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1954
2108
|
editing ? "cell-editing" : ""
|
|
1955
2109
|
].filter(Boolean).join(" ");
|
|
1956
2110
|
const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
|
|
1957
|
-
return `<div class="${classes}" tabindex="0" data-row="${escapeHtml(rowId)}" data-column="${escapeHtml(columnName)}" style="${stickyStyle}"><div class="cell-shell" style="padding-inline-start:${escapeHtml(controller.cellIndent(row, column))}">${treeToggle}${expandToggle}
|
|
2111
|
+
return `<div class="${classes}" tabindex="0" data-row="${escapeHtml(rowId)}" data-column="${escapeHtml(columnName)}" style="${stickyStyle}"><div class="cell-shell" style="padding-inline-start:${escapeHtml(controller.cellIndent(row, column))}">${treeToggle}${expandToggle}<div class="cell-content">${content}</div></div></div>`;
|
|
1958
2112
|
}
|
|
1959
2113
|
renderPagination(snapshot) {
|
|
1960
2114
|
const pageSizes = snapshot.options.paginationPageSizes ?? [10, 25, 50, 100];
|
|
@@ -1968,6 +2122,9 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
1968
2122
|
}
|
|
1969
2123
|
renderCellTemplate(row, column, displayIndex) {
|
|
1970
2124
|
const templateMarkup = this.getTemplateMarkup(this.cellSlotName(column));
|
|
2125
|
+
return this.renderCellTemplateFromMarkup(row, column, displayIndex, templateMarkup);
|
|
2126
|
+
}
|
|
2127
|
+
renderCellTemplateFromMarkup(row, column, displayIndex, templateMarkup) {
|
|
1971
2128
|
if (!templateMarkup) {
|
|
1972
2129
|
return `<span class="cell-value">${escapeHtml(this.controller?.displayValue(row, column) ?? "")}</span>`;
|
|
1973
2130
|
}
|
|
@@ -2020,6 +2177,79 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
2020
2177
|
rowIndexFor(row, fallback = 0) {
|
|
2021
2178
|
return this.snapshot?.pipeline.visibleRows.findIndex((candidate) => candidate.id === row.id) ?? fallback;
|
|
2022
2179
|
}
|
|
2180
|
+
classifyDataRefresh(previousSnapshot, nextSnapshot) {
|
|
2181
|
+
if (!nextSnapshot) {
|
|
2182
|
+
return { mode: "full", changedRowIds: null };
|
|
2183
|
+
}
|
|
2184
|
+
if (!previousSnapshot) {
|
|
2185
|
+
return {
|
|
2186
|
+
mode: nextSnapshot.pipeline.virtualizationEnabled ? "virtual" : "full",
|
|
2187
|
+
changedRowIds: null
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
if (previousSnapshot.visibleColumns.length !== nextSnapshot.visibleColumns.length) {
|
|
2191
|
+
return { mode: "full", changedRowIds: null };
|
|
2192
|
+
}
|
|
2193
|
+
for (let index = 0; index < previousSnapshot.visibleColumns.length; index += 1) {
|
|
2194
|
+
if (previousSnapshot.visibleColumns[index]?.name !== nextSnapshot.visibleColumns[index]?.name) {
|
|
2195
|
+
return { mode: "full", changedRowIds: null };
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
const previousWindow = this.getRenderedDisplayWindow(previousSnapshot);
|
|
2199
|
+
const nextWindow = this.getRenderedDisplayWindow(nextSnapshot);
|
|
2200
|
+
if (previousWindow.startIndex !== nextWindow.startIndex || previousWindow.items.length !== nextWindow.items.length) {
|
|
2201
|
+
return {
|
|
2202
|
+
mode: nextSnapshot.pipeline.virtualizationEnabled ? "virtual" : "full",
|
|
2203
|
+
changedRowIds: null
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
const changedRowIds = /* @__PURE__ */ new Set();
|
|
2207
|
+
for (let index = 0; index < previousWindow.items.length; index += 1) {
|
|
2208
|
+
const previousItem = previousWindow.items[index];
|
|
2209
|
+
const nextItem = nextWindow.items[index];
|
|
2210
|
+
if (!previousItem || !nextItem || previousItem.kind !== "row" || nextItem.kind !== "row") {
|
|
2211
|
+
return {
|
|
2212
|
+
mode: nextSnapshot.pipeline.virtualizationEnabled ? "virtual" : "full",
|
|
2213
|
+
changedRowIds: null
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
if (previousItem.row.id !== nextItem.row.id) {
|
|
2217
|
+
return {
|
|
2218
|
+
mode: nextSnapshot.pipeline.virtualizationEnabled ? "virtual" : "full",
|
|
2219
|
+
changedRowIds: null
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
if (previousItem.row.entity !== nextItem.row.entity) {
|
|
2223
|
+
changedRowIds.add(nextItem.row.id);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
return {
|
|
2227
|
+
mode: changedRowIds.size > 0 ? "patch" : null,
|
|
2228
|
+
changedRowIds: changedRowIds.size > 0 ? changedRowIds : null
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
getRenderedDisplayWindow(snapshot) {
|
|
2232
|
+
if (!snapshot.pipeline.virtualizationEnabled) {
|
|
2233
|
+
return {
|
|
2234
|
+
startIndex: 0,
|
|
2235
|
+
items: snapshot.pipeline.displayItems
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
|
|
2239
|
+
const viewportHeight = snapshot.options.viewportHeight ?? 560;
|
|
2240
|
+
const bodyViewportHeight = Math.max(snapshot.rowSize, viewportHeight - stickyChromeHeight);
|
|
2241
|
+
const bodyScrollTop = Math.max(0, this.scrollPosition - stickyChromeHeight);
|
|
2242
|
+
const overscan = 4;
|
|
2243
|
+
const startIndex = Math.max(0, Math.floor(bodyScrollTop / snapshot.rowSize) - overscan);
|
|
2244
|
+
const visibleCount = Math.ceil(bodyViewportHeight / snapshot.rowSize) + overscan * 2;
|
|
2245
|
+
return {
|
|
2246
|
+
startIndex,
|
|
2247
|
+
items: snapshot.pipeline.displayItems.slice(
|
|
2248
|
+
startIndex,
|
|
2249
|
+
Math.min(snapshot.pipeline.displayItems.length, startIndex + visibleCount)
|
|
2250
|
+
)
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2023
2253
|
cellSlotName(column) {
|
|
2024
2254
|
return `cell-${column.name}`;
|
|
2025
2255
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ornery/ui-grid-vanilla",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Vanilla DOM wrapper for the Rust/WASM-backed @ornery/ui-grid-core custom element",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@ornery/ui-grid-core": "
|
|
19
|
+
"@ornery/ui-grid-core": "0.1.10"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"typescript": "~5.8.0",
|
package/src/grid-controller.ts
CHANGED
|
@@ -134,6 +134,7 @@ export class VanillaGridController {
|
|
|
134
134
|
private expandedTreeRows: Record<string, boolean> = {};
|
|
135
135
|
private pinnedColumns: PinnedColumnState = {};
|
|
136
136
|
private columnOrder: string[] = [];
|
|
137
|
+
private columnWidthOverrides: Record<string, string> = {};
|
|
137
138
|
private currentPage = 1;
|
|
138
139
|
private pageSize = 0;
|
|
139
140
|
private editingCell: GridCellPosition | null = null;
|
|
@@ -590,6 +591,27 @@ export class VanillaGridController {
|
|
|
590
591
|
this.refresh();
|
|
591
592
|
}
|
|
592
593
|
|
|
594
|
+
canResizeColumns(): boolean {
|
|
595
|
+
return this.options.enableColumnResizing !== false;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
setColumnWidthOverride(columnName: string, widthPx: number): void {
|
|
599
|
+
const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
|
|
600
|
+
this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
|
|
601
|
+
this.refresh();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** Returns the grid-template-columns string as if `columnName` had the given width,
|
|
605
|
+
* without triggering a full refresh. Used for smooth drag-resize DOM updates. */
|
|
606
|
+
buildTemplateColumnsWithOverride(columnName: string, widthPx: number): string {
|
|
607
|
+
const widthStr = `${Math.max(88, Math.round(widthPx))}px`;
|
|
608
|
+
return buildGridTemplateColumns(
|
|
609
|
+
this.visibleColumns.map((c) =>
|
|
610
|
+
c.name === columnName ? { ...c, width: widthStr } : c,
|
|
611
|
+
),
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
593
615
|
isGroupingEnabled(): boolean {
|
|
594
616
|
return isGridGroupingEnabled(this.options);
|
|
595
617
|
}
|
|
@@ -743,10 +765,15 @@ export class VanillaGridController {
|
|
|
743
765
|
|
|
744
766
|
private refresh(): void {
|
|
745
767
|
const orderedColumns = orderVisibleColumns(this.options.columnDefs, this.columnOrder);
|
|
768
|
+
const applyWidthOverrides = (columns: GridColumnDef[]): GridColumnDef[] =>
|
|
769
|
+
columns.map((col) => {
|
|
770
|
+
const override = this.columnWidthOverrides[col.name];
|
|
771
|
+
return override == null ? col : { ...col, width: override };
|
|
772
|
+
});
|
|
746
773
|
const pinnedEntries = Object.entries(this.pinnedColumns);
|
|
747
774
|
|
|
748
775
|
if (pinnedEntries.length === 0) {
|
|
749
|
-
this.visibleColumns = orderedColumns;
|
|
776
|
+
this.visibleColumns = applyWidthOverrides(orderedColumns);
|
|
750
777
|
} else {
|
|
751
778
|
const byName = new Map(orderedColumns.map((column) => [column.name, column]));
|
|
752
779
|
const pinnedLeft = pinnedEntries
|
|
@@ -760,7 +787,7 @@ export class VanillaGridController {
|
|
|
760
787
|
const middleColumns = orderedColumns.filter(
|
|
761
788
|
(column) => this.pinnedColumns[column.name] === undefined,
|
|
762
789
|
);
|
|
763
|
-
this.visibleColumns = [...pinnedLeft, ...middleColumns, ...pinnedRight];
|
|
790
|
+
this.visibleColumns = applyWidthOverrides([...pinnedLeft, ...middleColumns, ...pinnedRight]);
|
|
764
791
|
}
|
|
765
792
|
|
|
766
793
|
this.pipeline = defaultGridEngine.buildPipeline({
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
clearRustWasmGridEngine,
|
|
7
7
|
} from '@ornery/ui-grid-core';
|
|
8
8
|
import {
|
|
9
|
+
defineStandaloneUiGridElement,
|
|
9
10
|
mountVanillaUiGrid,
|
|
10
11
|
registerVanillaUiGridRustModule,
|
|
11
12
|
type GridOptions,
|
|
@@ -274,4 +275,34 @@ describe('mountVanillaUiGrid integration', () => {
|
|
|
274
275
|
|
|
275
276
|
expect(detailCard.textContent).toContain('Owner Mina Patel');
|
|
276
277
|
});
|
|
278
|
+
|
|
279
|
+
it('preserves declarative attribute data when augmenting options imperatively', async () => {
|
|
280
|
+
// Use the already-registered 'ui-grid-element-vanilla-test' tag (from mountVanillaUiGrid
|
|
281
|
+
// in prior tests) to avoid the jsdom constraint of not registering the same class twice.
|
|
282
|
+
const grid = document.createElement('ui-grid-element-vanilla-test') as HTMLElement & {
|
|
283
|
+
options: GridOptions;
|
|
284
|
+
};
|
|
285
|
+
document.getElementById('app')!.appendChild(grid);
|
|
286
|
+
|
|
287
|
+
grid.setAttribute('grid-id', 'declarative-test');
|
|
288
|
+
grid.setAttribute('column-defs', JSON.stringify([{ name: 'name', displayName: 'Customer' }]));
|
|
289
|
+
grid.setAttribute('data', JSON.stringify([{ id: 'row-1', name: 'Gamma' }]));
|
|
290
|
+
|
|
291
|
+
// Flush the attributeChangedCallback microtask so attributeOptions are populated.
|
|
292
|
+
await new Promise<void>((r) => setTimeout(r, 0));
|
|
293
|
+
|
|
294
|
+
// Before any imperative assignment, the getter must surface attribute-derived data.
|
|
295
|
+
expect((grid.options.data as Array<{ id: string; name: string }>)[0].name).toBe('Gamma');
|
|
296
|
+
expect(grid.options.columnDefs[0].name).toBe('name');
|
|
297
|
+
|
|
298
|
+
// Simulate the bridge pattern: augment without losing attribute-derived data.
|
|
299
|
+
grid.options = {
|
|
300
|
+
...grid.options,
|
|
301
|
+
viewportHeight: 999,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
expect((grid.options.data as Array<{ id: string; name: string }>)[0].name).toBe('Gamma');
|
|
305
|
+
expect(grid.options.viewportHeight).toBe(999);
|
|
306
|
+
});
|
|
307
|
+
|
|
277
308
|
});
|
package/src/index.test.ts
CHANGED
|
@@ -40,7 +40,7 @@ describe('mountVanillaUiGrid', () => {
|
|
|
40
40
|
expect(registerRustWasmGridEngine).toHaveBeenCalledTimes(1);
|
|
41
41
|
expect(customElements.get('ui-grid-element')).toBeDefined();
|
|
42
42
|
expect(grid.tagName.toLowerCase()).toBe('ui-grid-element');
|
|
43
|
-
expect(grid.options).
|
|
43
|
+
expect(grid.options).toStrictEqual(options);
|
|
44
44
|
expect(target.firstElementChild).toBe(grid);
|
|
45
45
|
});
|
|
46
46
|
|