@ornery/ui-grid-vanilla 1.0.0 → 1.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/AGENTS.md +70 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +165 -26
- package/dist/index.mjs +165 -26
- package/package.json +2 -2
- package/src/auto-resize.test.ts +4 -3
- package/src/events.ts +9 -0
- package/src/focus.ts +40 -14
- package/src/grid-controller.ts +149 -22
- package/src/render.ts +2 -2
- package/src/ui-grid-standalone.element.ts +13 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @ornery/ui-grid-vanilla — Agent Instructions
|
|
2
|
+
|
|
3
|
+
The rendering engine. A framework-free custom element (`<ui-grid-element>`) that owns the Shadow DOM, CSS Grid layout, event handling, and the `VanillaGridController` state machine. Angular and React wrappers mount this element — they never render cells themselves.
|
|
4
|
+
|
|
5
|
+
## Build & Test
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run build --prefix projects/ui-grid-vanilla # tsup → dist/
|
|
9
|
+
npm test --prefix projects/ui-grid-vanilla # vitest (138 tests)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Depends on `@ornery/ui-grid-core` — build core first.
|
|
13
|
+
|
|
14
|
+
## Key Files
|
|
15
|
+
|
|
16
|
+
| File | Responsibility |
|
|
17
|
+
|------|---------------|
|
|
18
|
+
| `grid-controller.ts` | `VanillaGridController` — all mutable grid state, snapshot emission, column width calculation |
|
|
19
|
+
| `ui-grid-standalone.element.ts` | Custom element class: lifecycle, render orchestration, scroll handling, ResizeObserver |
|
|
20
|
+
| `render.ts` | HTML string builders for header/filter/body cells |
|
|
21
|
+
| `patch.ts` | DOM-diffing fast path: patches existing shadow DOM without full re-render |
|
|
22
|
+
| `events.ts` | All event listeners: click, mousedown (resize drag), dblclick (auto-fit), keydown, scroll, drag-and-drop |
|
|
23
|
+
| `focus.ts` | CellNav focus management, `measureAutoColumnWidth` |
|
|
24
|
+
| `framework-slots.ts` | `FrameworkSlotBridge` — stages slot add/remove deltas, `flush()` dispatches `cellSlotsChanged` |
|
|
25
|
+
| `attribute-bridge.ts` | Parses HTML attributes into `GridOptions` |
|
|
26
|
+
| `templates/index.ts` | Markup fragment helpers (filterRow, bodyVirtual, bodyStatic) |
|
|
27
|
+
| `ui-grid-shell.template.ts` | Full shell template (grid-frame → grid-table → header-strip + filter-strip + body-viewport) |
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
### Render Paths
|
|
32
|
+
|
|
33
|
+
1. **`renderFull`** — Full innerHTML swap of the shadow DOM shell. Used on structure changes (column add/remove, virtualization toggle).
|
|
34
|
+
2. **`renderPatch`** — In-place DOM mutation. Header grid-template-columns, filter cells, body rows patched individually. Preserves focus and scroll.
|
|
35
|
+
3. **`renderVirtualBody`** — Scroll-triggered fast path. Only replaces `.grid-virtual-body` innerHTML + calls `frameworkSlots.flush()`.
|
|
36
|
+
4. **`patchCells`** — Data-only update. Iterates existing body cells, updates `.cell-content` innerHTML.
|
|
37
|
+
|
|
38
|
+
### Column Width (drawnWidth)
|
|
39
|
+
|
|
40
|
+
Column widths are resolved to **fixed pixel values** by `buildGridTemplateColumns()`, mirroring the old ui-grid's `updateColumnWidths`/`drawnWidth` approach:
|
|
41
|
+
|
|
42
|
+
- Fixed-px columns: used as-is
|
|
43
|
+
- Percentage columns: resolved against viewport width
|
|
44
|
+
- Flex columns (default `minmax(11rem, 1fr)`): remaining space divided equally, minimum 176px
|
|
45
|
+
- Selection row-header: fixed at configured width, not resizable
|
|
46
|
+
|
|
47
|
+
The element feeds its measured width to the controller via `setViewportWidth()`. The ResizeObserver keeps it current.
|
|
48
|
+
|
|
49
|
+
### State Management
|
|
50
|
+
|
|
51
|
+
`setOptions()` must NOT clobber interactive state. The controller tracks `lastOptions*` values and only applies declarative state when the consumer's value structurally changed. Interactive state that survives `setOptions`: sort, filters, expanded rows, selection, pagination, cell navigation, column widths, column order.
|
|
52
|
+
|
|
53
|
+
### Framework Slot System
|
|
54
|
+
|
|
55
|
+
1. Wrapper calls `setFrameworkRenderedSlots({ cells: ['colA', 'colB'] })`
|
|
56
|
+
2. Render emits `<slot name="cell-colA-rowId">` placeholders
|
|
57
|
+
3. `flush()` dispatches `cellSlotsChanged` with `{ added, removed }` deltas
|
|
58
|
+
4. Wrapper listens, creates framework views, appends to light DOM with matching `slot` attribute
|
|
59
|
+
|
|
60
|
+
## Per-Column Flags
|
|
61
|
+
|
|
62
|
+
- `enableColumnResizing: false` — no resize handle rendered, column width locked
|
|
63
|
+
- `enableColumnMoving: false` — not draggable, not a valid drop target
|
|
64
|
+
|
|
65
|
+
## Do NOT
|
|
66
|
+
|
|
67
|
+
- Skip `frameworkSlots.flush()` after rendering DOM that contains slot placeholders
|
|
68
|
+
- Call `setFrameworkRenderedSlots` on every options update — only on structural column changes
|
|
69
|
+
- Use `innerHTML` on `.grid-body-viewport` — it destroys scroll position; only patch inside `.body-grid`
|
|
70
|
+
- Forget to sync horizontal scroll after a full render (`syncHeaderHorizontalScroll`)
|
package/dist/index.d.mts
CHANGED
|
@@ -245,6 +245,10 @@ declare class VanillaGridController {
|
|
|
245
245
|
private activeFilters;
|
|
246
246
|
private sortState;
|
|
247
247
|
private groupByColumns;
|
|
248
|
+
private lastOptionsGroupBy;
|
|
249
|
+
private lastOptionsPinnedState;
|
|
250
|
+
private lastOptionsInfiniteScrollUp;
|
|
251
|
+
private lastOptionsInfiniteScrollDown;
|
|
248
252
|
private collapsedGroups;
|
|
249
253
|
private hiddenRowReasons;
|
|
250
254
|
private expandedRows;
|
|
@@ -252,6 +256,7 @@ declare class VanillaGridController {
|
|
|
252
256
|
private pinnedColumns;
|
|
253
257
|
private columnOrder;
|
|
254
258
|
private columnWidthOverrides;
|
|
259
|
+
private viewportWidth;
|
|
255
260
|
private currentPage;
|
|
256
261
|
private pageSize;
|
|
257
262
|
private editingCell;
|
|
@@ -387,6 +392,8 @@ declare class VanillaGridController {
|
|
|
387
392
|
setInfiniteScrollResetHandler(handler: (() => void) | null): void;
|
|
388
393
|
getInfiniteScrollState(): Readonly<GridInfiniteScrollState>;
|
|
389
394
|
canResizeColumns(): boolean;
|
|
395
|
+
setViewportWidth(width: number): void;
|
|
396
|
+
getViewportWidth(): number;
|
|
390
397
|
setColumnWidthOverride(columnName: string, widthPx: number): void;
|
|
391
398
|
/** Returns the grid-template-columns string as if `columnName` had the given width,
|
|
392
399
|
* without triggering a full refresh. Used for smooth drag-resize DOM updates. */
|
package/dist/index.d.ts
CHANGED
|
@@ -245,6 +245,10 @@ declare class VanillaGridController {
|
|
|
245
245
|
private activeFilters;
|
|
246
246
|
private sortState;
|
|
247
247
|
private groupByColumns;
|
|
248
|
+
private lastOptionsGroupBy;
|
|
249
|
+
private lastOptionsPinnedState;
|
|
250
|
+
private lastOptionsInfiniteScrollUp;
|
|
251
|
+
private lastOptionsInfiniteScrollDown;
|
|
248
252
|
private collapsedGroups;
|
|
249
253
|
private hiddenRowReasons;
|
|
250
254
|
private expandedRows;
|
|
@@ -252,6 +256,7 @@ declare class VanillaGridController {
|
|
|
252
256
|
private pinnedColumns;
|
|
253
257
|
private columnOrder;
|
|
254
258
|
private columnWidthOverrides;
|
|
259
|
+
private viewportWidth;
|
|
255
260
|
private currentPage;
|
|
256
261
|
private pageSize;
|
|
257
262
|
private editingCell;
|
|
@@ -387,6 +392,8 @@ declare class VanillaGridController {
|
|
|
387
392
|
setInfiniteScrollResetHandler(handler: (() => void) | null): void;
|
|
388
393
|
getInfiniteScrollState(): Readonly<GridInfiniteScrollState>;
|
|
389
394
|
canResizeColumns(): boolean;
|
|
395
|
+
setViewportWidth(width: number): void;
|
|
396
|
+
getViewportWidth(): number;
|
|
390
397
|
setColumnWidthOverride(columnName: string, widthPx: number): void;
|
|
391
398
|
/** Returns the grid-template-columns string as if `columnName` had the given width,
|
|
392
399
|
* without triggering a full refresh. Used for smooth drag-resize DOM updates. */
|
package/dist/index.js
CHANGED
|
@@ -649,7 +649,7 @@ function renderHeaderCell(el, column, sortEnabled, groupingEnabled, pinningEnabl
|
|
|
649
649
|
const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
|
|
650
650
|
const sortIconKey = sortDirection === import_ui_grid_core2.SORT_DIRECTIONS.asc ? "sortAsc" : sortDirection === import_ui_grid_core2.SORT_DIRECTIONS.desc ? "sortDesc" : "sortNone";
|
|
651
651
|
const pinLabel = isPinned ? el.snapshot?.labels.unpin ?? "Unpin" : el.snapshot?.labels.pinColumn ?? "Pin";
|
|
652
|
-
const canResize = controller.canResizeColumns();
|
|
652
|
+
const canResize = controller.canResizeColumns() && column.enableColumnResizing !== false;
|
|
653
653
|
const headerValue = escapeHtml((0, import_ui_grid_core2.formatGridHeaderDisplayValue)((0, import_ui_grid_core2.buildGridHeaderContext)(column)));
|
|
654
654
|
const resizerHtml = canResize ? resizerMarkup(escapeHtml(column.name), escapeHtml(headerValue)) : "";
|
|
655
655
|
const isPinnedLeftLast = controller.isPinnedLeftLast(column);
|
|
@@ -657,7 +657,7 @@ function renderHeaderCell(el, column, sortEnabled, groupingEnabled, pinningEnabl
|
|
|
657
657
|
const isPinMenuOpen = el.openPinMenuColumn === column.name;
|
|
658
658
|
const isDragTarget = el.dropTargetColumnName === column.name;
|
|
659
659
|
const isDragging = el.draggedColumnName === column.name;
|
|
660
|
-
const isDraggable = (0, import_ui_grid_core2.canGridMoveColumns)(options);
|
|
660
|
+
const isDraggable = (0, import_ui_grid_core2.canGridMoveColumns)(options) && column.enableColumnMoving !== false;
|
|
661
661
|
const className = headerCellClass(
|
|
662
662
|
sortDirection !== import_ui_grid_core2.SORT_DIRECTIONS.none,
|
|
663
663
|
isPinned,
|
|
@@ -1504,18 +1504,35 @@ function measureAutoColumnWidth(el, columnName) {
|
|
|
1504
1504
|
const root = el.shadowRoot;
|
|
1505
1505
|
if (root == null) return 176;
|
|
1506
1506
|
const escaped = cssEscape(columnName);
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
`.body-cell[data-column="${escaped}"] .cell-shell`
|
|
1511
|
-
];
|
|
1507
|
+
const probe = document.createElement("div");
|
|
1508
|
+
probe.style.cssText = "position:absolute;top:-9999px;left:-9999px;width:max-content;visibility:hidden;";
|
|
1509
|
+
root.appendChild(probe);
|
|
1512
1510
|
let maxWidth = 0;
|
|
1513
|
-
for (const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1511
|
+
for (const header of root.querySelectorAll(`.header-cell[data-column="${escaped}"]`)) {
|
|
1512
|
+
const clone = header.cloneNode(true);
|
|
1513
|
+
clone.style.cssText = "display:grid;grid-template-columns:auto auto;gap:inherit;padding:inherit;width:max-content;";
|
|
1514
|
+
probe.appendChild(clone);
|
|
1515
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
1516
|
+
probe.removeChild(clone);
|
|
1517
|
+
}
|
|
1518
|
+
for (const filter of root.querySelectorAll(`ui-grid-filter-cell[data-column="${escaped}"], .filter-cell[data-column="${escaped}"]`)) {
|
|
1519
|
+
const clone = filter.cloneNode(true);
|
|
1520
|
+
clone.style.cssText = "display:block;width:max-content;";
|
|
1521
|
+
probe.appendChild(clone);
|
|
1522
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
1523
|
+
probe.removeChild(clone);
|
|
1524
|
+
}
|
|
1525
|
+
for (const cell of root.querySelectorAll(`.body-cell[data-column="${escaped}"]`)) {
|
|
1526
|
+
const shell = cell.querySelector(".cell-shell");
|
|
1527
|
+
if (!shell) continue;
|
|
1528
|
+
const clone = shell.cloneNode(true);
|
|
1529
|
+
clone.style.cssText = "display:flex;width:max-content;";
|
|
1530
|
+
probe.appendChild(clone);
|
|
1531
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
1532
|
+
probe.removeChild(clone);
|
|
1533
|
+
}
|
|
1534
|
+
root.removeChild(probe);
|
|
1535
|
+
return Math.max(88, maxWidth + 12);
|
|
1519
1536
|
}
|
|
1520
1537
|
|
|
1521
1538
|
// src/events.ts
|
|
@@ -1987,6 +2004,11 @@ function bindEvents(el) {
|
|
|
1987
2004
|
event.preventDefault();
|
|
1988
2005
|
return;
|
|
1989
2006
|
}
|
|
2007
|
+
const colDef = el.snapshot.visibleColumns.find((c) => c.name === columnName);
|
|
2008
|
+
if (colDef?.enableColumnMoving === false) {
|
|
2009
|
+
event.preventDefault();
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
1990
2012
|
el.draggedColumnName = columnName;
|
|
1991
2013
|
el.dropTargetColumnName = null;
|
|
1992
2014
|
dragEvent.dataTransfer.effectAllowed = "move";
|
|
@@ -2000,6 +2022,8 @@ function bindEvents(el) {
|
|
|
2000
2022
|
if (!headerCell || !el.draggedColumnName || !dragEvent.dataTransfer) return;
|
|
2001
2023
|
const columnName = headerCell.dataset["column"];
|
|
2002
2024
|
if (!columnName || columnName === el.draggedColumnName) return;
|
|
2025
|
+
const colDef = el.snapshot?.visibleColumns.find((c) => c.name === columnName);
|
|
2026
|
+
if (colDef?.enableColumnMoving === false) return;
|
|
2003
2027
|
event.preventDefault();
|
|
2004
2028
|
dragEvent.dataTransfer.dropEffect = "move";
|
|
2005
2029
|
if (el.dropTargetColumnName !== columnName) {
|
|
@@ -2140,8 +2164,72 @@ var import_ui_grid_core5 = require("@ornery/ui-grid-core");
|
|
|
2140
2164
|
function orderVisibleColumns(columns, order) {
|
|
2141
2165
|
return [...columns].filter((column) => column.visible !== false).sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
|
|
2142
2166
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2167
|
+
var DEFAULT_COL_MIN_WIDTH = 176;
|
|
2168
|
+
function buildGridTemplateColumns(columns, viewportWidth) {
|
|
2169
|
+
if (!viewportWidth || viewportWidth <= 0) {
|
|
2170
|
+
return columns.map((column) => (0, import_ui_grid_core5.gridColumnWidth)(column)).join(" ");
|
|
2171
|
+
}
|
|
2172
|
+
const availableWidth = viewportWidth;
|
|
2173
|
+
const resolved = new Array(columns.length);
|
|
2174
|
+
const flexIndices = [];
|
|
2175
|
+
let usedWidth = 0;
|
|
2176
|
+
for (let i = 0; i < columns.length; i++) {
|
|
2177
|
+
const raw = columns[i].width;
|
|
2178
|
+
const minWidth = columns[i].minWidth;
|
|
2179
|
+
const maxWidth = columns[i].maxWidth ?? Infinity;
|
|
2180
|
+
if (raw && raw.endsWith("px")) {
|
|
2181
|
+
let px = parseInt(raw, 10);
|
|
2182
|
+
if (minWidth != null) px = Math.max(minWidth, px);
|
|
2183
|
+
px = Math.min(maxWidth, px);
|
|
2184
|
+
resolved[i] = px;
|
|
2185
|
+
usedWidth += px;
|
|
2186
|
+
} else if (raw && raw.endsWith("%")) {
|
|
2187
|
+
const pct = parseInt(raw.replace(/%/g, ""), 10);
|
|
2188
|
+
let w = Math.round(pct / 100 * availableWidth);
|
|
2189
|
+
if (minWidth != null) w = Math.max(minWidth, w);
|
|
2190
|
+
w = Math.min(maxWidth, w);
|
|
2191
|
+
resolved[i] = w;
|
|
2192
|
+
usedWidth += w;
|
|
2193
|
+
} else {
|
|
2194
|
+
flexIndices.push(i);
|
|
2195
|
+
resolved[i] = 0;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
if (flexIndices.length > 0) {
|
|
2199
|
+
const remaining = Math.max(0, availableWidth - usedWidth);
|
|
2200
|
+
const perFlex = Math.floor(remaining / flexIndices.length);
|
|
2201
|
+
for (const idx of flexIndices) {
|
|
2202
|
+
const minWidth = columns[idx].minWidth ?? DEFAULT_COL_MIN_WIDTH;
|
|
2203
|
+
const maxWidth = columns[idx].maxWidth ?? Infinity;
|
|
2204
|
+
const w = Math.max(minWidth, Math.min(maxWidth, perFlex));
|
|
2205
|
+
resolved[idx] = w;
|
|
2206
|
+
usedWidth += w;
|
|
2207
|
+
}
|
|
2208
|
+
let leftover = availableWidth - usedWidth;
|
|
2209
|
+
let changed = true;
|
|
2210
|
+
while (leftover > 0 && changed) {
|
|
2211
|
+
changed = false;
|
|
2212
|
+
for (const idx of flexIndices) {
|
|
2213
|
+
if (leftover <= 0) break;
|
|
2214
|
+
const maxWidth = columns[idx].maxWidth ?? Infinity;
|
|
2215
|
+
if (resolved[idx] < maxWidth) {
|
|
2216
|
+
resolved[idx]++;
|
|
2217
|
+
leftover--;
|
|
2218
|
+
changed = true;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
return resolved.map((w) => `${w}px`).join(" ");
|
|
2224
|
+
}
|
|
2225
|
+
function pinnedStatesEqual(a, b) {
|
|
2226
|
+
const keysA = Object.keys(a);
|
|
2227
|
+
const keysB = Object.keys(b);
|
|
2228
|
+
if (keysA.length !== keysB.length) return false;
|
|
2229
|
+
for (const key of keysA) {
|
|
2230
|
+
if (a[key] !== b[key]) return false;
|
|
2231
|
+
}
|
|
2232
|
+
return true;
|
|
2145
2233
|
}
|
|
2146
2234
|
var VanillaGridController = class {
|
|
2147
2235
|
options;
|
|
@@ -2151,6 +2239,10 @@ var VanillaGridController = class {
|
|
|
2151
2239
|
direction: import_ui_grid_core5.SORT_DIRECTIONS.none
|
|
2152
2240
|
};
|
|
2153
2241
|
groupByColumns = [];
|
|
2242
|
+
lastOptionsGroupBy = void 0;
|
|
2243
|
+
lastOptionsPinnedState = void 0;
|
|
2244
|
+
lastOptionsInfiniteScrollUp = void 0;
|
|
2245
|
+
lastOptionsInfiniteScrollDown = void 0;
|
|
2154
2246
|
collapsedGroups = {};
|
|
2155
2247
|
hiddenRowReasons = {};
|
|
2156
2248
|
expandedRows = {};
|
|
@@ -2158,6 +2250,7 @@ var VanillaGridController = class {
|
|
|
2158
2250
|
pinnedColumns = {};
|
|
2159
2251
|
columnOrder = [];
|
|
2160
2252
|
columnWidthOverrides = {};
|
|
2253
|
+
viewportWidth = 0;
|
|
2161
2254
|
currentPage = 1;
|
|
2162
2255
|
pageSize = 0;
|
|
2163
2256
|
editingCell = null;
|
|
@@ -2194,7 +2287,12 @@ var VanillaGridController = class {
|
|
|
2194
2287
|
this.validatorRegistry = (0, import_ui_grid_core5.createGridValidatorRegistry)(this.labels);
|
|
2195
2288
|
this.columnOrder = options.columnDefs.map((column) => column.name);
|
|
2196
2289
|
this.groupByColumns = options.grouping?.groupBy ? [...options.grouping.groupBy] : [];
|
|
2290
|
+
this.lastOptionsGroupBy = options.grouping?.groupBy ? [...options.grouping.groupBy] : void 0;
|
|
2197
2291
|
this.pinnedColumns = (0, import_ui_grid_core5.buildInitialPinnedState)(options.columnDefs);
|
|
2292
|
+
const declaresPin = options.columnDefs.some((col) => col.pinnedLeft === true || col.pinnedRight === true);
|
|
2293
|
+
this.lastOptionsPinnedState = declaresPin ? { ...this.pinnedColumns } : void 0;
|
|
2294
|
+
this.lastOptionsInfiniteScrollUp = options.infiniteScrollUp;
|
|
2295
|
+
this.lastOptionsInfiniteScrollDown = options.infiniteScrollDown;
|
|
2198
2296
|
this.infiniteScrollState = (0, import_ui_grid_core5.resetInfiniteScrollState)(
|
|
2199
2297
|
options.infiniteScrollUp === true,
|
|
2200
2298
|
options.infiniteScrollDown !== false
|
|
@@ -2411,20 +2509,41 @@ var VanillaGridController = class {
|
|
|
2411
2509
|
}
|
|
2412
2510
|
}
|
|
2413
2511
|
if (options.grouping?.groupBy !== void 0) {
|
|
2414
|
-
|
|
2512
|
+
const incoming = options.grouping.groupBy;
|
|
2513
|
+
const prev = this.lastOptionsGroupBy;
|
|
2514
|
+
const changed = !prev || prev.length !== incoming.length || prev.some((col, i) => col !== incoming[i]);
|
|
2515
|
+
if (changed) {
|
|
2516
|
+
this.groupByColumns = [...incoming];
|
|
2517
|
+
this.lastOptionsGroupBy = [...incoming];
|
|
2518
|
+
}
|
|
2519
|
+
} else if (this.lastOptionsGroupBy !== void 0) {
|
|
2520
|
+
this.lastOptionsGroupBy = void 0;
|
|
2415
2521
|
}
|
|
2416
2522
|
const declaresPin = options.columnDefs.some(
|
|
2417
2523
|
(col) => col.pinnedLeft === true || col.pinnedRight === true
|
|
2418
2524
|
);
|
|
2419
2525
|
if (declaresPin) {
|
|
2420
|
-
|
|
2526
|
+
const incoming = (0, import_ui_grid_core5.buildInitialPinnedState)(options.columnDefs);
|
|
2527
|
+
const prev = this.lastOptionsPinnedState;
|
|
2528
|
+
if (!prev || !pinnedStatesEqual(prev, incoming)) {
|
|
2529
|
+
this.pinnedColumns = incoming;
|
|
2530
|
+
this.lastOptionsPinnedState = { ...incoming };
|
|
2531
|
+
}
|
|
2532
|
+
} else if (this.lastOptionsPinnedState !== void 0) {
|
|
2533
|
+
this.lastOptionsPinnedState = void 0;
|
|
2421
2534
|
}
|
|
2422
2535
|
if (options.infiniteScrollUp !== void 0 || options.infiniteScrollDown !== void 0) {
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2536
|
+
const upChanged = options.infiniteScrollUp !== void 0 && options.infiniteScrollUp !== this.lastOptionsInfiniteScrollUp;
|
|
2537
|
+
const downChanged = options.infiniteScrollDown !== void 0 && options.infiniteScrollDown !== this.lastOptionsInfiniteScrollDown;
|
|
2538
|
+
if (upChanged || downChanged) {
|
|
2539
|
+
this.infiniteScrollState = (0, import_ui_grid_core5.setInfiniteScrollDirectionsState)(
|
|
2540
|
+
this.infiniteScrollState,
|
|
2541
|
+
options.infiniteScrollUp ?? this.infiniteScrollState.scrollUp,
|
|
2542
|
+
options.infiniteScrollDown ?? this.infiniteScrollState.scrollDown
|
|
2543
|
+
);
|
|
2544
|
+
this.lastOptionsInfiniteScrollUp = options.infiniteScrollUp;
|
|
2545
|
+
this.lastOptionsInfiniteScrollDown = options.infiniteScrollDown;
|
|
2546
|
+
}
|
|
2428
2547
|
}
|
|
2429
2548
|
this.apiRegistered = false;
|
|
2430
2549
|
this.refresh();
|
|
@@ -2466,7 +2585,7 @@ var VanillaGridController = class {
|
|
|
2466
2585
|
labels: this.labels,
|
|
2467
2586
|
rowSize: this.getRowSize(),
|
|
2468
2587
|
visibleColumns: this.visibleColumns,
|
|
2469
|
-
gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns),
|
|
2588
|
+
gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns, this.viewportWidth),
|
|
2470
2589
|
pipeline: this.pipeline,
|
|
2471
2590
|
activeFilters: this.activeFilters,
|
|
2472
2591
|
sortState: this.sortState,
|
|
@@ -3117,6 +3236,12 @@ var VanillaGridController = class {
|
|
|
3117
3236
|
canResizeColumns() {
|
|
3118
3237
|
return this.options.enableColumnResizing !== false;
|
|
3119
3238
|
}
|
|
3239
|
+
setViewportWidth(width) {
|
|
3240
|
+
this.viewportWidth = Math.round(width);
|
|
3241
|
+
}
|
|
3242
|
+
getViewportWidth() {
|
|
3243
|
+
return this.viewportWidth;
|
|
3244
|
+
}
|
|
3120
3245
|
setColumnWidthOverride(columnName, widthPx) {
|
|
3121
3246
|
const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
|
|
3122
3247
|
this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
|
|
@@ -3129,7 +3254,8 @@ var VanillaGridController = class {
|
|
|
3129
3254
|
return buildGridTemplateColumns(
|
|
3130
3255
|
this.visibleColumns.map(
|
|
3131
3256
|
(c) => c.name === columnName ? { ...c, width: widthStr } : c
|
|
3132
|
-
)
|
|
3257
|
+
),
|
|
3258
|
+
this.viewportWidth
|
|
3133
3259
|
);
|
|
3134
3260
|
}
|
|
3135
3261
|
isGroupingEnabled() {
|
|
@@ -3366,11 +3492,15 @@ var VanillaGridController = class {
|
|
|
3366
3492
|
name: "selectionRowHeaderCol",
|
|
3367
3493
|
displayName: "",
|
|
3368
3494
|
width: `${resolvedSelection.selectionRowHeaderWidth}px`,
|
|
3495
|
+
minWidth: resolvedSelection.selectionRowHeaderWidth,
|
|
3496
|
+
maxWidth: resolvedSelection.selectionRowHeaderWidth,
|
|
3369
3497
|
enableSorting: false,
|
|
3370
3498
|
enableFiltering: false,
|
|
3371
3499
|
enableGrouping: false,
|
|
3372
3500
|
enableCellEdit: false,
|
|
3373
3501
|
enablePinning: false,
|
|
3502
|
+
enableColumnResizing: false,
|
|
3503
|
+
enableColumnMoving: false,
|
|
3374
3504
|
align: "center"
|
|
3375
3505
|
};
|
|
3376
3506
|
if (!this.visibleColumns.some((c) => c.name === "selectionRowHeaderCol")) {
|
|
@@ -5047,10 +5177,16 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
5047
5177
|
/** @internal */
|
|
5048
5178
|
ensureController(options) {
|
|
5049
5179
|
if (this.controller) {
|
|
5180
|
+
if (this.clientWidth > 0) {
|
|
5181
|
+
this.controller.setViewportWidth(this.clientWidth);
|
|
5182
|
+
}
|
|
5050
5183
|
this.controller.setOptions(options);
|
|
5051
5184
|
return;
|
|
5052
5185
|
}
|
|
5053
5186
|
this.controller = createVanillaGridController(options);
|
|
5187
|
+
if (this.clientWidth > 0) {
|
|
5188
|
+
this.controller.setViewportWidth(this.clientWidth);
|
|
5189
|
+
}
|
|
5054
5190
|
this.controller.setCellNavScrollHandler((rowId, columnName) => {
|
|
5055
5191
|
if (!rowId || !columnName) return;
|
|
5056
5192
|
this.scrollFocusedRowIntoView(rowId);
|
|
@@ -5184,6 +5320,7 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
5184
5320
|
virtualBody.innerHTML = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
|
|
5185
5321
|
this.lastItemsFingerprint = this.fingerprintItems(itemsToRender);
|
|
5186
5322
|
this.lastVirtualOffset = virtualOffset;
|
|
5323
|
+
this.frameworkSlots.flush();
|
|
5187
5324
|
}
|
|
5188
5325
|
/** @internal */
|
|
5189
5326
|
render() {
|
|
@@ -5283,10 +5420,12 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
5283
5420
|
if (this.autoResizeObserver) return;
|
|
5284
5421
|
if (typeof ResizeObserver === "undefined") return;
|
|
5285
5422
|
this.autoResizeObserver = new ResizeObserver((entries) => {
|
|
5286
|
-
const options = this.snapshot?.options;
|
|
5287
|
-
if (options?.enableAutoResize === false) return;
|
|
5288
5423
|
const entry = entries[0];
|
|
5289
5424
|
if (!entry) return;
|
|
5425
|
+
const newWidth = entry.contentRect.width;
|
|
5426
|
+
if (this.controller && newWidth > 0) {
|
|
5427
|
+
this.controller.setViewportWidth(newWidth);
|
|
5428
|
+
}
|
|
5290
5429
|
if (this.autoResizeDebounceHandle !== null) {
|
|
5291
5430
|
cancelAnimationFrame(this.autoResizeDebounceHandle);
|
|
5292
5431
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -630,7 +630,7 @@ function renderHeaderCell(el, column, sortEnabled, groupingEnabled, pinningEnabl
|
|
|
630
630
|
const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
|
|
631
631
|
const sortIconKey = sortDirection === SORT_DIRECTIONS.asc ? "sortAsc" : sortDirection === SORT_DIRECTIONS.desc ? "sortDesc" : "sortNone";
|
|
632
632
|
const pinLabel = isPinned ? el.snapshot?.labels.unpin ?? "Unpin" : el.snapshot?.labels.pinColumn ?? "Pin";
|
|
633
|
-
const canResize = controller.canResizeColumns();
|
|
633
|
+
const canResize = controller.canResizeColumns() && column.enableColumnResizing !== false;
|
|
634
634
|
const headerValue = escapeHtml(formatGridHeaderDisplayValue(buildGridHeaderContext2(column)));
|
|
635
635
|
const resizerHtml = canResize ? resizerMarkup(escapeHtml(column.name), escapeHtml(headerValue)) : "";
|
|
636
636
|
const isPinnedLeftLast = controller.isPinnedLeftLast(column);
|
|
@@ -638,7 +638,7 @@ function renderHeaderCell(el, column, sortEnabled, groupingEnabled, pinningEnabl
|
|
|
638
638
|
const isPinMenuOpen = el.openPinMenuColumn === column.name;
|
|
639
639
|
const isDragTarget = el.dropTargetColumnName === column.name;
|
|
640
640
|
const isDragging = el.draggedColumnName === column.name;
|
|
641
|
-
const isDraggable = canGridMoveColumns(options);
|
|
641
|
+
const isDraggable = canGridMoveColumns(options) && column.enableColumnMoving !== false;
|
|
642
642
|
const className = headerCellClass(
|
|
643
643
|
sortDirection !== SORT_DIRECTIONS.none,
|
|
644
644
|
isPinned,
|
|
@@ -1487,18 +1487,35 @@ function measureAutoColumnWidth(el, columnName) {
|
|
|
1487
1487
|
const root = el.shadowRoot;
|
|
1488
1488
|
if (root == null) return 176;
|
|
1489
1489
|
const escaped = cssEscape(columnName);
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
`.body-cell[data-column="${escaped}"] .cell-shell`
|
|
1494
|
-
];
|
|
1490
|
+
const probe = document.createElement("div");
|
|
1491
|
+
probe.style.cssText = "position:absolute;top:-9999px;left:-9999px;width:max-content;visibility:hidden;";
|
|
1492
|
+
root.appendChild(probe);
|
|
1495
1493
|
let maxWidth = 0;
|
|
1496
|
-
for (const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1494
|
+
for (const header of root.querySelectorAll(`.header-cell[data-column="${escaped}"]`)) {
|
|
1495
|
+
const clone = header.cloneNode(true);
|
|
1496
|
+
clone.style.cssText = "display:grid;grid-template-columns:auto auto;gap:inherit;padding:inherit;width:max-content;";
|
|
1497
|
+
probe.appendChild(clone);
|
|
1498
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
1499
|
+
probe.removeChild(clone);
|
|
1500
|
+
}
|
|
1501
|
+
for (const filter of root.querySelectorAll(`ui-grid-filter-cell[data-column="${escaped}"], .filter-cell[data-column="${escaped}"]`)) {
|
|
1502
|
+
const clone = filter.cloneNode(true);
|
|
1503
|
+
clone.style.cssText = "display:block;width:max-content;";
|
|
1504
|
+
probe.appendChild(clone);
|
|
1505
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
1506
|
+
probe.removeChild(clone);
|
|
1507
|
+
}
|
|
1508
|
+
for (const cell of root.querySelectorAll(`.body-cell[data-column="${escaped}"]`)) {
|
|
1509
|
+
const shell = cell.querySelector(".cell-shell");
|
|
1510
|
+
if (!shell) continue;
|
|
1511
|
+
const clone = shell.cloneNode(true);
|
|
1512
|
+
clone.style.cssText = "display:flex;width:max-content;";
|
|
1513
|
+
probe.appendChild(clone);
|
|
1514
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
1515
|
+
probe.removeChild(clone);
|
|
1516
|
+
}
|
|
1517
|
+
root.removeChild(probe);
|
|
1518
|
+
return Math.max(88, maxWidth + 12);
|
|
1502
1519
|
}
|
|
1503
1520
|
|
|
1504
1521
|
// src/events.ts
|
|
@@ -1970,6 +1987,11 @@ function bindEvents(el) {
|
|
|
1970
1987
|
event.preventDefault();
|
|
1971
1988
|
return;
|
|
1972
1989
|
}
|
|
1990
|
+
const colDef = el.snapshot.visibleColumns.find((c) => c.name === columnName);
|
|
1991
|
+
if (colDef?.enableColumnMoving === false) {
|
|
1992
|
+
event.preventDefault();
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1973
1995
|
el.draggedColumnName = columnName;
|
|
1974
1996
|
el.dropTargetColumnName = null;
|
|
1975
1997
|
dragEvent.dataTransfer.effectAllowed = "move";
|
|
@@ -1983,6 +2005,8 @@ function bindEvents(el) {
|
|
|
1983
2005
|
if (!headerCell || !el.draggedColumnName || !dragEvent.dataTransfer) return;
|
|
1984
2006
|
const columnName = headerCell.dataset["column"];
|
|
1985
2007
|
if (!columnName || columnName === el.draggedColumnName) return;
|
|
2008
|
+
const colDef = el.snapshot?.visibleColumns.find((c) => c.name === columnName);
|
|
2009
|
+
if (colDef?.enableColumnMoving === false) return;
|
|
1986
2010
|
event.preventDefault();
|
|
1987
2011
|
dragEvent.dataTransfer.dropEffect = "move";
|
|
1988
2012
|
if (el.dropTargetColumnName !== columnName) {
|
|
@@ -2229,8 +2253,72 @@ import {
|
|
|
2229
2253
|
function orderVisibleColumns(columns, order) {
|
|
2230
2254
|
return [...columns].filter((column) => column.visible !== false).sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
|
|
2231
2255
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2256
|
+
var DEFAULT_COL_MIN_WIDTH = 176;
|
|
2257
|
+
function buildGridTemplateColumns(columns, viewportWidth) {
|
|
2258
|
+
if (!viewportWidth || viewportWidth <= 0) {
|
|
2259
|
+
return columns.map((column) => gridColumnWidth(column)).join(" ");
|
|
2260
|
+
}
|
|
2261
|
+
const availableWidth = viewportWidth;
|
|
2262
|
+
const resolved = new Array(columns.length);
|
|
2263
|
+
const flexIndices = [];
|
|
2264
|
+
let usedWidth = 0;
|
|
2265
|
+
for (let i = 0; i < columns.length; i++) {
|
|
2266
|
+
const raw = columns[i].width;
|
|
2267
|
+
const minWidth = columns[i].minWidth;
|
|
2268
|
+
const maxWidth = columns[i].maxWidth ?? Infinity;
|
|
2269
|
+
if (raw && raw.endsWith("px")) {
|
|
2270
|
+
let px = parseInt(raw, 10);
|
|
2271
|
+
if (minWidth != null) px = Math.max(minWidth, px);
|
|
2272
|
+
px = Math.min(maxWidth, px);
|
|
2273
|
+
resolved[i] = px;
|
|
2274
|
+
usedWidth += px;
|
|
2275
|
+
} else if (raw && raw.endsWith("%")) {
|
|
2276
|
+
const pct = parseInt(raw.replace(/%/g, ""), 10);
|
|
2277
|
+
let w = Math.round(pct / 100 * availableWidth);
|
|
2278
|
+
if (minWidth != null) w = Math.max(minWidth, w);
|
|
2279
|
+
w = Math.min(maxWidth, w);
|
|
2280
|
+
resolved[i] = w;
|
|
2281
|
+
usedWidth += w;
|
|
2282
|
+
} else {
|
|
2283
|
+
flexIndices.push(i);
|
|
2284
|
+
resolved[i] = 0;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
if (flexIndices.length > 0) {
|
|
2288
|
+
const remaining = Math.max(0, availableWidth - usedWidth);
|
|
2289
|
+
const perFlex = Math.floor(remaining / flexIndices.length);
|
|
2290
|
+
for (const idx of flexIndices) {
|
|
2291
|
+
const minWidth = columns[idx].minWidth ?? DEFAULT_COL_MIN_WIDTH;
|
|
2292
|
+
const maxWidth = columns[idx].maxWidth ?? Infinity;
|
|
2293
|
+
const w = Math.max(minWidth, Math.min(maxWidth, perFlex));
|
|
2294
|
+
resolved[idx] = w;
|
|
2295
|
+
usedWidth += w;
|
|
2296
|
+
}
|
|
2297
|
+
let leftover = availableWidth - usedWidth;
|
|
2298
|
+
let changed = true;
|
|
2299
|
+
while (leftover > 0 && changed) {
|
|
2300
|
+
changed = false;
|
|
2301
|
+
for (const idx of flexIndices) {
|
|
2302
|
+
if (leftover <= 0) break;
|
|
2303
|
+
const maxWidth = columns[idx].maxWidth ?? Infinity;
|
|
2304
|
+
if (resolved[idx] < maxWidth) {
|
|
2305
|
+
resolved[idx]++;
|
|
2306
|
+
leftover--;
|
|
2307
|
+
changed = true;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
return resolved.map((w) => `${w}px`).join(" ");
|
|
2313
|
+
}
|
|
2314
|
+
function pinnedStatesEqual(a, b) {
|
|
2315
|
+
const keysA = Object.keys(a);
|
|
2316
|
+
const keysB = Object.keys(b);
|
|
2317
|
+
if (keysA.length !== keysB.length) return false;
|
|
2318
|
+
for (const key of keysA) {
|
|
2319
|
+
if (a[key] !== b[key]) return false;
|
|
2320
|
+
}
|
|
2321
|
+
return true;
|
|
2234
2322
|
}
|
|
2235
2323
|
var VanillaGridController = class {
|
|
2236
2324
|
options;
|
|
@@ -2240,6 +2328,10 @@ var VanillaGridController = class {
|
|
|
2240
2328
|
direction: SORT_DIRECTIONS2.none
|
|
2241
2329
|
};
|
|
2242
2330
|
groupByColumns = [];
|
|
2331
|
+
lastOptionsGroupBy = void 0;
|
|
2332
|
+
lastOptionsPinnedState = void 0;
|
|
2333
|
+
lastOptionsInfiniteScrollUp = void 0;
|
|
2334
|
+
lastOptionsInfiniteScrollDown = void 0;
|
|
2243
2335
|
collapsedGroups = {};
|
|
2244
2336
|
hiddenRowReasons = {};
|
|
2245
2337
|
expandedRows = {};
|
|
@@ -2247,6 +2339,7 @@ var VanillaGridController = class {
|
|
|
2247
2339
|
pinnedColumns = {};
|
|
2248
2340
|
columnOrder = [];
|
|
2249
2341
|
columnWidthOverrides = {};
|
|
2342
|
+
viewportWidth = 0;
|
|
2250
2343
|
currentPage = 1;
|
|
2251
2344
|
pageSize = 0;
|
|
2252
2345
|
editingCell = null;
|
|
@@ -2283,7 +2376,12 @@ var VanillaGridController = class {
|
|
|
2283
2376
|
this.validatorRegistry = createGridValidatorRegistry(this.labels);
|
|
2284
2377
|
this.columnOrder = options.columnDefs.map((column) => column.name);
|
|
2285
2378
|
this.groupByColumns = options.grouping?.groupBy ? [...options.grouping.groupBy] : [];
|
|
2379
|
+
this.lastOptionsGroupBy = options.grouping?.groupBy ? [...options.grouping.groupBy] : void 0;
|
|
2286
2380
|
this.pinnedColumns = buildInitialPinnedState(options.columnDefs);
|
|
2381
|
+
const declaresPin = options.columnDefs.some((col) => col.pinnedLeft === true || col.pinnedRight === true);
|
|
2382
|
+
this.lastOptionsPinnedState = declaresPin ? { ...this.pinnedColumns } : void 0;
|
|
2383
|
+
this.lastOptionsInfiniteScrollUp = options.infiniteScrollUp;
|
|
2384
|
+
this.lastOptionsInfiniteScrollDown = options.infiniteScrollDown;
|
|
2287
2385
|
this.infiniteScrollState = resetInfiniteScrollState(
|
|
2288
2386
|
options.infiniteScrollUp === true,
|
|
2289
2387
|
options.infiniteScrollDown !== false
|
|
@@ -2500,20 +2598,41 @@ var VanillaGridController = class {
|
|
|
2500
2598
|
}
|
|
2501
2599
|
}
|
|
2502
2600
|
if (options.grouping?.groupBy !== void 0) {
|
|
2503
|
-
|
|
2601
|
+
const incoming = options.grouping.groupBy;
|
|
2602
|
+
const prev = this.lastOptionsGroupBy;
|
|
2603
|
+
const changed = !prev || prev.length !== incoming.length || prev.some((col, i) => col !== incoming[i]);
|
|
2604
|
+
if (changed) {
|
|
2605
|
+
this.groupByColumns = [...incoming];
|
|
2606
|
+
this.lastOptionsGroupBy = [...incoming];
|
|
2607
|
+
}
|
|
2608
|
+
} else if (this.lastOptionsGroupBy !== void 0) {
|
|
2609
|
+
this.lastOptionsGroupBy = void 0;
|
|
2504
2610
|
}
|
|
2505
2611
|
const declaresPin = options.columnDefs.some(
|
|
2506
2612
|
(col) => col.pinnedLeft === true || col.pinnedRight === true
|
|
2507
2613
|
);
|
|
2508
2614
|
if (declaresPin) {
|
|
2509
|
-
|
|
2615
|
+
const incoming = buildInitialPinnedState(options.columnDefs);
|
|
2616
|
+
const prev = this.lastOptionsPinnedState;
|
|
2617
|
+
if (!prev || !pinnedStatesEqual(prev, incoming)) {
|
|
2618
|
+
this.pinnedColumns = incoming;
|
|
2619
|
+
this.lastOptionsPinnedState = { ...incoming };
|
|
2620
|
+
}
|
|
2621
|
+
} else if (this.lastOptionsPinnedState !== void 0) {
|
|
2622
|
+
this.lastOptionsPinnedState = void 0;
|
|
2510
2623
|
}
|
|
2511
2624
|
if (options.infiniteScrollUp !== void 0 || options.infiniteScrollDown !== void 0) {
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2625
|
+
const upChanged = options.infiniteScrollUp !== void 0 && options.infiniteScrollUp !== this.lastOptionsInfiniteScrollUp;
|
|
2626
|
+
const downChanged = options.infiniteScrollDown !== void 0 && options.infiniteScrollDown !== this.lastOptionsInfiniteScrollDown;
|
|
2627
|
+
if (upChanged || downChanged) {
|
|
2628
|
+
this.infiniteScrollState = setInfiniteScrollDirectionsState(
|
|
2629
|
+
this.infiniteScrollState,
|
|
2630
|
+
options.infiniteScrollUp ?? this.infiniteScrollState.scrollUp,
|
|
2631
|
+
options.infiniteScrollDown ?? this.infiniteScrollState.scrollDown
|
|
2632
|
+
);
|
|
2633
|
+
this.lastOptionsInfiniteScrollUp = options.infiniteScrollUp;
|
|
2634
|
+
this.lastOptionsInfiniteScrollDown = options.infiniteScrollDown;
|
|
2635
|
+
}
|
|
2517
2636
|
}
|
|
2518
2637
|
this.apiRegistered = false;
|
|
2519
2638
|
this.refresh();
|
|
@@ -2555,7 +2674,7 @@ var VanillaGridController = class {
|
|
|
2555
2674
|
labels: this.labels,
|
|
2556
2675
|
rowSize: this.getRowSize(),
|
|
2557
2676
|
visibleColumns: this.visibleColumns,
|
|
2558
|
-
gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns),
|
|
2677
|
+
gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns, this.viewportWidth),
|
|
2559
2678
|
pipeline: this.pipeline,
|
|
2560
2679
|
activeFilters: this.activeFilters,
|
|
2561
2680
|
sortState: this.sortState,
|
|
@@ -3206,6 +3325,12 @@ var VanillaGridController = class {
|
|
|
3206
3325
|
canResizeColumns() {
|
|
3207
3326
|
return this.options.enableColumnResizing !== false;
|
|
3208
3327
|
}
|
|
3328
|
+
setViewportWidth(width) {
|
|
3329
|
+
this.viewportWidth = Math.round(width);
|
|
3330
|
+
}
|
|
3331
|
+
getViewportWidth() {
|
|
3332
|
+
return this.viewportWidth;
|
|
3333
|
+
}
|
|
3209
3334
|
setColumnWidthOverride(columnName, widthPx) {
|
|
3210
3335
|
const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
|
|
3211
3336
|
this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
|
|
@@ -3218,7 +3343,8 @@ var VanillaGridController = class {
|
|
|
3218
3343
|
return buildGridTemplateColumns(
|
|
3219
3344
|
this.visibleColumns.map(
|
|
3220
3345
|
(c) => c.name === columnName ? { ...c, width: widthStr } : c
|
|
3221
|
-
)
|
|
3346
|
+
),
|
|
3347
|
+
this.viewportWidth
|
|
3222
3348
|
);
|
|
3223
3349
|
}
|
|
3224
3350
|
isGroupingEnabled() {
|
|
@@ -3455,11 +3581,15 @@ var VanillaGridController = class {
|
|
|
3455
3581
|
name: "selectionRowHeaderCol",
|
|
3456
3582
|
displayName: "",
|
|
3457
3583
|
width: `${resolvedSelection.selectionRowHeaderWidth}px`,
|
|
3584
|
+
minWidth: resolvedSelection.selectionRowHeaderWidth,
|
|
3585
|
+
maxWidth: resolvedSelection.selectionRowHeaderWidth,
|
|
3458
3586
|
enableSorting: false,
|
|
3459
3587
|
enableFiltering: false,
|
|
3460
3588
|
enableGrouping: false,
|
|
3461
3589
|
enableCellEdit: false,
|
|
3462
3590
|
enablePinning: false,
|
|
3591
|
+
enableColumnResizing: false,
|
|
3592
|
+
enableColumnMoving: false,
|
|
3463
3593
|
align: "center"
|
|
3464
3594
|
};
|
|
3465
3595
|
if (!this.visibleColumns.some((c) => c.name === "selectionRowHeaderCol")) {
|
|
@@ -5136,10 +5266,16 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
5136
5266
|
/** @internal */
|
|
5137
5267
|
ensureController(options) {
|
|
5138
5268
|
if (this.controller) {
|
|
5269
|
+
if (this.clientWidth > 0) {
|
|
5270
|
+
this.controller.setViewportWidth(this.clientWidth);
|
|
5271
|
+
}
|
|
5139
5272
|
this.controller.setOptions(options);
|
|
5140
5273
|
return;
|
|
5141
5274
|
}
|
|
5142
5275
|
this.controller = createVanillaGridController(options);
|
|
5276
|
+
if (this.clientWidth > 0) {
|
|
5277
|
+
this.controller.setViewportWidth(this.clientWidth);
|
|
5278
|
+
}
|
|
5143
5279
|
this.controller.setCellNavScrollHandler((rowId, columnName) => {
|
|
5144
5280
|
if (!rowId || !columnName) return;
|
|
5145
5281
|
this.scrollFocusedRowIntoView(rowId);
|
|
@@ -5273,6 +5409,7 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
5273
5409
|
virtualBody.innerHTML = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
|
|
5274
5410
|
this.lastItemsFingerprint = this.fingerprintItems(itemsToRender);
|
|
5275
5411
|
this.lastVirtualOffset = virtualOffset;
|
|
5412
|
+
this.frameworkSlots.flush();
|
|
5276
5413
|
}
|
|
5277
5414
|
/** @internal */
|
|
5278
5415
|
render() {
|
|
@@ -5372,10 +5509,12 @@ var UiGridStandaloneElement = class extends HTMLElement {
|
|
|
5372
5509
|
if (this.autoResizeObserver) return;
|
|
5373
5510
|
if (typeof ResizeObserver === "undefined") return;
|
|
5374
5511
|
this.autoResizeObserver = new ResizeObserver((entries) => {
|
|
5375
|
-
const options = this.snapshot?.options;
|
|
5376
|
-
if (options?.enableAutoResize === false) return;
|
|
5377
5512
|
const entry = entries[0];
|
|
5378
5513
|
if (!entry) return;
|
|
5514
|
+
const newWidth = entry.contentRect.width;
|
|
5515
|
+
if (this.controller && newWidth > 0) {
|
|
5516
|
+
this.controller.setViewportWidth(newWidth);
|
|
5517
|
+
}
|
|
5379
5518
|
if (this.autoResizeDebounceHandle !== null) {
|
|
5380
5519
|
cancelAnimationFrame(this.autoResizeDebounceHandle);
|
|
5381
5520
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ornery/ui-grid-vanilla",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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": "1.0.
|
|
19
|
+
"@ornery/ui-grid-core": "1.0.2"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"happy-dom": "^20.9.0",
|
package/src/auto-resize.test.ts
CHANGED
|
@@ -160,13 +160,14 @@ describe('enableAutoResize (ResizeObserver)', () => {
|
|
|
160
160
|
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
it('
|
|
163
|
+
it('still re-renders on resize even with enableAutoResize false (column widths need recalculation)', () => {
|
|
164
164
|
document.body.appendChild(element);
|
|
165
165
|
element.options = {
|
|
166
166
|
id: 'test-grid',
|
|
167
167
|
data: [{ name: 'Alice' }],
|
|
168
168
|
columnDefs: [{ name: 'name' }],
|
|
169
169
|
enableAutoResize: false,
|
|
170
|
+
enableMinHeightCheck: false,
|
|
170
171
|
};
|
|
171
172
|
|
|
172
173
|
const renderSpy = vi.spyOn(element as any, 'render');
|
|
@@ -177,8 +178,8 @@ describe('enableAutoResize (ResizeObserver)', () => {
|
|
|
177
178
|
|
|
178
179
|
flushRaf();
|
|
179
180
|
|
|
180
|
-
//
|
|
181
|
-
expect(renderSpy).
|
|
181
|
+
// Resize always triggers render for column width recalculation
|
|
182
|
+
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
182
183
|
});
|
|
183
184
|
|
|
184
185
|
it('cancels pending rAF on teardown', () => {
|
package/src/events.ts
CHANGED
|
@@ -664,6 +664,12 @@ export function bindEvents(el: UiGridStandaloneElement): void {
|
|
|
664
664
|
return;
|
|
665
665
|
}
|
|
666
666
|
|
|
667
|
+
const colDef = el.snapshot.visibleColumns.find((c) => c.name === columnName);
|
|
668
|
+
if (colDef?.enableColumnMoving === false) {
|
|
669
|
+
event.preventDefault();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
667
673
|
el.draggedColumnName = columnName;
|
|
668
674
|
el.dropTargetColumnName = null;
|
|
669
675
|
dragEvent.dataTransfer.effectAllowed = 'move';
|
|
@@ -681,6 +687,9 @@ export function bindEvents(el: UiGridStandaloneElement): void {
|
|
|
681
687
|
const columnName = headerCell.dataset['column'];
|
|
682
688
|
if (!columnName || columnName === el.draggedColumnName) return;
|
|
683
689
|
|
|
690
|
+
const colDef = el.snapshot?.visibleColumns.find((c) => c.name === columnName);
|
|
691
|
+
if (colDef?.enableColumnMoving === false) return;
|
|
692
|
+
|
|
684
693
|
event.preventDefault();
|
|
685
694
|
dragEvent.dataTransfer.dropEffect = 'move';
|
|
686
695
|
|
package/src/focus.ts
CHANGED
|
@@ -329,25 +329,51 @@ export function matchesKeyOverride(
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
/**
|
|
332
|
-
* Measure the
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
332
|
+
* Measure the natural content width of a column by cloning visible cells
|
|
333
|
+
* into a hidden max-content container. This avoids measuring the CSS Grid
|
|
334
|
+
* track width (which just reflects the current column size) and instead
|
|
335
|
+
* returns the width the content actually needs.
|
|
336
336
|
*/
|
|
337
337
|
export function measureAutoColumnWidth(el: UiGridStandaloneElement, columnName: string): number {
|
|
338
338
|
const root = el.shadowRoot;
|
|
339
339
|
if (root == null) return 176;
|
|
340
340
|
const escaped = cssEscape(columnName);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
341
|
+
|
|
342
|
+
const probe = document.createElement('div');
|
|
343
|
+
probe.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:max-content;visibility:hidden;';
|
|
344
|
+
root.appendChild(probe);
|
|
345
|
+
|
|
346
346
|
let maxWidth = 0;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
|
|
348
|
+
// Header cell: clone and measure at natural width.
|
|
349
|
+
for (const header of root.querySelectorAll<HTMLElement>(`.header-cell[data-column="${escaped}"]`)) {
|
|
350
|
+
const clone = header.cloneNode(true) as HTMLElement;
|
|
351
|
+
clone.style.cssText = 'display:grid;grid-template-columns:auto auto;gap:inherit;padding:inherit;width:max-content;';
|
|
352
|
+
probe.appendChild(clone);
|
|
353
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
354
|
+
probe.removeChild(clone);
|
|
351
355
|
}
|
|
352
|
-
|
|
356
|
+
|
|
357
|
+
// Filter cell: both custom element and class-based fallback.
|
|
358
|
+
for (const filter of root.querySelectorAll<HTMLElement>(`ui-grid-filter-cell[data-column="${escaped}"], .filter-cell[data-column="${escaped}"]`)) {
|
|
359
|
+
const clone = filter.cloneNode(true) as HTMLElement;
|
|
360
|
+
clone.style.cssText = 'display:block;width:max-content;';
|
|
361
|
+
probe.appendChild(clone);
|
|
362
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
363
|
+
probe.removeChild(clone);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Body cells: clone .cell-shell content and measure.
|
|
367
|
+
for (const cell of root.querySelectorAll<HTMLElement>(`.body-cell[data-column="${escaped}"]`)) {
|
|
368
|
+
const shell = cell.querySelector<HTMLElement>('.cell-shell');
|
|
369
|
+
if (!shell) continue;
|
|
370
|
+
const clone = shell.cloneNode(true) as HTMLElement;
|
|
371
|
+
clone.style.cssText = 'display:flex;width:max-content;';
|
|
372
|
+
probe.appendChild(clone);
|
|
373
|
+
maxWidth = Math.max(maxWidth, clone.scrollWidth);
|
|
374
|
+
probe.removeChild(clone);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
root.removeChild(probe);
|
|
378
|
+
return Math.max(88, maxWidth + 12);
|
|
353
379
|
}
|
package/src/grid-controller.ts
CHANGED
|
@@ -201,8 +201,94 @@ function orderVisibleColumns(
|
|
|
201
201
|
.sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
const DEFAULT_COL_MIN_WIDTH = 176;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolve column widths to fixed px values, mirroring the old ui-grid's
|
|
208
|
+
* `updateColumnWidths` / `drawnWidth` approach. Every cell (header, filter,
|
|
209
|
+
* body) gets the exact same px track size — alignment guaranteed regardless
|
|
210
|
+
* of container width or scrollbar presence.
|
|
211
|
+
*
|
|
212
|
+
* Algorithm (ported from GridRenderContainer.prototype.updateColumnWidths):
|
|
213
|
+
* 1. Fixed-px columns: use their declared width directly.
|
|
214
|
+
* 2. Percentage columns: resolve against availableWidth.
|
|
215
|
+
* 3. Flex columns (*, 1fr, minmax): divide remaining space equally.
|
|
216
|
+
* 4. Leftover/excess pixels distributed 1px at a time for pixel-perfect fit.
|
|
217
|
+
*/
|
|
218
|
+
function buildGridTemplateColumns(columns: readonly GridColumnDef[], viewportWidth?: number): string {
|
|
219
|
+
if (!viewportWidth || viewportWidth <= 0) {
|
|
220
|
+
return columns.map((column) => gridColumnWidth(column)).join(' ');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const availableWidth = viewportWidth;
|
|
224
|
+
const resolved: number[] = new Array(columns.length);
|
|
225
|
+
const flexIndices: number[] = [];
|
|
226
|
+
let usedWidth = 0;
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < columns.length; i++) {
|
|
229
|
+
const raw = columns[i].width;
|
|
230
|
+
const minWidth = columns[i].minWidth;
|
|
231
|
+
const maxWidth = columns[i].maxWidth ?? Infinity;
|
|
232
|
+
|
|
233
|
+
if (raw && raw.endsWith('px')) {
|
|
234
|
+
let px = parseInt(raw, 10);
|
|
235
|
+
if (minWidth != null) px = Math.max(minWidth, px);
|
|
236
|
+
px = Math.min(maxWidth, px);
|
|
237
|
+
resolved[i] = px;
|
|
238
|
+
usedWidth += px;
|
|
239
|
+
} else if (raw && raw.endsWith('%')) {
|
|
240
|
+
const pct = parseInt(raw.replace(/%/g, ''), 10);
|
|
241
|
+
let w = Math.round((pct / 100) * availableWidth);
|
|
242
|
+
if (minWidth != null) w = Math.max(minWidth, w);
|
|
243
|
+
w = Math.min(maxWidth, w);
|
|
244
|
+
resolved[i] = w;
|
|
245
|
+
usedWidth += w;
|
|
246
|
+
} else {
|
|
247
|
+
flexIndices.push(i);
|
|
248
|
+
resolved[i] = 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (flexIndices.length > 0) {
|
|
253
|
+
const remaining = Math.max(0, availableWidth - usedWidth);
|
|
254
|
+
const perFlex = Math.floor(remaining / flexIndices.length);
|
|
255
|
+
|
|
256
|
+
for (const idx of flexIndices) {
|
|
257
|
+
const minWidth = columns[idx].minWidth ?? DEFAULT_COL_MIN_WIDTH;
|
|
258
|
+
const maxWidth = columns[idx].maxWidth ?? Infinity;
|
|
259
|
+
const w = Math.max(minWidth, Math.min(maxWidth, perFlex));
|
|
260
|
+
resolved[idx] = w;
|
|
261
|
+
usedWidth += w;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Distribute leftover pixels one at a time (old grid's processColumnUpwards)
|
|
265
|
+
let leftover = availableWidth - usedWidth;
|
|
266
|
+
let changed = true;
|
|
267
|
+
while (leftover > 0 && changed) {
|
|
268
|
+
changed = false;
|
|
269
|
+
for (const idx of flexIndices) {
|
|
270
|
+
if (leftover <= 0) break;
|
|
271
|
+
const maxWidth = columns[idx].maxWidth ?? Infinity;
|
|
272
|
+
if (resolved[idx] < maxWidth) {
|
|
273
|
+
resolved[idx]++;
|
|
274
|
+
leftover--;
|
|
275
|
+
changed = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return resolved.map((w) => `${w}px`).join(' ');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function pinnedStatesEqual(a: PinnedColumnState, b: PinnedColumnState): boolean {
|
|
285
|
+
const keysA = Object.keys(a);
|
|
286
|
+
const keysB = Object.keys(b);
|
|
287
|
+
if (keysA.length !== keysB.length) return false;
|
|
288
|
+
for (const key of keysA) {
|
|
289
|
+
if (a[key] !== b[key]) return false;
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
206
292
|
}
|
|
207
293
|
|
|
208
294
|
export class VanillaGridController {
|
|
@@ -213,6 +299,10 @@ export class VanillaGridController {
|
|
|
213
299
|
direction: SORT_DIRECTIONS.none,
|
|
214
300
|
};
|
|
215
301
|
private groupByColumns: string[] = [];
|
|
302
|
+
private lastOptionsGroupBy: string[] | undefined = undefined;
|
|
303
|
+
private lastOptionsPinnedState: PinnedColumnState | undefined = undefined;
|
|
304
|
+
private lastOptionsInfiniteScrollUp: boolean | undefined = undefined;
|
|
305
|
+
private lastOptionsInfiniteScrollDown: boolean | undefined = undefined;
|
|
216
306
|
private collapsedGroups: Record<string, boolean> = {};
|
|
217
307
|
private hiddenRowReasons: Record<string, string[]> = {};
|
|
218
308
|
private expandedRows: Record<string, boolean> = {};
|
|
@@ -220,6 +310,7 @@ export class VanillaGridController {
|
|
|
220
310
|
private pinnedColumns: PinnedColumnState = {};
|
|
221
311
|
private columnOrder: string[] = [];
|
|
222
312
|
private columnWidthOverrides: Record<string, string> = {};
|
|
313
|
+
private viewportWidth = 0;
|
|
223
314
|
private currentPage = 1;
|
|
224
315
|
private pageSize = 0;
|
|
225
316
|
private editingCell: GridCellPosition | null = null;
|
|
@@ -262,7 +353,12 @@ export class VanillaGridController {
|
|
|
262
353
|
this.validatorRegistry = createGridValidatorRegistry(this.labels);
|
|
263
354
|
this.columnOrder = options.columnDefs.map((column) => column.name);
|
|
264
355
|
this.groupByColumns = options.grouping?.groupBy ? [...options.grouping.groupBy] : [];
|
|
356
|
+
this.lastOptionsGroupBy = options.grouping?.groupBy ? [...options.grouping.groupBy] : undefined;
|
|
265
357
|
this.pinnedColumns = buildInitialPinnedState(options.columnDefs);
|
|
358
|
+
const declaresPin = options.columnDefs.some((col) => col.pinnedLeft === true || col.pinnedRight === true);
|
|
359
|
+
this.lastOptionsPinnedState = declaresPin ? { ...this.pinnedColumns } : undefined;
|
|
360
|
+
this.lastOptionsInfiniteScrollUp = options.infiniteScrollUp;
|
|
361
|
+
this.lastOptionsInfiniteScrollDown = options.infiniteScrollDown;
|
|
266
362
|
|
|
267
363
|
this.infiniteScrollState = resetInfiniteScrollState(
|
|
268
364
|
options.infiniteScrollUp === true,
|
|
@@ -519,35 +615,53 @@ export class VanillaGridController {
|
|
|
519
615
|
}
|
|
520
616
|
}
|
|
521
617
|
|
|
522
|
-
// Apply declarative grouping
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
// When the option is absent, leave the current groupByColumns untouched
|
|
526
|
-
// so interactive toggles persist across non-grouping option updates.
|
|
618
|
+
// Apply declarative grouping only when the consumer's value structurally
|
|
619
|
+
// changed from what was last provided via options. This lets interactive
|
|
620
|
+
// toggles persist across re-renders that pass the same grouping config.
|
|
527
621
|
if (options.grouping?.groupBy !== undefined) {
|
|
528
|
-
|
|
622
|
+
const incoming = options.grouping.groupBy;
|
|
623
|
+
const prev = this.lastOptionsGroupBy;
|
|
624
|
+
const changed = !prev || prev.length !== incoming.length || prev.some((col, i) => col !== incoming[i]);
|
|
625
|
+
if (changed) {
|
|
626
|
+
this.groupByColumns = [...incoming];
|
|
627
|
+
this.lastOptionsGroupBy = [...incoming];
|
|
628
|
+
}
|
|
629
|
+
} else if (this.lastOptionsGroupBy !== undefined) {
|
|
630
|
+
this.lastOptionsGroupBy = undefined;
|
|
529
631
|
}
|
|
530
632
|
|
|
531
|
-
// Re-seed pinned state from columnDefs
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
633
|
+
// Re-seed pinned state from columnDefs only when the declarative pin
|
|
634
|
+
// configuration structurally changed from the last options pass. This
|
|
635
|
+
// prevents interactive un-pin actions from being clobbered by re-renders
|
|
636
|
+
// that pass the same columnDefs.
|
|
535
637
|
const declaresPin = options.columnDefs.some(
|
|
536
638
|
(col) => col.pinnedLeft === true || col.pinnedRight === true,
|
|
537
639
|
);
|
|
538
640
|
if (declaresPin) {
|
|
539
|
-
|
|
641
|
+
const incoming = buildInitialPinnedState(options.columnDefs);
|
|
642
|
+
const prev = this.lastOptionsPinnedState;
|
|
643
|
+
if (!prev || !pinnedStatesEqual(prev, incoming)) {
|
|
644
|
+
this.pinnedColumns = incoming;
|
|
645
|
+
this.lastOptionsPinnedState = { ...incoming };
|
|
646
|
+
}
|
|
647
|
+
} else if (this.lastOptionsPinnedState !== undefined) {
|
|
648
|
+
this.lastOptionsPinnedState = undefined;
|
|
540
649
|
}
|
|
541
650
|
|
|
542
|
-
// Re-seed infinite-scroll directions when the
|
|
543
|
-
//
|
|
544
|
-
// without needing to go through setScrollDirections.
|
|
651
|
+
// Re-seed infinite-scroll directions only when the values structurally
|
|
652
|
+
// changed from the last options pass.
|
|
545
653
|
if (options.infiniteScrollUp !== undefined || options.infiniteScrollDown !== undefined) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
654
|
+
const upChanged = options.infiniteScrollUp !== undefined && options.infiniteScrollUp !== this.lastOptionsInfiniteScrollUp;
|
|
655
|
+
const downChanged = options.infiniteScrollDown !== undefined && options.infiniteScrollDown !== this.lastOptionsInfiniteScrollDown;
|
|
656
|
+
if (upChanged || downChanged) {
|
|
657
|
+
this.infiniteScrollState = setInfiniteScrollDirectionsState(
|
|
658
|
+
this.infiniteScrollState,
|
|
659
|
+
options.infiniteScrollUp ?? this.infiniteScrollState.scrollUp,
|
|
660
|
+
options.infiniteScrollDown ?? this.infiniteScrollState.scrollDown,
|
|
661
|
+
);
|
|
662
|
+
this.lastOptionsInfiniteScrollUp = options.infiniteScrollUp;
|
|
663
|
+
this.lastOptionsInfiniteScrollDown = options.infiniteScrollDown;
|
|
664
|
+
}
|
|
551
665
|
}
|
|
552
666
|
|
|
553
667
|
this.apiRegistered = false;
|
|
@@ -593,7 +707,7 @@ export class VanillaGridController {
|
|
|
593
707
|
labels: this.labels,
|
|
594
708
|
rowSize: this.getRowSize(),
|
|
595
709
|
visibleColumns: this.visibleColumns,
|
|
596
|
-
gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns),
|
|
710
|
+
gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns, this.viewportWidth),
|
|
597
711
|
pipeline: this.pipeline,
|
|
598
712
|
activeFilters: this.activeFilters,
|
|
599
713
|
sortState: this.sortState,
|
|
@@ -1377,6 +1491,14 @@ export class VanillaGridController {
|
|
|
1377
1491
|
return this.options.enableColumnResizing !== false;
|
|
1378
1492
|
}
|
|
1379
1493
|
|
|
1494
|
+
setViewportWidth(width: number): void {
|
|
1495
|
+
this.viewportWidth = Math.round(width);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
getViewportWidth(): number {
|
|
1499
|
+
return this.viewportWidth;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1380
1502
|
setColumnWidthOverride(columnName: string, widthPx: number): void {
|
|
1381
1503
|
const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
|
|
1382
1504
|
this.columnWidthOverrides = { ...this.columnWidthOverrides, [columnName]: nextWidth };
|
|
@@ -1391,6 +1513,7 @@ export class VanillaGridController {
|
|
|
1391
1513
|
this.visibleColumns.map((c) =>
|
|
1392
1514
|
c.name === columnName ? { ...c, width: widthStr } : c,
|
|
1393
1515
|
),
|
|
1516
|
+
this.viewportWidth,
|
|
1394
1517
|
);
|
|
1395
1518
|
}
|
|
1396
1519
|
|
|
@@ -1691,11 +1814,15 @@ export class VanillaGridController {
|
|
|
1691
1814
|
name: 'selectionRowHeaderCol',
|
|
1692
1815
|
displayName: '',
|
|
1693
1816
|
width: `${resolvedSelection.selectionRowHeaderWidth}px`,
|
|
1817
|
+
minWidth: resolvedSelection.selectionRowHeaderWidth,
|
|
1818
|
+
maxWidth: resolvedSelection.selectionRowHeaderWidth,
|
|
1694
1819
|
enableSorting: false,
|
|
1695
1820
|
enableFiltering: false,
|
|
1696
1821
|
enableGrouping: false,
|
|
1697
1822
|
enableCellEdit: false,
|
|
1698
1823
|
enablePinning: false,
|
|
1824
|
+
enableColumnResizing: false,
|
|
1825
|
+
enableColumnMoving: false,
|
|
1699
1826
|
align: 'center',
|
|
1700
1827
|
};
|
|
1701
1828
|
// Dedupe if already present (setOptions could be re-entering).
|
package/src/render.ts
CHANGED
|
@@ -105,7 +105,7 @@ export function renderHeaderCell(
|
|
|
105
105
|
const pinLabel = isPinned
|
|
106
106
|
? (el.snapshot?.labels.unpin ?? 'Unpin')
|
|
107
107
|
: (el.snapshot?.labels.pinColumn ?? 'Pin');
|
|
108
|
-
const canResize = controller.canResizeColumns();
|
|
108
|
+
const canResize = controller.canResizeColumns() && column.enableColumnResizing !== false;
|
|
109
109
|
const headerValue = escapeHtml(formatGridHeaderDisplayValue(buildGridHeaderContext(column)));
|
|
110
110
|
const resizerHtml = canResize
|
|
111
111
|
? resizerMarkup(escapeHtml(column.name), escapeHtml(headerValue))
|
|
@@ -116,7 +116,7 @@ export function renderHeaderCell(
|
|
|
116
116
|
const isPinMenuOpen = el.openPinMenuColumn === column.name;
|
|
117
117
|
const isDragTarget = el.dropTargetColumnName === column.name;
|
|
118
118
|
const isDragging = el.draggedColumnName === column.name;
|
|
119
|
-
const isDraggable = canGridMoveColumns(options);
|
|
119
|
+
const isDraggable = canGridMoveColumns(options) && column.enableColumnMoving !== false;
|
|
120
120
|
const className = headerCellClass(
|
|
121
121
|
sortDirection !== SORT_DIRECTIONS.none,
|
|
122
122
|
isPinned,
|
|
@@ -709,11 +709,17 @@ export class UiGridStandaloneElement extends HTMLElement {
|
|
|
709
709
|
/** @internal */
|
|
710
710
|
ensureController(options: VanillaGridOptions): void {
|
|
711
711
|
if (this.controller) {
|
|
712
|
+
if (this.clientWidth > 0) {
|
|
713
|
+
this.controller.setViewportWidth(this.clientWidth);
|
|
714
|
+
}
|
|
712
715
|
this.controller.setOptions(options);
|
|
713
716
|
return;
|
|
714
717
|
}
|
|
715
718
|
|
|
716
719
|
this.controller = createVanillaGridController(options);
|
|
720
|
+
if (this.clientWidth > 0) {
|
|
721
|
+
this.controller.setViewportWidth(this.clientWidth);
|
|
722
|
+
}
|
|
717
723
|
// Let the controller's cellNav.scrollToFocus delegate into our
|
|
718
724
|
// element's scroll-and-focus helpers.
|
|
719
725
|
this.controller.setCellNavScrollHandler((rowId, columnName) => {
|
|
@@ -883,6 +889,8 @@ export class UiGridStandaloneElement extends HTMLElement {
|
|
|
883
889
|
// next full render() can take the fast per-cell patch path.
|
|
884
890
|
this.lastItemsFingerprint = this.fingerprintItems(itemsToRender);
|
|
885
891
|
this.lastVirtualOffset = virtualOffset;
|
|
892
|
+
|
|
893
|
+
this.frameworkSlots.flush();
|
|
886
894
|
}
|
|
887
895
|
|
|
888
896
|
/** @internal */
|
|
@@ -1011,12 +1019,14 @@ export class UiGridStandaloneElement extends HTMLElement {
|
|
|
1011
1019
|
if (typeof ResizeObserver === 'undefined') return;
|
|
1012
1020
|
|
|
1013
1021
|
this.autoResizeObserver = new ResizeObserver((entries) => {
|
|
1014
|
-
const options = this.snapshot?.options;
|
|
1015
|
-
if (options?.enableAutoResize === false) return;
|
|
1016
|
-
|
|
1017
1022
|
const entry = entries[0];
|
|
1018
1023
|
if (!entry) return;
|
|
1019
1024
|
|
|
1025
|
+
const newWidth = entry.contentRect.width;
|
|
1026
|
+
if (this.controller && newWidth > 0) {
|
|
1027
|
+
this.controller.setViewportWidth(newWidth);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1020
1030
|
if (this.autoResizeDebounceHandle !== null) {
|
|
1021
1031
|
cancelAnimationFrame(this.autoResizeDebounceHandle);
|
|
1022
1032
|
}
|