@rickcedwhat/playwright-smart-table 6.20.1 → 6.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/rowFinder.d.ts +1 -0
- package/dist/engine/rowFinder.js +75 -75
- package/dist/engine/scanPages.d.ts +58 -0
- package/dist/engine/scanPages.js +46 -0
- package/dist/engine/tableIteration.d.ts +1 -0
- package/dist/engine/tableIteration.js +38 -20
- package/dist/filterEngine.d.ts +4 -0
- package/dist/filterEngine.js +15 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/plugins/index.d.ts +51 -10
- package/dist/plugins/index.js +14 -6
- package/dist/presets/glide/index.d.ts +1 -1
- package/dist/presets/glide/index.js +2 -2
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +4 -4
- package/dist/smartRow.d.ts +2 -2
- package/dist/smartRow.js +160 -49
- package/dist/strategies/columns.d.ts +5 -9
- package/dist/strategies/columns.js +0 -10
- package/dist/strategies/contentReady.d.ts +20 -0
- package/dist/strategies/contentReady.js +62 -0
- package/dist/strategies/filter.d.ts +0 -4
- package/dist/strategies/filter.js +0 -16
- package/dist/strategies/headers.d.ts +5 -0
- package/dist/strategies/headers.js +17 -9
- package/dist/strategies/index.d.ts +23 -15
- package/dist/strategies/index.js +9 -5
- package/dist/strategies/pagination.js +11 -2
- package/dist/strategies/viewport.d.ts +4 -1
- package/dist/strategies/viewport.js +62 -32
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +45 -1
- package/dist/types.d.ts +43 -1
- package/dist/useTable.js +105 -43
- package/dist/utils/elementTracker.js +6 -3
- package/dist/utils/resolveCellLocator.d.ts +35 -0
- package/dist/utils/resolveCellLocator.js +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContentReadyStrategies = void 0;
|
|
4
|
+
exports.ContentReadyStrategies = {
|
|
5
|
+
/**
|
|
6
|
+
* Polls the row's text content until two consecutive reads match.
|
|
7
|
+
*/
|
|
8
|
+
textStable: (options = {}) => {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 500;
|
|
11
|
+
const interval = (_b = options.interval) !== null && _b !== void 0 ? _b : 50;
|
|
12
|
+
return async (row, page) => {
|
|
13
|
+
const deadline = Date.now() + timeout;
|
|
14
|
+
const remaining = () => Math.max(0, deadline - Date.now());
|
|
15
|
+
let prev = await row.innerText({ timeout: remaining() || timeout });
|
|
16
|
+
while (remaining() > 0) {
|
|
17
|
+
await page.waitForTimeout(Math.min(interval, remaining()));
|
|
18
|
+
if (remaining() <= 0)
|
|
19
|
+
break;
|
|
20
|
+
const cur = await row.innerText({ timeout: remaining() });
|
|
21
|
+
if (cur === prev)
|
|
22
|
+
return;
|
|
23
|
+
prev = cur;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
/**
|
|
28
|
+
* Uses MutationObserver to wait for DOM mutations on the row's subtree
|
|
29
|
+
* to settle. Resolves when no mutations occur for `quietPeriod` ms, or
|
|
30
|
+
* when `timeout` expires.
|
|
31
|
+
*/
|
|
32
|
+
mutationSettled: (options = {}) => {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 500;
|
|
35
|
+
const quietPeriod = (_b = options.quietPeriod) !== null && _b !== void 0 ? _b : 100;
|
|
36
|
+
return async (row) => {
|
|
37
|
+
await row.evaluate((el, opts) => {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
let quietTimer;
|
|
40
|
+
let deadlineTimer;
|
|
41
|
+
const done = () => {
|
|
42
|
+
clearTimeout(quietTimer);
|
|
43
|
+
clearTimeout(deadlineTimer);
|
|
44
|
+
observer.disconnect();
|
|
45
|
+
resolve();
|
|
46
|
+
};
|
|
47
|
+
const observer = new MutationObserver(() => {
|
|
48
|
+
clearTimeout(quietTimer);
|
|
49
|
+
quietTimer = setTimeout(done, opts.quietPeriod);
|
|
50
|
+
});
|
|
51
|
+
observer.observe(el, {
|
|
52
|
+
childList: true,
|
|
53
|
+
subtree: true,
|
|
54
|
+
characterData: true,
|
|
55
|
+
});
|
|
56
|
+
quietTimer = setTimeout(done, opts.quietPeriod);
|
|
57
|
+
deadlineTimer = setTimeout(done, opts.timeout);
|
|
58
|
+
});
|
|
59
|
+
}, { timeout, quietPeriod });
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -2,12 +2,8 @@ import type { FilterStrategy } from '../types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Example filter strategies.
|
|
4
4
|
* - default: small convenience wrapper that mirrors the engine's default behavior.
|
|
5
|
-
* - spy(factory): returns a strategy that marks `calledRef.called = true` when invoked.
|
|
6
5
|
*/
|
|
7
6
|
export declare const FilterStrategies: {
|
|
8
7
|
default: FilterStrategy;
|
|
9
|
-
spy: (calledRef?: {
|
|
10
|
-
called?: boolean;
|
|
11
|
-
}) => FilterStrategy;
|
|
12
8
|
};
|
|
13
9
|
export type { FilterStrategy } from '../types';
|
|
@@ -4,7 +4,6 @@ exports.FilterStrategies = void 0;
|
|
|
4
4
|
/**
|
|
5
5
|
* Example filter strategies.
|
|
6
6
|
* - default: small convenience wrapper that mirrors the engine's default behavior.
|
|
7
|
-
* - spy(factory): returns a strategy that marks `calledRef.called = true` when invoked.
|
|
8
7
|
*/
|
|
9
8
|
exports.FilterStrategies = {
|
|
10
9
|
default: {
|
|
@@ -20,19 +19,4 @@ exports.FilterStrategies = {
|
|
|
20
19
|
return rows.filter({ has: targetCell.getByText(textVal, { exact: true }) });
|
|
21
20
|
}
|
|
22
21
|
},
|
|
23
|
-
spy: (calledRef = {}) => ({
|
|
24
|
-
apply({ rows, filter, colIndex, tableContext }) {
|
|
25
|
-
calledRef.called = true;
|
|
26
|
-
// Delegate to default behaviour for actual filtering
|
|
27
|
-
const page = tableContext.page;
|
|
28
|
-
const resolve = tableContext.resolve;
|
|
29
|
-
const cellTemplate = resolve(tableContext.config.cellSelector, page);
|
|
30
|
-
const targetCell = cellTemplate.nth(colIndex);
|
|
31
|
-
if (typeof filter.value === 'function') {
|
|
32
|
-
return rows.filter({ has: filter.value(targetCell) });
|
|
33
|
-
}
|
|
34
|
-
const textVal = typeof filter.value === 'number' ? String(filter.value) : filter.value;
|
|
35
|
-
return rows.filter({ has: targetCell.getByText(textVal, { exact: true }) });
|
|
36
|
-
}
|
|
37
|
-
}),
|
|
38
22
|
};
|
|
@@ -13,6 +13,11 @@ export declare const HeaderStrategies: {
|
|
|
13
13
|
/**
|
|
14
14
|
* Physically scrolls the table horizontally to force virtualized columns to mount,
|
|
15
15
|
* collecting their names along the way.
|
|
16
|
+
*
|
|
17
|
+
* **Requires `selector`** — the CSS selector for the horizontal scroll container
|
|
18
|
+
* (e.g. `.dvn-scroller` for Glide, `.rdg-viewport` for RDG). Framework class names
|
|
19
|
+
* belong in presets / your config, not in this generic factory. Without `selector`,
|
|
20
|
+
* only currently visible headers are returned (no scrolling).
|
|
16
21
|
*/
|
|
17
22
|
horizontalScroll: (options?: {
|
|
18
23
|
limit?: number;
|
|
@@ -22,6 +22,11 @@ exports.HeaderStrategies = {
|
|
|
22
22
|
/**
|
|
23
23
|
* Physically scrolls the table horizontally to force virtualized columns to mount,
|
|
24
24
|
* collecting their names along the way.
|
|
25
|
+
*
|
|
26
|
+
* **Requires `selector`** — the CSS selector for the horizontal scroll container
|
|
27
|
+
* (e.g. `.dvn-scroller` for Glide, `.rdg-viewport` for RDG). Framework class names
|
|
28
|
+
* belong in presets / your config, not in this generic factory. Without `selector`,
|
|
29
|
+
* only currently visible headers are returned (no scrolling).
|
|
25
30
|
*/
|
|
26
31
|
horizontalScroll: (options) => {
|
|
27
32
|
return async (context) => {
|
|
@@ -37,19 +42,22 @@ exports.HeaderStrategies = {
|
|
|
37
42
|
};
|
|
38
43
|
let currentHeaders = await getVisible();
|
|
39
44
|
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
40
|
-
const
|
|
41
|
-
|
|
45
|
+
const selector = options === null || options === void 0 ? void 0 : options.selector;
|
|
46
|
+
if (!selector) {
|
|
47
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'HeaderStrategies.horizontalScroll: no selector provided — returning visible headers only. Pass { selector } for the scroll container (e.g. ".dvn-scroller").');
|
|
48
|
+
return Array.from(collectedHeaders);
|
|
49
|
+
}
|
|
50
|
+
const scrollerHandle = await root.evaluateHandle((el, sel) => {
|
|
51
|
+
if (el.matches(sel))
|
|
42
52
|
return el;
|
|
43
|
-
|
|
44
|
-
const effectiveSelector = selector || '.dvn-scroller, .rdg-viewport, [role="grid"]';
|
|
45
|
-
const ancestor = el.closest(effectiveSelector);
|
|
53
|
+
const ancestor = el.closest(sel);
|
|
46
54
|
if (ancestor)
|
|
47
55
|
return ancestor;
|
|
48
|
-
const child = el.querySelector(
|
|
56
|
+
const child = el.querySelector(sel);
|
|
49
57
|
if (child)
|
|
50
58
|
return child;
|
|
51
|
-
return
|
|
52
|
-
},
|
|
59
|
+
return null;
|
|
60
|
+
}, selector);
|
|
53
61
|
const isScrollerFound = await scrollerHandle.evaluate(el => !!el);
|
|
54
62
|
if (isScrollerFound) {
|
|
55
63
|
await scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
@@ -71,7 +79,7 @@ exports.HeaderStrategies = {
|
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
else {
|
|
74
|
-
(0, debugUtils_1.logDebug)(config, 'info',
|
|
82
|
+
(0, debugUtils_1.logDebug)(config, 'info', `HeaderStrategies.horizontalScroll: Could not find scroller matching "${selector}". Returning visible headers.`);
|
|
75
83
|
}
|
|
76
84
|
if (isScrollerFound) {
|
|
77
85
|
await scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
@@ -3,12 +3,19 @@ export * from './sorting';
|
|
|
3
3
|
export * from './columns';
|
|
4
4
|
export * from './headers';
|
|
5
5
|
export * from './fill';
|
|
6
|
-
export
|
|
6
|
+
export type { ColumnResolutionStrategy } from './resolution';
|
|
7
7
|
export * from './dedupe';
|
|
8
8
|
export * from './loading';
|
|
9
9
|
export * from './stabilization';
|
|
10
10
|
export * from './filter';
|
|
11
11
|
export * from './viewport';
|
|
12
|
+
export * from './contentReady';
|
|
13
|
+
/**
|
|
14
|
+
* Built-in strategy factories.
|
|
15
|
+
* @note `CellNavigation` / `Resolution` removed from this namespace in #428 (dead/no-op).
|
|
16
|
+
* Use `strategies.navigation` (NavigationPrimitives) and header maps instead.
|
|
17
|
+
* `Filter.spy` was test-only and is no longer shipped.
|
|
18
|
+
*/
|
|
12
19
|
export declare const Strategies: {
|
|
13
20
|
Pagination: {
|
|
14
21
|
click: (selectors: {
|
|
@@ -26,21 +33,18 @@ export declare const Strategies: {
|
|
|
26
33
|
detectCurrentPage?: (root: import("@playwright/test").Locator) => number | Promise<number>;
|
|
27
34
|
stabilization?: import("./stabilization").StabilizationStrategy;
|
|
28
35
|
timeout?: number;
|
|
29
|
-
}) => import("
|
|
36
|
+
}) => import("..").PaginationStrategy;
|
|
30
37
|
infiniteScroll: (options?: {
|
|
31
38
|
action?: "scroll" | "js-scroll";
|
|
32
39
|
scrollTarget?: import("..").Selector;
|
|
33
40
|
scrollAmount?: number;
|
|
34
41
|
stabilization?: import("./stabilization").StabilizationStrategy;
|
|
35
42
|
timeout?: number;
|
|
36
|
-
}) => import("
|
|
43
|
+
}) => import("..").PaginationStrategy;
|
|
37
44
|
};
|
|
38
45
|
Sorting: {
|
|
39
46
|
AriaSort: () => import("..").SortingStrategy;
|
|
40
47
|
};
|
|
41
|
-
CellNavigation: {
|
|
42
|
-
default: () => Promise<void>;
|
|
43
|
-
};
|
|
44
48
|
Header: {
|
|
45
49
|
visible: ({ config, resolve, root }: import("..").StrategyContext) => Promise<string[]>;
|
|
46
50
|
horizontalScroll: (options?: {
|
|
@@ -50,13 +54,10 @@ export declare const Strategies: {
|
|
|
50
54
|
}) => import("./headers").HeaderStrategy;
|
|
51
55
|
};
|
|
52
56
|
Fill: {
|
|
53
|
-
default: ({ row, columnName, value, fillOptions, config, table }: Parameters<import("
|
|
54
|
-
};
|
|
55
|
-
Resolution: {
|
|
56
|
-
default: import("./resolution").ColumnResolutionStrategy;
|
|
57
|
+
default: ({ row, columnName, value, fillOptions, config, table }: Parameters<import("..").FillStrategy>[0]) => Promise<void>;
|
|
57
58
|
};
|
|
58
59
|
Dedupe: {
|
|
59
|
-
byTopPosition: (tolerance?: number) => import("
|
|
60
|
+
byTopPosition: (tolerance?: number) => import("..").DedupeStrategy;
|
|
60
61
|
};
|
|
61
62
|
Loading: {
|
|
62
63
|
Table: {
|
|
@@ -93,11 +94,18 @@ export declare const Strategies: {
|
|
|
93
94
|
};
|
|
94
95
|
Filter: {
|
|
95
96
|
default: import("./filter").FilterStrategy;
|
|
96
|
-
spy: (calledRef?: {
|
|
97
|
-
called?: boolean;
|
|
98
|
-
}) => import("./filter").FilterStrategy;
|
|
99
97
|
};
|
|
100
98
|
Viewport: {
|
|
101
|
-
dataAttribute: (options?: import("./viewport").DataAttributeViewportOptions) => import("
|
|
99
|
+
dataAttribute: (options?: import("./viewport").DataAttributeViewportOptions) => import("..").ViewportStrategy;
|
|
100
|
+
};
|
|
101
|
+
ContentReady: {
|
|
102
|
+
textStable: (options?: {
|
|
103
|
+
timeout?: number;
|
|
104
|
+
interval?: number;
|
|
105
|
+
}) => import("./contentReady").ContentReadyStrategy;
|
|
106
|
+
mutationSettled: (options?: {
|
|
107
|
+
timeout?: number;
|
|
108
|
+
quietPeriod?: number;
|
|
109
|
+
}) => import("./contentReady").ContentReadyStrategy;
|
|
102
110
|
};
|
|
103
111
|
};
|
package/dist/strategies/index.js
CHANGED
|
@@ -17,36 +17,40 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.Strategies = void 0;
|
|
18
18
|
const pagination_1 = require("./pagination");
|
|
19
19
|
const sorting_1 = require("./sorting");
|
|
20
|
-
const columns_1 = require("./columns");
|
|
21
20
|
const headers_1 = require("./headers");
|
|
22
21
|
const fill_1 = require("./fill");
|
|
23
|
-
const resolution_1 = require("./resolution");
|
|
24
22
|
const dedupe_1 = require("./dedupe");
|
|
25
23
|
const loading_1 = require("./loading");
|
|
26
24
|
const stabilization_1 = require("./stabilization");
|
|
27
25
|
const filter_1 = require("./filter");
|
|
28
26
|
const viewport_1 = require("./viewport");
|
|
27
|
+
const contentReady_1 = require("./contentReady");
|
|
29
28
|
__exportStar(require("./pagination"), exports);
|
|
30
29
|
__exportStar(require("./sorting"), exports);
|
|
31
30
|
__exportStar(require("./columns"), exports);
|
|
32
31
|
__exportStar(require("./headers"), exports);
|
|
33
32
|
__exportStar(require("./fill"), exports);
|
|
34
|
-
__exportStar(require("./resolution"), exports);
|
|
35
33
|
__exportStar(require("./dedupe"), exports);
|
|
36
34
|
__exportStar(require("./loading"), exports);
|
|
37
35
|
__exportStar(require("./stabilization"), exports);
|
|
38
36
|
__exportStar(require("./filter"), exports);
|
|
39
37
|
__exportStar(require("./viewport"), exports);
|
|
38
|
+
__exportStar(require("./contentReady"), exports);
|
|
39
|
+
/**
|
|
40
|
+
* Built-in strategy factories.
|
|
41
|
+
* @note `CellNavigation` / `Resolution` removed from this namespace in #428 (dead/no-op).
|
|
42
|
+
* Use `strategies.navigation` (NavigationPrimitives) and header maps instead.
|
|
43
|
+
* `Filter.spy` was test-only and is no longer shipped.
|
|
44
|
+
*/
|
|
40
45
|
exports.Strategies = {
|
|
41
46
|
Pagination: pagination_1.PaginationStrategies,
|
|
42
47
|
Sorting: sorting_1.SortingStrategies,
|
|
43
|
-
CellNavigation: columns_1.CellNavigationStrategies,
|
|
44
48
|
Header: headers_1.HeaderStrategies,
|
|
45
49
|
Fill: fill_1.FillStrategies,
|
|
46
|
-
Resolution: resolution_1.ResolutionStrategies,
|
|
47
50
|
Dedupe: dedupe_1.DedupeStrategies,
|
|
48
51
|
Loading: loading_1.LoadingStrategies,
|
|
49
52
|
Stabilization: stabilization_1.StabilizationStrategies,
|
|
50
53
|
Filter: filter_1.FilterStrategies,
|
|
51
54
|
Viewport: viewport_1.ViewportStrategies,
|
|
55
|
+
ContentReady: contentReady_1.ContentReadyStrategies,
|
|
52
56
|
};
|
|
@@ -83,6 +83,7 @@ exports.PaginationStrategies = {
|
|
|
83
83
|
const scrollTarget = options.scrollTarget
|
|
84
84
|
? resolve(options.scrollTarget, root)
|
|
85
85
|
: root;
|
|
86
|
+
const beforeScrollTop = await scrollTarget.evaluate((el) => el.scrollTop);
|
|
86
87
|
const doScroll = async () => {
|
|
87
88
|
const box = await scrollTarget.boundingBox();
|
|
88
89
|
const scrollValue = amount * directionMultiplier;
|
|
@@ -98,8 +99,16 @@ exports.PaginationStrategies = {
|
|
|
98
99
|
await page.mouse.wheel(0, scrollValue);
|
|
99
100
|
}
|
|
100
101
|
};
|
|
101
|
-
// Stabilization: Wait
|
|
102
|
-
|
|
102
|
+
// Stabilization: Wait for content to settle
|
|
103
|
+
const stabilizationResult = await stabilization(context, doScroll);
|
|
104
|
+
// EOF detection: use scroll position, not stabilization result.
|
|
105
|
+
// Stabilization may time out for recycling virtualizers when the scroll
|
|
106
|
+
// amount is small enough to stay within the overscan window (no new rows
|
|
107
|
+
// rendered), but the scroll position still changed — we haven't reached
|
|
108
|
+
// the end of the data.
|
|
109
|
+
const afterScrollTop = await scrollTarget.evaluate((el) => el.scrollTop);
|
|
110
|
+
const scrollMoved = Math.abs(afterScrollTop - beforeScrollTop) >= 1;
|
|
111
|
+
return scrollMoved || stabilizationResult;
|
|
103
112
|
};
|
|
104
113
|
};
|
|
105
114
|
const createGoToFirst = () => {
|
|
@@ -3,7 +3,10 @@ export type DataAttributeViewportOptions = {
|
|
|
3
3
|
/**
|
|
4
4
|
* CSS selector for the scroll container (the element with overflow: auto/scroll).
|
|
5
5
|
* Resolved with `closest()` from the root element, so ancestors are found automatically.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* **No framework default** — pass the selector for your table (e.g. Tailwind
|
|
8
|
+
* `div[class*="overflow-auto"]`, MUI `.MuiDataGrid-virtualScroller`). Omitting it
|
|
9
|
+
* means geometry filtering treats all mounted rows as visible and `scrollTo*` is a no-op.
|
|
7
10
|
*/
|
|
8
11
|
scrollContainer?: string;
|
|
9
12
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ViewportStrategies = void 0;
|
|
4
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
4
5
|
/**
|
|
5
6
|
* Viewport strategies for tables where row and cell elements carry a DOM attribute
|
|
6
7
|
* equal to their index (e.g. Braintrust logs table, TanStack-style grids).
|
|
@@ -11,17 +12,30 @@ exports.ViewportStrategies = void 0;
|
|
|
11
12
|
* - scrollToColumn — aligns header's left edge to container left, waits for cell mount
|
|
12
13
|
* - scrollToRow — scrolls row into view, waits for row mount
|
|
13
14
|
*
|
|
15
|
+
* **Important:** `rowAttribute` must be present on the elements matched by `rowSelector`,
|
|
16
|
+
* not on child cells. If row elements lack the attribute, `getVisibleRowRange` returns
|
|
17
|
+
* `{first:0, last:0}` and logs a warning at the `error` log level.
|
|
18
|
+
*
|
|
14
19
|
* @example
|
|
15
|
-
* // TanStack / Braintrust (
|
|
16
|
-
* viewport: Strategies.Viewport.dataAttribute(
|
|
20
|
+
* // TanStack / Braintrust — pass your scroll container explicitly (no Tailwind default)
|
|
21
|
+
* viewport: Strategies.Viewport.dataAttribute({
|
|
22
|
+
* scrollContainer: 'div[class*="overflow-auto"]',
|
|
23
|
+
* })
|
|
17
24
|
*
|
|
18
25
|
* @example
|
|
19
26
|
* // ARIA grid (aria-rowindex / aria-colindex, 1-based)
|
|
20
|
-
* viewport: Strategies.Viewport.dataAttribute({
|
|
27
|
+
* viewport: Strategies.Viewport.dataAttribute({
|
|
28
|
+
* scrollContainer: '.MuiDataGrid-virtualScroller',
|
|
29
|
+
* rowAttribute: 'aria-rowindex',
|
|
30
|
+
* columnAttribute: 'aria-colindex',
|
|
31
|
+
* rowOffset: 1,
|
|
32
|
+
* columnOffset: 1,
|
|
33
|
+
* })
|
|
21
34
|
*/
|
|
22
35
|
const dataAttribute = (options) => {
|
|
23
36
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
24
|
-
|
|
37
|
+
// null = no container: geometry filter keeps all mounted rows; scrollTo* no-ops (#431)
|
|
38
|
+
const containerSel = (_a = options === null || options === void 0 ? void 0 : options.scrollContainer) !== null && _a !== void 0 ? _a : null;
|
|
25
39
|
const attachTimeout = (_b = options === null || options === void 0 ? void 0 : options.attachTimeout) !== null && _b !== void 0 ? _b : 3000;
|
|
26
40
|
const rowAttr = (_c = options === null || options === void 0 ? void 0 : options.rowAttribute) !== null && _c !== void 0 ? _c : 'data-index';
|
|
27
41
|
const colAttr = (_d = options === null || options === void 0 ? void 0 : options.columnAttribute) !== null && _d !== void 0 ? _d : 'data-index';
|
|
@@ -34,17 +48,22 @@ const dataAttribute = (options) => {
|
|
|
34
48
|
getVisibleColumnRange: async ({ root, config }) => {
|
|
35
49
|
const rowSel = config.rowSelector;
|
|
36
50
|
const cellSel = typeof config.cellSelector === 'string' ? config.cellSelector : `[${colAttr}]`;
|
|
37
|
-
|
|
51
|
+
const result = await root.evaluate((el, { rowSel, cellSel, colAttr, colOffset }) => {
|
|
38
52
|
const firstRow = el.querySelector(rowSel);
|
|
39
53
|
if (!firstRow)
|
|
40
|
-
return { first: 0, last: 0 };
|
|
41
|
-
const
|
|
54
|
+
return { first: 0, last: 0, _cellCount: 0, _validCount: 0 };
|
|
55
|
+
const cells = Array.from(firstRow.querySelectorAll(cellSel));
|
|
56
|
+
const indices = cells
|
|
42
57
|
.map(c => Number(c.getAttribute(colAttr)) - colOffset)
|
|
43
58
|
.filter(n => !isNaN(n));
|
|
44
59
|
if (!indices.length)
|
|
45
|
-
return { first: 0, last: 0 };
|
|
46
|
-
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
60
|
+
return { first: 0, last: 0, _cellCount: cells.length, _validCount: 0 };
|
|
61
|
+
return { first: Math.min(...indices), last: Math.max(...indices), _cellCount: cells.length, _validCount: indices.length };
|
|
47
62
|
}, { rowSel, cellSel, colAttr, colOffset });
|
|
63
|
+
if (result._cellCount > 0 && result._validCount === 0) {
|
|
64
|
+
(0, debugUtils_1.logDebug)(config, 'error', `dataAttribute viewport: ${result._cellCount} cell(s) found but none have attribute "${colAttr}". Check that columnAttribute targets cell elements matched by cellSelector.`);
|
|
65
|
+
}
|
|
66
|
+
return { first: result.first, last: result.last };
|
|
48
67
|
},
|
|
49
68
|
getVisibleRowIndices: async ({ root, config }) => {
|
|
50
69
|
const rowSel = config.rowSelector;
|
|
@@ -52,7 +71,9 @@ const dataAttribute = (options) => {
|
|
|
52
71
|
// the rows' DOM positions so the iteration engine can drop overscan rows (#353/#357).
|
|
53
72
|
return root.evaluate((el, { rowSel, containerSel }) => {
|
|
54
73
|
const rows = Array.from(el.querySelectorAll(rowSel));
|
|
55
|
-
const container =
|
|
74
|
+
const container = containerSel
|
|
75
|
+
? el.closest(containerSel)
|
|
76
|
+
: null;
|
|
56
77
|
const containerRect = container ? container.getBoundingClientRect() : null;
|
|
57
78
|
const visible = [];
|
|
58
79
|
rows.forEach((r, i) => {
|
|
@@ -72,41 +93,42 @@ const dataAttribute = (options) => {
|
|
|
72
93
|
},
|
|
73
94
|
getVisibleRowRange: async ({ root, config }) => {
|
|
74
95
|
const rowSel = config.rowSelector;
|
|
75
|
-
|
|
96
|
+
const result = await root.evaluate((el, { rowSel, rowAttr, rowOffset, containerSel }) => {
|
|
76
97
|
const rows = Array.from(el.querySelectorAll(rowSel));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// Inclusive: a row with ANY vertical overlap counts as visible; only rows
|
|
81
|
-
// entirely above or below the container are dropped — so we never drop a
|
|
82
|
-
// partially-visible real row.
|
|
83
|
-
const container = el.closest(containerSel);
|
|
98
|
+
const container = containerSel
|
|
99
|
+
? el.closest(containerSel)
|
|
100
|
+
: null;
|
|
84
101
|
const containerRect = container ? container.getBoundingClientRect() : null;
|
|
85
|
-
const
|
|
102
|
+
const visibleRows = rows
|
|
86
103
|
.filter(r => {
|
|
87
|
-
// Container not resolvable — can't measure, so keep all rows (safe
|
|
88
|
-
// fallback, preserves pre-#353 behavior rather than risk dropping real rows).
|
|
89
104
|
if (!containerRect)
|
|
90
105
|
return true;
|
|
91
106
|
const rect = r.getBoundingClientRect();
|
|
92
107
|
if (rect.height === 0)
|
|
93
|
-
return false;
|
|
108
|
+
return false;
|
|
94
109
|
return rect.bottom > containerRect.top && rect.top < containerRect.bottom;
|
|
95
|
-
})
|
|
110
|
+
});
|
|
111
|
+
const indices = visibleRows
|
|
96
112
|
.map(r => Number(r.getAttribute(rowAttr)) - rowOffset)
|
|
97
113
|
.filter(n => !isNaN(n));
|
|
98
114
|
if (!indices.length)
|
|
99
|
-
return { first: 0, last: 0 };
|
|
100
|
-
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
115
|
+
return { first: 0, last: 0, _rowCount: visibleRows.length, _validCount: 0 };
|
|
116
|
+
return { first: Math.min(...indices), last: Math.max(...indices), _rowCount: visibleRows.length, _validCount: indices.length };
|
|
101
117
|
}, { rowSel, rowAttr, rowOffset, containerSel });
|
|
118
|
+
if (result._rowCount > 0 && result._validCount === 0) {
|
|
119
|
+
(0, debugUtils_1.logDebug)(config, 'error', `dataAttribute viewport: ${result._rowCount} row(s) found but none have attribute "${rowAttr}". Check that rowAttribute targets row elements matched by rowSelector, not child cells. getVisibleRowRange returning {first:0, last:0}.`);
|
|
120
|
+
}
|
|
121
|
+
return { first: result.first, last: result.last };
|
|
102
122
|
},
|
|
103
123
|
scrollToColumn: async ({ root, config }, colIndex) => {
|
|
104
124
|
const headerSel = typeof config.headerSelector === 'string' ? config.headerSelector : null;
|
|
125
|
+
if (!containerSel || !headerSel)
|
|
126
|
+
return;
|
|
105
127
|
const cellSel = typeof config.cellSelector === 'string' ? config.cellSelector : `[${colAttr}]`;
|
|
106
|
-
await root.evaluate((el, { containerSel, headerSel, cellSel, idx, columnWidth, scrollPadding }) => {
|
|
128
|
+
const canScroll = await root.evaluate((el, { containerSel, headerSel, cellSel, idx, columnWidth, scrollPadding }) => {
|
|
107
129
|
const container = el.closest(containerSel);
|
|
108
|
-
if (!container
|
|
109
|
-
return;
|
|
130
|
+
if (!container)
|
|
131
|
+
return false;
|
|
110
132
|
const headers = Array.from(el.querySelectorAll(headerSel));
|
|
111
133
|
const target = headers[idx];
|
|
112
134
|
if (!target) {
|
|
@@ -118,7 +140,7 @@ const dataAttribute = (options) => {
|
|
|
118
140
|
? widths.reduce((sum, width) => sum + width, 0) / widths.length
|
|
119
141
|
: 120);
|
|
120
142
|
container.scrollLeft = Math.max(0, idx * estimatedWidth - scrollPadding);
|
|
121
|
-
return;
|
|
143
|
+
return true;
|
|
122
144
|
}
|
|
123
145
|
const cRect = container.getBoundingClientRect();
|
|
124
146
|
const tRect = target.getBoundingClientRect();
|
|
@@ -130,7 +152,10 @@ const dataAttribute = (options) => {
|
|
|
130
152
|
// Scrolling right: reveal the target's right edge with padding.
|
|
131
153
|
container.scrollLeft += (tRect.right - cRect.right) + scrollPadding;
|
|
132
154
|
}
|
|
155
|
+
return true;
|
|
133
156
|
}, { containerSel, headerSel, cellSel, idx: colIndex, columnWidth, scrollPadding });
|
|
157
|
+
if (!canScroll)
|
|
158
|
+
return;
|
|
134
159
|
// Wait for a cell at this column index to mount in any row
|
|
135
160
|
await root
|
|
136
161
|
.locator(`${config.rowSelector} [${colAttr}="${colIndex + colOffset}"]`)
|
|
@@ -138,15 +163,17 @@ const dataAttribute = (options) => {
|
|
|
138
163
|
.waitFor({ state: 'attached', timeout: attachTimeout });
|
|
139
164
|
},
|
|
140
165
|
scrollToRow: async ({ root, config }, rowIndex) => {
|
|
166
|
+
if (!containerSel)
|
|
167
|
+
return;
|
|
141
168
|
const rowSel = config.rowSelector;
|
|
142
|
-
await root.evaluate((el, { containerSel, rowSel, rowAttr, idx, rowOffset, rowHeight, scrollPadding }) => {
|
|
169
|
+
const canScroll = await root.evaluate((el, { containerSel, rowSel, rowAttr, idx, rowOffset, rowHeight, scrollPadding }) => {
|
|
143
170
|
const container = el.closest(containerSel);
|
|
144
171
|
if (!container)
|
|
145
|
-
return;
|
|
172
|
+
return false;
|
|
146
173
|
const row = container.querySelector(`${rowSel}[${rowAttr}="${idx + rowOffset}"]`);
|
|
147
174
|
if (row) {
|
|
148
175
|
row.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
149
|
-
return;
|
|
176
|
+
return true;
|
|
150
177
|
}
|
|
151
178
|
const visibleRows = Array.from(container.querySelectorAll(rowSel));
|
|
152
179
|
const heights = visibleRows
|
|
@@ -156,7 +183,10 @@ const dataAttribute = (options) => {
|
|
|
156
183
|
? heights.reduce((sum, height) => sum + height, 0) / heights.length
|
|
157
184
|
: 40);
|
|
158
185
|
container.scrollTop = Math.max(0, idx * estimatedHeight - scrollPadding);
|
|
186
|
+
return true;
|
|
159
187
|
}, { containerSel, rowSel, rowAttr, idx: rowIndex, rowOffset, rowHeight, scrollPadding });
|
|
188
|
+
if (!canScroll)
|
|
189
|
+
return;
|
|
160
190
|
await root
|
|
161
191
|
.locator(`${rowSel}[${rowAttr}="${rowIndex + rowOffset}"]`)
|
|
162
192
|
.waitFor({ state: 'attached', timeout: attachTimeout });
|