@rickcedwhat/playwright-smart-table 6.11.0 → 6.13.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/README.md +1 -0
- package/dist/engine/rowFinder.d.ts +3 -2
- package/dist/engine/rowFinder.js +41 -23
- package/dist/engine/tableIteration.d.ts +1 -0
- package/dist/engine/tableIteration.js +11 -4
- package/dist/index.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/plugins/index.d.ts +9 -9
- package/dist/presets/glide/index.d.ts +14 -9
- package/dist/presets/glide/index.js +23 -11
- package/dist/presets/glide/viewport.d.ts +9 -0
- package/dist/presets/glide/viewport.js +113 -0
- package/dist/presets/index.d.ts +2 -1
- package/dist/presets/index.js +3 -1
- package/dist/presets/mui.js +15 -2
- package/dist/smartRow.js +59 -24
- package/dist/strategies/columns.d.ts +10 -0
- package/dist/strategies/index.d.ts +1 -1
- package/dist/strategies/stabilization.js +14 -1
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +8 -6
- package/dist/types.d.ts +8 -4
- package/dist/useTable.js +74 -6
- package/dist/utils/elementTracker.d.ts +10 -0
- package/dist/utils/elementTracker.js +31 -7
- package/dist/utils/navigationBarrier.d.ts +2 -0
- package/dist/utils/navigationBarrier.js +4 -0
- package/package.json +17 -19
- package/dist/presets/glide/columns.d.ts +0 -14
- package/dist/presets/glide/columns.js +0 -53
package/README.md
CHANGED
|
@@ -11,10 +11,9 @@ export declare class RowFinder<T = any> {
|
|
|
11
11
|
private makeSmartRow;
|
|
12
12
|
private tableState;
|
|
13
13
|
private resolve;
|
|
14
|
-
constructor(rootLocator: Locator, config: FinalTableConfig, resolve: (item: Selector, parent: Locator | Page) => Locator, filterEngine: FilterEngine, tableMapper: TableMapper, makeSmartRow: (loc: Locator, map: Map<string, number>, index: number, tablePageIndex?: number) => SmartRow<T>, tableState?: {
|
|
14
|
+
constructor(rootLocator: Locator, config: FinalTableConfig, resolve: (item: Selector, parent: Locator | Page) => Locator, filterEngine: FilterEngine, tableMapper: TableMapper, makeSmartRow: (loc: Locator, map: Map<string, number>, index: number | undefined, tablePageIndex?: number) => SmartRow<T>, tableState?: {
|
|
15
15
|
currentPageIndex: number;
|
|
16
16
|
});
|
|
17
|
-
private log;
|
|
18
17
|
findRow(filters: Record<string, FilterValue>, options?: {
|
|
19
18
|
exact?: boolean;
|
|
20
19
|
maxPages?: number;
|
|
@@ -22,6 +21,8 @@ export declare class RowFinder<T = any> {
|
|
|
22
21
|
findRows(filters?: Record<string, FilterValue>, options?: {
|
|
23
22
|
exact?: boolean;
|
|
24
23
|
maxPages?: number;
|
|
24
|
+
useBulkPagination?: boolean;
|
|
25
25
|
}): Promise<SmartRowArray<T>>;
|
|
26
26
|
private findRowLocator;
|
|
27
|
+
private resolveRowIndex;
|
|
27
28
|
}
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -16,9 +16,6 @@ class RowFinder {
|
|
|
16
16
|
this.tableState = tableState;
|
|
17
17
|
this.resolve = resolve;
|
|
18
18
|
}
|
|
19
|
-
log(msg) {
|
|
20
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', msg);
|
|
21
|
-
}
|
|
22
19
|
async findRow(filters, options = {}) {
|
|
23
20
|
(0, debugUtils_1.logDebug)(this.config, 'info', 'Searching for row', filters);
|
|
24
21
|
await this.tableMapper.getMap();
|
|
@@ -26,7 +23,9 @@ class RowFinder {
|
|
|
26
23
|
if (rowLocator) {
|
|
27
24
|
(0, debugUtils_1.logDebug)(this.config, 'info', 'Row found');
|
|
28
25
|
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
29
|
-
|
|
26
|
+
const map = await this.tableMapper.getMap();
|
|
27
|
+
const rowIndex = await this.resolveRowIndex(rowLocator);
|
|
28
|
+
return this.makeSmartRow(rowLocator, map, rowIndex, this.tableState.currentPageIndex);
|
|
30
29
|
}
|
|
31
30
|
(0, debugUtils_1.logDebug)(this.config, 'error', 'Row not found', filters);
|
|
32
31
|
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
@@ -43,7 +42,7 @@ class RowFinder {
|
|
|
43
42
|
const allRows = [];
|
|
44
43
|
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
45
44
|
let pagesScanned = 1;
|
|
46
|
-
this.
|
|
45
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: starting (maxPages=${effectiveMaxPages}, filters=${JSON.stringify(filtersRecord)})`);
|
|
47
46
|
const tracker = new elementTracker_1.ElementTracker('findRows');
|
|
48
47
|
try {
|
|
49
48
|
const collectMatches = async () => {
|
|
@@ -61,13 +60,13 @@ class RowFinder {
|
|
|
61
60
|
for (const idx of newIndices) {
|
|
62
61
|
const smartRow = this.makeSmartRow(currentRows[idx], map, allRows.length, this.tableState.currentPageIndex);
|
|
63
62
|
if (isRowLoading && await isRowLoading(smartRow)) {
|
|
64
|
-
this.
|
|
63
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: page ${this.tableState.currentPageIndex} — row skipped (isRowLoading=true)`);
|
|
65
64
|
continue;
|
|
66
65
|
}
|
|
67
66
|
allRows.push(smartRow);
|
|
68
67
|
added++;
|
|
69
68
|
}
|
|
70
|
-
this.
|
|
69
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
71
70
|
};
|
|
72
71
|
// Scan first page
|
|
73
72
|
await collectMatches();
|
|
@@ -80,40 +79,42 @@ class RowFinder {
|
|
|
80
79
|
page: this.rootLocator.page()
|
|
81
80
|
};
|
|
82
81
|
let paginationResult;
|
|
83
|
-
|
|
82
|
+
const useBulk = (options === null || options === void 0 ? void 0 : options.useBulkPagination) !== false && !!((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk);
|
|
83
|
+
if (useBulk) {
|
|
84
84
|
paginationResult = await this.config.strategies.pagination.goNextBulk(context);
|
|
85
85
|
}
|
|
86
86
|
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
87
87
|
paginationResult = await this.config.strategies.pagination.goNext(context);
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
|
-
this.
|
|
90
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: no pagination primitive — stopping`);
|
|
91
91
|
break;
|
|
92
92
|
}
|
|
93
93
|
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
94
94
|
if (!didPaginate) {
|
|
95
|
-
this.
|
|
95
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: pagination returned false — end of data`);
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
98
98
|
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
99
99
|
this.tableState.currentPageIndex += pagesJumped;
|
|
100
100
|
pagesScanned += pagesJumped;
|
|
101
|
-
this.
|
|
101
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
102
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'pagination');
|
|
102
103
|
await collectMatches();
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
finally {
|
|
106
107
|
await tracker.cleanup(this.rootLocator.page());
|
|
107
108
|
}
|
|
108
|
-
this.
|
|
109
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
|
|
109
110
|
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
110
111
|
}
|
|
111
112
|
async findRowLocator(filters, options = {}) {
|
|
112
|
-
var _a, _b
|
|
113
|
+
var _a, _b;
|
|
113
114
|
const map = await this.tableMapper.getMap();
|
|
114
115
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
|
|
115
116
|
let pagesScanned = 1;
|
|
116
|
-
this.
|
|
117
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
117
118
|
while (true) {
|
|
118
119
|
// Check Loading
|
|
119
120
|
if ((_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
|
|
@@ -124,7 +125,7 @@ class RowFinder {
|
|
|
124
125
|
resolve: this.resolve
|
|
125
126
|
});
|
|
126
127
|
if (isLoading) {
|
|
127
|
-
this.
|
|
128
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', 'Table is loading... waiting');
|
|
128
129
|
await this.rootLocator.page().waitForTimeout(200);
|
|
129
130
|
continue;
|
|
130
131
|
}
|
|
@@ -132,7 +133,7 @@ class RowFinder {
|
|
|
132
133
|
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
133
134
|
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
|
|
134
135
|
const count = await matchedRows.count();
|
|
135
|
-
this.
|
|
136
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
|
|
136
137
|
if (count > 1) {
|
|
137
138
|
const sampleData = [];
|
|
138
139
|
try {
|
|
@@ -151,7 +152,7 @@ class RowFinder {
|
|
|
151
152
|
if (count === 1)
|
|
152
153
|
return matchedRows.first();
|
|
153
154
|
if (pagesScanned < effectiveMaxPages) {
|
|
154
|
-
this.
|
|
155
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Not found. Attempting pagination...`);
|
|
155
156
|
const context = {
|
|
156
157
|
root: this.rootLocator,
|
|
157
158
|
config: this.config,
|
|
@@ -159,14 +160,16 @@ class RowFinder {
|
|
|
159
160
|
page: this.rootLocator.page()
|
|
160
161
|
};
|
|
161
162
|
let paginationResult;
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
const pagination = this.config.strategies.pagination;
|
|
164
|
+
const useBulk = options.useBulkPagination !== false && !!(pagination === null || pagination === void 0 ? void 0 : pagination.goNextBulk);
|
|
165
|
+
if (useBulk && (pagination === null || pagination === void 0 ? void 0 : pagination.goNextBulk)) {
|
|
166
|
+
paginationResult = await pagination.goNextBulk(context);
|
|
164
167
|
}
|
|
165
|
-
else if (
|
|
166
|
-
paginationResult = await
|
|
168
|
+
else if (pagination === null || pagination === void 0 ? void 0 : pagination.goNext) {
|
|
169
|
+
paginationResult = await pagination.goNext(context);
|
|
167
170
|
}
|
|
168
171
|
else {
|
|
169
|
-
this.
|
|
172
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Pagination failed (no goNext or goNextBulk primitive).`);
|
|
170
173
|
return null;
|
|
171
174
|
}
|
|
172
175
|
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
@@ -174,14 +177,29 @@ class RowFinder {
|
|
|
174
177
|
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
175
178
|
this.tableState.currentPageIndex += pagesJumped;
|
|
176
179
|
pagesScanned += pagesJumped;
|
|
180
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRowLocator: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
181
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'pagination');
|
|
177
182
|
continue;
|
|
178
183
|
}
|
|
179
184
|
else {
|
|
180
|
-
this.
|
|
185
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Pagination failed (end of data).`);
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
return null;
|
|
184
189
|
}
|
|
185
190
|
}
|
|
191
|
+
async resolveRowIndex(rowLocator) {
|
|
192
|
+
const allRows = await this.resolve(this.config.rowSelector, this.rootLocator).all();
|
|
193
|
+
const targetHandle = await rowLocator.elementHandle();
|
|
194
|
+
if (!targetHandle)
|
|
195
|
+
return undefined;
|
|
196
|
+
for (let i = 0; i < allRows.length; i++) {
|
|
197
|
+
const handle = await allRows[i].elementHandle();
|
|
198
|
+
if (handle && await handle.evaluate((el, t) => el === t, targetHandle)) {
|
|
199
|
+
return i;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
186
204
|
}
|
|
187
205
|
exports.RowFinder = RowFinder;
|
|
@@ -11,6 +11,7 @@ export interface TableIterationEnv<T = any> {
|
|
|
11
11
|
createSmartRowArray: (rows: SmartRow<T>[]) => SmartRowArray<T>;
|
|
12
12
|
config: FinalTableConfig<T>;
|
|
13
13
|
getPage: () => Page;
|
|
14
|
+
getCurrentPageIndex: () => number;
|
|
14
15
|
}
|
|
15
16
|
/** Delegates to {@link runMap}; void results are discarded. */
|
|
16
17
|
export declare function runForEach<T>(env: TableIterationEnv<T>, callback: (ctx: RowIterationContext<T>) => void | Promise<void>, options?: RowIterationOptions): Promise<void>;
|
|
@@ -27,7 +27,7 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
27
27
|
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
28
28
|
const defaultMode = label === 'map' ? 'parallel' : 'sequential';
|
|
29
29
|
const concurrency = (_d = (_c = options.concurrency) !== null && _c !== void 0 ? _c : env.config.concurrency) !== null && _d !== void 0 ? _d : defaultMode;
|
|
30
|
-
const useBarrier = concurrency
|
|
30
|
+
const useBarrier = concurrency === 'synchronized';
|
|
31
31
|
// Mutex must not pair with the navigation barrier: synchronized mode needs every row
|
|
32
32
|
// to enter barrier.sync concurrently; serializing callbacks here deadlocks (first row waits
|
|
33
33
|
// for batchSize peers that never reach the barrier).
|
|
@@ -50,8 +50,15 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
50
50
|
};
|
|
51
51
|
while (!stopped) {
|
|
52
52
|
const rowLocators = env.getRowLocators();
|
|
53
|
-
const
|
|
53
|
+
const allIndices = await tracker.peekUnseenIndices(rowLocators);
|
|
54
54
|
const pageRows = await rowLocators.all();
|
|
55
|
+
// In synchronized mode, overscan rows rendered beyond the visible viewport by virtual
|
|
56
|
+
// scrollers can be evicted when the horizontal barrier fires snapFirstColumnIntoView.
|
|
57
|
+
// Filter them out before committing so they stay unseen and are picked up on the next page.
|
|
58
|
+
const newIndices = concurrency === 'synchronized'
|
|
59
|
+
? (await Promise.all(allIndices.map(async (idx) => ({ idx, present: pageRows[idx] != null && (await pageRows[idx].count()) > 0 })))).filter(r => r.present).map(r => r.idx)
|
|
60
|
+
: allIndices;
|
|
61
|
+
await tracker.commitIndices(rowLocators, newIndices);
|
|
55
62
|
const batchSize = newIndices.length;
|
|
56
63
|
if (batchSize === 0) {
|
|
57
64
|
log(env.config, `${label}: page ${pagesScanned} — no new row(s) found`);
|
|
@@ -60,7 +67,7 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
60
67
|
log(env.config, `${label}: scanning page ${pagesScanned} — ${batchSize} new row(s)`);
|
|
61
68
|
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(batchSize) : undefined;
|
|
62
69
|
const actionMutex = useMutex ? new mutex_1.Mutex() : null;
|
|
63
|
-
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i,
|
|
70
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i, env.getCurrentPageIndex(), barrier));
|
|
64
71
|
const processRow = async (row) => {
|
|
65
72
|
try {
|
|
66
73
|
if (stopped && row.rowIndex > stoppedIndex) {
|
|
@@ -79,7 +86,7 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
79
86
|
if (stopped && row.rowIndex > stoppedIndex)
|
|
80
87
|
return SKIP;
|
|
81
88
|
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
82
|
-
return await callback({ row, index: row.rowIndex, rowIndex: row.rowIndex, stop: () => stop(row.rowIndex) });
|
|
89
|
+
return await callback({ row, index: row.rowIndex, rowIndex: row.rowIndex, pageIndex: env.getCurrentPageIndex(), stop: () => stop(row.rowIndex) });
|
|
83
90
|
};
|
|
84
91
|
if (actionMutex) {
|
|
85
92
|
return await actionMutex.run(runCallback);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { PLAYWRIGHT_SMART_TABLE_VERSION } from './packageVersion';
|
|
2
2
|
export { useTable } from './useTable';
|
|
3
|
-
export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, } from './types';
|
|
3
|
+
export type { TableConfig, TableResult, SmartRow, SmartCell, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, } from './types';
|
|
4
4
|
export { Strategies } from './strategies';
|
|
5
5
|
export * as presets from './presets';
|
|
6
6
|
/** @deprecated Use `presets` instead. Plugins will be removed in v7.0.0. */
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Generated by scripts/embed-version.mjs from package.json
|
|
4
4
|
*/
|
|
5
5
|
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
6
|
-
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.
|
|
6
|
+
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.13.0";
|
package/dist/packageVersion.js
CHANGED
|
@@ -6,4 +6,4 @@ exports.PLAYWRIGHT_SMART_TABLE_VERSION = void 0;
|
|
|
6
6
|
* Generated by scripts/embed-version.mjs from package.json
|
|
7
7
|
*/
|
|
8
8
|
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
9
|
-
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.
|
|
9
|
+
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.13.0";
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -8,15 +8,15 @@ export declare const Plugins: {
|
|
|
8
8
|
/** @deprecated Use `presets.rdg` */
|
|
9
9
|
RDG: {
|
|
10
10
|
Strategies: import("../types").TableStrategies | undefined;
|
|
11
|
-
headerSelector?: string | ((root: import("playwright
|
|
11
|
+
headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
12
12
|
rowSelector?: string | undefined;
|
|
13
|
-
cellSelector?: string | ((row: import("playwright
|
|
13
|
+
cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
14
14
|
maxPages?: number | undefined;
|
|
15
15
|
concurrency?: import("../types").RowIterationMode | undefined;
|
|
16
16
|
headerTransformer?: ((args: {
|
|
17
17
|
text: string;
|
|
18
18
|
index: number;
|
|
19
|
-
locator: import("playwright
|
|
19
|
+
locator: import("@playwright/test").Locator;
|
|
20
20
|
seenHeaders: Set<string>;
|
|
21
21
|
}) => string | Promise<string>) | undefined;
|
|
22
22
|
autoScroll?: boolean | undefined;
|
|
@@ -28,15 +28,15 @@ export declare const Plugins: {
|
|
|
28
28
|
/** @deprecated Use `presets.glide` */
|
|
29
29
|
Glide: {
|
|
30
30
|
Strategies: import("../types").TableStrategies | undefined;
|
|
31
|
-
headerSelector?: string | ((root: import("playwright
|
|
31
|
+
headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
32
32
|
rowSelector?: string | undefined;
|
|
33
|
-
cellSelector?: string | ((row: import("playwright
|
|
33
|
+
cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
34
34
|
maxPages?: number | undefined;
|
|
35
35
|
concurrency?: import("../types").RowIterationMode | undefined;
|
|
36
36
|
headerTransformer?: ((args: {
|
|
37
37
|
text: string;
|
|
38
38
|
index: number;
|
|
39
|
-
locator: import("playwright
|
|
39
|
+
locator: import("@playwright/test").Locator;
|
|
40
40
|
seenHeaders: Set<string>;
|
|
41
41
|
}) => string | Promise<string>) | undefined;
|
|
42
42
|
autoScroll?: boolean | undefined;
|
|
@@ -48,15 +48,15 @@ export declare const Plugins: {
|
|
|
48
48
|
/** @deprecated Use `presets.muiDataGrid` */
|
|
49
49
|
MUI: {
|
|
50
50
|
Strategies: import("../types").TableStrategies | undefined;
|
|
51
|
-
headerSelector?: string | ((root: import("playwright
|
|
51
|
+
headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
52
52
|
rowSelector?: string | undefined;
|
|
53
|
-
cellSelector?: string | ((row: import("playwright
|
|
53
|
+
cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
54
54
|
maxPages?: number | undefined;
|
|
55
55
|
concurrency?: import("../types").RowIterationMode | undefined;
|
|
56
56
|
headerTransformer?: ((args: {
|
|
57
57
|
text: string;
|
|
58
58
|
index: number;
|
|
59
|
-
locator: import("playwright
|
|
59
|
+
locator: import("@playwright/test").Locator;
|
|
60
60
|
seenHeaders: Set<string>;
|
|
61
61
|
}) => string | Promise<string>) | undefined;
|
|
62
62
|
autoScroll?: boolean | undefined;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { FillStrategy, TableConfig } from '../../types';
|
|
2
|
+
export { createGlideViewport } from './viewport';
|
|
3
|
+
export type { GlideViewportOptions } from './viewport';
|
|
4
|
+
import { GlideViewportOptions } from './viewport';
|
|
2
5
|
/** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
|
|
3
6
|
export declare const GlideStrategies: {
|
|
4
7
|
fillSimple: FillStrategy;
|
|
@@ -9,15 +12,7 @@ export declare const GlideStrategies: {
|
|
|
9
12
|
selector?: string;
|
|
10
13
|
scrollAmount?: number;
|
|
11
14
|
}) => Promise<string[]>;
|
|
12
|
-
|
|
13
|
-
goUp: (context: import("../..").StrategyContext) => Promise<void>;
|
|
14
|
-
goDown: (context: import("../..").StrategyContext) => Promise<void>;
|
|
15
|
-
goLeft: (context: import("../..").StrategyContext) => Promise<void>;
|
|
16
|
-
goRight: (context: import("../..").StrategyContext) => Promise<void>;
|
|
17
|
-
goHome: (context: import("../..").StrategyContext) => Promise<void>;
|
|
18
|
-
snapFirstColumnIntoView: (context: import("../..").StrategyContext) => Promise<void>;
|
|
19
|
-
seekColumnIndex: (context: import("../..").StrategyContext, columnIndex: number) => Promise<void>;
|
|
20
|
-
};
|
|
15
|
+
viewport: import("../../types").ViewportStrategy;
|
|
21
16
|
loading: {
|
|
22
17
|
isHeaderLoading: () => Promise<boolean>;
|
|
23
18
|
};
|
|
@@ -28,6 +23,16 @@ export declare const GlideStrategies: {
|
|
|
28
23
|
locator: any;
|
|
29
24
|
} | null>;
|
|
30
25
|
};
|
|
26
|
+
/** Alias of {@link GlideViewportOptions}. All fields are forwarded to the viewport strategy. */
|
|
27
|
+
export type GlideOptions = GlideViewportOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Factory that returns a configured Glide preset.
|
|
30
|
+
* Use when your grid differs from the defaults:
|
|
31
|
+
* ```ts
|
|
32
|
+
* useTable(loc, { ...createGlide({ attachTimeout: 5000 }), maxPages: 5 })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function createGlide(options?: GlideOptions): Partial<TableConfig>;
|
|
31
36
|
export declare const Glide: Partial<TableConfig> & {
|
|
32
37
|
Strategies: typeof GlideStrategies;
|
|
33
38
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Glide = exports.GlideStrategies = void 0;
|
|
4
|
-
|
|
3
|
+
exports.Glide = exports.GlideStrategies = exports.createGlideViewport = void 0;
|
|
4
|
+
exports.createGlide = createGlide;
|
|
5
5
|
const headers_1 = require("./headers");
|
|
6
6
|
const pagination_1 = require("../../strategies/pagination");
|
|
7
7
|
const stabilization_1 = require("../../strategies/stabilization");
|
|
8
|
+
var viewport_1 = require("./viewport");
|
|
9
|
+
Object.defineProperty(exports, "createGlideViewport", { enumerable: true, get: function () { return viewport_1.createGlideViewport; } });
|
|
10
|
+
const viewport_2 = require("./viewport");
|
|
8
11
|
const glideFillStrategy = async ({ row, columnName, value, page }) => {
|
|
9
12
|
// Canvas-aware click: Glide is a canvas grid. The accessibility 'td' elements
|
|
10
13
|
// may not trigger internal focus on a simple click. We click the canvas at the cell's location.
|
|
@@ -86,15 +89,7 @@ const GlideDefaultStrategies = {
|
|
|
86
89
|
fill: glideFillStrategy,
|
|
87
90
|
pagination: glidePaginationStrategy,
|
|
88
91
|
header: headers_1.scrollRightHeader,
|
|
89
|
-
|
|
90
|
-
goUp: columns_1.glideGoUp,
|
|
91
|
-
goDown: columns_1.glideGoDown,
|
|
92
|
-
goLeft: columns_1.glideGoLeft,
|
|
93
|
-
goRight: columns_1.glideGoRight,
|
|
94
|
-
goHome: columns_1.glideSnapFirstColumnIntoView,
|
|
95
|
-
snapFirstColumnIntoView: columns_1.glideSnapFirstColumnIntoView,
|
|
96
|
-
seekColumnIndex: columns_1.glideSeekColumnIndex
|
|
97
|
-
},
|
|
92
|
+
viewport: (0, viewport_2.createGlideViewport)(),
|
|
98
93
|
loading: {
|
|
99
94
|
isHeaderLoading: async () => false
|
|
100
95
|
},
|
|
@@ -103,10 +98,27 @@ const GlideDefaultStrategies = {
|
|
|
103
98
|
};
|
|
104
99
|
/** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
|
|
105
100
|
exports.GlideStrategies = Object.assign(Object.assign({}, GlideDefaultStrategies), { fillSimple: glideFillSimple });
|
|
101
|
+
/**
|
|
102
|
+
* Factory that returns a configured Glide preset.
|
|
103
|
+
* Use when your grid differs from the defaults:
|
|
104
|
+
* ```ts
|
|
105
|
+
* useTable(loc, { ...createGlide({ attachTimeout: 5000 }), maxPages: 5 })
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
function createGlide(options = {}) {
|
|
109
|
+
return {
|
|
110
|
+
headerSelector: 'table[role="grid"] thead tr th',
|
|
111
|
+
rowSelector: 'table[role="grid"] tbody tr',
|
|
112
|
+
cellSelector: 'td',
|
|
113
|
+
concurrency: 'sequential',
|
|
114
|
+
strategies: Object.assign(Object.assign({}, GlideDefaultStrategies), { viewport: (0, viewport_2.createGlideViewport)(options) })
|
|
115
|
+
};
|
|
116
|
+
}
|
|
106
117
|
/**
|
|
107
118
|
* Full preset for Glide Data Grid (selectors + default strategies only).
|
|
108
119
|
* Spread: useTable(loc, { ...Plugins.Glide, maxPages: 5 }).
|
|
109
120
|
* Strategies only (including fillSimple): useTable(loc, { rowSelector: '...', strategies: Plugins.Glide.Strategies }).
|
|
121
|
+
* For a non-64-column grid, use `createGlide({ columnCount })` instead.
|
|
110
122
|
*/
|
|
111
123
|
const GlidePreset = {
|
|
112
124
|
headerSelector: 'table[role="grid"] thead tr th',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ViewportStrategy } from '../../types';
|
|
2
|
+
export interface GlideViewportOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Milliseconds to wait for a cell/row to appear in the DOM after a scroll.
|
|
5
|
+
* Default: 3000.
|
|
6
|
+
*/
|
|
7
|
+
attachTimeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const createGlideViewport: (options?: GlideViewportOptions) => ViewportStrategy;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGlideViewport = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Viewport strategy for Glide Data Grid.
|
|
6
|
+
*
|
|
7
|
+
* Glide renders an ARIA accessibility table (`table[role="grid"]`) alongside its
|
|
8
|
+
* canvas. Rows and cells are virtualized: only the currently visible window of
|
|
9
|
+
* `tbody tr` / `td[aria-colindex]` elements exists in the DOM at any moment.
|
|
10
|
+
*
|
|
11
|
+
* This strategy:
|
|
12
|
+
* - `getVisibleColumnRange` — reads `aria-colindex` values from the first visible row
|
|
13
|
+
* - `getVisibleRowRange` — reads `aria-rowindex` values from visible rows (2-based: header = 1)
|
|
14
|
+
* - `scrollToColumn` — sets `.dvn-scroller.scrollLeft` via ratio derived from header count, waits for cell mount
|
|
15
|
+
* - `scrollToRow` — sets `.dvn-scroller.scrollTop` via measured row height, waits for row mount
|
|
16
|
+
*/
|
|
17
|
+
const sanitize = (value, fallback) => {
|
|
18
|
+
const n = Number(value);
|
|
19
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
20
|
+
};
|
|
21
|
+
const createGlideViewport = (options = {}) => {
|
|
22
|
+
const attachTimeout = sanitize(options.attachTimeout, 3000);
|
|
23
|
+
return {
|
|
24
|
+
getVisibleColumnRange: async ({ page }) => {
|
|
25
|
+
const indices = await page
|
|
26
|
+
.locator('table[role="grid"] tbody tr')
|
|
27
|
+
.first()
|
|
28
|
+
.locator('td[aria-colindex]')
|
|
29
|
+
.evaluateAll((tds) => tds.map((td) => parseInt(td.getAttribute('aria-colindex') || '1') - 1));
|
|
30
|
+
if (!indices.length)
|
|
31
|
+
return { first: 0, last: 0 };
|
|
32
|
+
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
33
|
+
},
|
|
34
|
+
getVisibleRowRange: async ({ page }) => {
|
|
35
|
+
// aria-rowindex is 1-based; header row = 1, so data row 0 → aria-rowindex="2"
|
|
36
|
+
const indices = await page
|
|
37
|
+
.locator('table[role="grid"] tbody tr[aria-rowindex]')
|
|
38
|
+
.evaluateAll((trs) => trs.map((tr) => parseInt(tr.getAttribute('aria-rowindex') || '2') - 2));
|
|
39
|
+
if (!indices.length)
|
|
40
|
+
return { first: 0, last: 0 };
|
|
41
|
+
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
42
|
+
},
|
|
43
|
+
scrollToColumn: async (context, colIndex) => {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
const { page } = context;
|
|
46
|
+
const scroller = page.locator('.dvn-scroller').first();
|
|
47
|
+
// Derive total column count from the initialized header map (available post-init).
|
|
48
|
+
// Falls back to a ratio seek only by position if headers are unavailable.
|
|
49
|
+
const headers = await ((_a = context.getHeaders) === null || _a === void 0 ? void 0 : _a.call(context));
|
|
50
|
+
const totalColumns = (_b = headers === null || headers === void 0 ? void 0 : headers.length) !== null && _b !== void 0 ? _b : 0;
|
|
51
|
+
if (totalColumns > 0) {
|
|
52
|
+
await scroller.evaluate((el, { idx, count }) => {
|
|
53
|
+
const max = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
54
|
+
const ratio = Math.min(1, Math.max(0, (idx + 0.5) / count));
|
|
55
|
+
el.scrollLeft = Math.floor(ratio * max);
|
|
56
|
+
}, { idx: colIndex, count: totalColumns });
|
|
57
|
+
}
|
|
58
|
+
const target = page
|
|
59
|
+
.locator(`table[role="grid"] tbody tr td[aria-colindex="${colIndex + 1}"]`)
|
|
60
|
+
.first();
|
|
61
|
+
// Wait for the cell to attach. The first probe is short (800ms fast path); after
|
|
62
|
+
// any nudge the probe uses the full remaining budget so Glide has time to re-render.
|
|
63
|
+
// Total ceiling = 800ms + attachTimeout, matching the original two-phase behaviour.
|
|
64
|
+
const deadline = Date.now() + 800 + attachTimeout;
|
|
65
|
+
let nudged = false;
|
|
66
|
+
while (true) {
|
|
67
|
+
const remaining = deadline - Date.now();
|
|
68
|
+
if (remaining <= 0)
|
|
69
|
+
throw Object.assign(new Error(`Timed out after ${attachTimeout}ms waiting for aria-colindex="${colIndex + 1}" to attach`), { name: 'TimeoutError' });
|
|
70
|
+
try {
|
|
71
|
+
await target.waitFor({ state: 'attached', timeout: nudged ? remaining : Math.min(800, remaining) });
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (!(err instanceof Error) || err.name !== 'TimeoutError')
|
|
76
|
+
throw err;
|
|
77
|
+
await scroller.evaluate((el) => {
|
|
78
|
+
el.scrollLeft = Math.min(el.scrollLeft + el.clientWidth, el.scrollWidth);
|
|
79
|
+
});
|
|
80
|
+
nudged = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
scrollToRow: async ({ page }, rowIndex) => {
|
|
85
|
+
const scroller = page.locator('.dvn-scroller').first();
|
|
86
|
+
// aria-rowindex is 2-based for data rows (header = 1)
|
|
87
|
+
const ariaRowIndex = rowIndex + 2;
|
|
88
|
+
const rowSel = `table[role="grid"] tbody tr[aria-rowindex="${ariaRowIndex}"]`;
|
|
89
|
+
// If the row is already mounted, scroll it into view directly.
|
|
90
|
+
const isAttached = await page.locator(rowSel).count() > 0;
|
|
91
|
+
if (isAttached) {
|
|
92
|
+
await page.locator(rowSel).first().scrollIntoViewIfNeeded();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Measure actual row height from the DOM; fall back to Glide's default (34px).
|
|
96
|
+
const rowHeight = await page
|
|
97
|
+
.locator('table[role="grid"] tbody tr')
|
|
98
|
+
.first()
|
|
99
|
+
.evaluate((el) => el.getBoundingClientRect().height)
|
|
100
|
+
.catch(() => 34);
|
|
101
|
+
const height = rowHeight > 0 ? rowHeight : 34;
|
|
102
|
+
await scroller.evaluate((el, { idx, height: h }) => {
|
|
103
|
+
const targetTop = idx * h;
|
|
104
|
+
el.scrollTop = Math.max(0, targetTop - Math.floor(el.clientHeight / 2));
|
|
105
|
+
}, { idx: rowIndex, height });
|
|
106
|
+
await page
|
|
107
|
+
.locator(rowSel)
|
|
108
|
+
.waitFor({ state: 'attached', timeout: attachTimeout });
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
exports.createGlideViewport = createGlideViewport;
|
package/dist/presets/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { muiTable, muiDataGrid } from './mui';
|
|
2
2
|
export { RDG as rdg } from './rdg';
|
|
3
|
-
export { Glide as glide } from './glide';
|
|
3
|
+
export { Glide as glide, createGlide, createGlideViewport } from './glide';
|
|
4
|
+
export type { GlideOptions, GlideViewportOptions } from './glide';
|
package/dist/presets/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.glide = exports.rdg = exports.muiDataGrid = exports.muiTable = void 0;
|
|
3
|
+
exports.createGlideViewport = exports.createGlide = exports.glide = exports.rdg = exports.muiDataGrid = exports.muiTable = void 0;
|
|
4
4
|
var mui_1 = require("./mui");
|
|
5
5
|
Object.defineProperty(exports, "muiTable", { enumerable: true, get: function () { return mui_1.muiTable; } });
|
|
6
6
|
Object.defineProperty(exports, "muiDataGrid", { enumerable: true, get: function () { return mui_1.muiDataGrid; } });
|
|
@@ -8,3 +8,5 @@ var rdg_1 = require("./rdg");
|
|
|
8
8
|
Object.defineProperty(exports, "rdg", { enumerable: true, get: function () { return rdg_1.RDG; } });
|
|
9
9
|
var glide_1 = require("./glide");
|
|
10
10
|
Object.defineProperty(exports, "glide", { enumerable: true, get: function () { return glide_1.Glide; } });
|
|
11
|
+
Object.defineProperty(exports, "createGlide", { enumerable: true, get: function () { return glide_1.createGlide; } });
|
|
12
|
+
Object.defineProperty(exports, "createGlideViewport", { enumerable: true, get: function () { return glide_1.createGlideViewport; } });
|
package/dist/presets/mui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.muiDataGrid = exports.muiTable = void 0;
|
|
4
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
4
5
|
/**
|
|
5
6
|
* Safely wait for MUI pagination to stabilize.
|
|
6
7
|
* Polls for displayed row text changes and waits for loading overlays to disappear.
|
|
@@ -86,13 +87,25 @@ exports.muiTable = {
|
|
|
86
87
|
const root = await getPaginationRoot(context);
|
|
87
88
|
const prevBtn = root.locator('button[aria-label="Go to previous page"]');
|
|
88
89
|
const displayedRows = root.locator('.MuiTablePagination-displayedRows');
|
|
89
|
-
let maxRetries = 50;
|
|
90
90
|
let clicked = false;
|
|
91
|
-
|
|
91
|
+
let noProgressCount = 0;
|
|
92
|
+
const NO_PROGRESS_LIMIT = 3;
|
|
93
|
+
while (await prevBtn.count() > 0 && !(await prevBtn.isDisabled())) {
|
|
92
94
|
const oldText = await displayedRows.innerText().catch(() => '');
|
|
93
95
|
await prevBtn.click();
|
|
94
96
|
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
95
97
|
clicked = true;
|
|
98
|
+
const newText = await displayedRows.innerText().catch(() => '');
|
|
99
|
+
if (newText === oldText) {
|
|
100
|
+
noProgressCount++;
|
|
101
|
+
if (noProgressCount >= NO_PROGRESS_LIMIT) {
|
|
102
|
+
(0, debugUtils_1.logDebug)(context.config, 'error', `goToFirst: no pagination progress after ${NO_PROGRESS_LIMIT} consecutive clicks — aborting to avoid infinite loop`);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
noProgressCount = 0;
|
|
108
|
+
}
|
|
96
109
|
}
|
|
97
110
|
return clicked;
|
|
98
111
|
}
|