@rickcedwhat/playwright-smart-table 6.12.0 → 6.14.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 +2 -0
- package/dist/engine/rowFinder.d.ts +0 -1
- package/dist/engine/rowFinder.js +48 -18
- package/dist/engine/tableIteration.d.ts +1 -0
- package/dist/engine/tableIteration.js +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/index.d.ts +14 -9
- package/dist/presets/glide/index.js +24 -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/rdg.js +1 -0
- package/dist/smartRow.js +78 -23
- package/dist/strategies/columns.d.ts +10 -0
- package/dist/strategies/headers.d.ts +1 -1
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +79 -10
- package/dist/types.d.ts +73 -9
- package/dist/useTable.js +35 -6
- package/dist/utils/navigationBarrier.d.ts +2 -0
- package/dist/utils/navigationBarrier.js +4 -0
- package/dist/utils.js +1 -0
- package/package.json +5 -6
- package/dist/presets/glide/columns.d.ts +0 -14
- package/dist/presets/glide/columns.js +0 -53
package/dist/types.d.ts
CHANGED
|
@@ -37,16 +37,45 @@ export type GetCellLocatorFn = (args: {
|
|
|
37
37
|
config: FinalTableConfig<any>;
|
|
38
38
|
}) => Locator;
|
|
39
39
|
/**
|
|
40
|
-
* Hook called before each cell value is read in toJSON (and columnOverrides.read).
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* Hook called before each cell value is read in `toJSON` (and `columnOverrides.read`).
|
|
41
|
+
*
|
|
42
|
+
* Use this to scroll off-screen columns into view in **horizontally** virtualized tables,
|
|
43
|
+
* wait for lazy-rendered content (popovers, tooltips, async cell renderers), or perform
|
|
44
|
+
* any other pre-read setup that doesn't involve Y-axis scrolling.
|
|
45
|
+
*
|
|
46
|
+
* ---
|
|
47
|
+
*
|
|
48
|
+
* **⚠️ Y-scroll footgun — do NOT call `scrollIntoViewIfNeeded()` on row-virtualized grids.**
|
|
49
|
+
*
|
|
50
|
+
* `scrollIntoViewIfNeeded` adjusts *both* scroll axes. On grids that recycle DOM nodes
|
|
51
|
+
* based on the Y position (MUI DataGrid, AG Grid, react-window, etc.) calling it will
|
|
52
|
+
* shift the viewport vertically, unmounting the row you are currently reading and
|
|
53
|
+
* silently returning stale or empty cell values for the remainder of the row.
|
|
54
|
+
*
|
|
55
|
+
* Safe uses:
|
|
56
|
+
* - Column-only (X-axis) virtualization where rows are always in the DOM.
|
|
57
|
+
* - Calling `scrollIntoViewIfNeeded` on the **header cell** only, when that header is
|
|
58
|
+
* guaranteed not to trigger a Y-scroll (e.g. sticky header grids).
|
|
59
|
+
*
|
|
60
|
+
* For grids with **both** row and column virtualization, use the `viewport` strategy
|
|
61
|
+
* instead — it drives explicit `scrollToRow` / `scrollToColumn` calls and is aware of
|
|
62
|
+
* the virtualization lifecycle.
|
|
43
63
|
*
|
|
44
64
|
* @example
|
|
45
|
-
* //
|
|
65
|
+
* // Safe: column-only horizontal virtualization (rows always in DOM)
|
|
46
66
|
* strategies: {
|
|
47
67
|
* beforeCellRead: async ({ columnName, getHeaderCell }) => {
|
|
48
68
|
* const header = await getHeaderCell(columnName);
|
|
49
|
-
* await header.scrollIntoViewIfNeeded();
|
|
69
|
+
* await header.scrollIntoViewIfNeeded(); // only scrolls X — rows stay mounted
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // Safe: wait for a lazy-rendered popover/tooltip before reading its text
|
|
75
|
+
* strategies: {
|
|
76
|
+
* beforeCellRead: async ({ cell }) => {
|
|
77
|
+
* await cell.hover();
|
|
78
|
+
* await cell.page().waitForSelector('.cell-tooltip', { state: 'visible' });
|
|
50
79
|
* }
|
|
51
80
|
* }
|
|
52
81
|
*/
|
|
@@ -401,6 +430,38 @@ export interface LoadingStrategy {
|
|
|
401
430
|
isTableLoading?: (context: TableContext) => Promise<boolean>;
|
|
402
431
|
isRowLoading?: (row: SmartRow) => Promise<boolean>;
|
|
403
432
|
isHeaderLoading?: (context: TableContext) => Promise<boolean>;
|
|
433
|
+
/**
|
|
434
|
+
* How long (ms) to wait for a loading row to resolve before applying onRowLoadingTimeout.
|
|
435
|
+
* When not set, loading rows are immediately skipped (backward-compatible behavior).
|
|
436
|
+
*/
|
|
437
|
+
rowLoadingTimeout?: number;
|
|
438
|
+
/**
|
|
439
|
+
* What to do when a row is still loading after rowLoadingTimeout ms.
|
|
440
|
+
* - 'skip': drop the row from results (matches legacy skip behavior)
|
|
441
|
+
* - 'read-as-is': include the row even though it's still loading (default)
|
|
442
|
+
* - 'throw': throw an error with row index and timeout info
|
|
443
|
+
* Defaults to 'read-as-is' when rowLoadingTimeout is set.
|
|
444
|
+
*/
|
|
445
|
+
onRowLoadingTimeout?: 'skip' | 'read-as-is' | 'throw';
|
|
446
|
+
/**
|
|
447
|
+
* Predicate called before reading each cell. Return true if the cell is still loading.
|
|
448
|
+
* When cellLoadingTimeout is also set, the engine waits up to that many ms for the
|
|
449
|
+
* cell to resolve before applying onCellLoadingTimeout.
|
|
450
|
+
*/
|
|
451
|
+
isCellLoading?: (cell: import('@playwright/test').Locator, columnName: string, row: SmartRow) => Promise<boolean>;
|
|
452
|
+
/**
|
|
453
|
+
* How long (ms) to wait for a loading cell to resolve before applying onCellLoadingTimeout.
|
|
454
|
+
* When not set and isCellLoading returns true, the cell is read as-is immediately.
|
|
455
|
+
*/
|
|
456
|
+
cellLoadingTimeout?: number;
|
|
457
|
+
/**
|
|
458
|
+
* What to do when a cell is still loading after cellLoadingTimeout ms.
|
|
459
|
+
* - 'skip': use empty string for this cell value
|
|
460
|
+
* - 'read-as-is': read the cell content even though it's still loading (default)
|
|
461
|
+
* - 'throw': throw an error with column name, row index and timeout info
|
|
462
|
+
* Defaults to 'read-as-is' when cellLoadingTimeout is set.
|
|
463
|
+
*/
|
|
464
|
+
onCellLoadingTimeout?: 'skip' | 'read-as-is' | 'throw';
|
|
404
465
|
}
|
|
405
466
|
/**
|
|
406
467
|
* Organized container for all table interaction strategies.
|
|
@@ -516,6 +577,8 @@ export type RowIterationContext<T = any> = {
|
|
|
516
577
|
rowIndex: number;
|
|
517
578
|
/** 0-based iteration counter — the order this row was visited, not its DOM position or grid identity. */
|
|
518
579
|
index: number;
|
|
580
|
+
/** 0-based page index — which page this row was collected from. */
|
|
581
|
+
pageIndex: number;
|
|
519
582
|
stop: () => void;
|
|
520
583
|
};
|
|
521
584
|
/** Concurrency modes for row iteration. */
|
|
@@ -547,6 +610,7 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
547
610
|
row: SmartRow<T>;
|
|
548
611
|
rowIndex: number;
|
|
549
612
|
index: number;
|
|
613
|
+
pageIndex: number;
|
|
550
614
|
}> {
|
|
551
615
|
/**
|
|
552
616
|
* Represents the current page index of the table's DOM.
|
|
@@ -559,7 +623,7 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
559
623
|
*/
|
|
560
624
|
init(options?: {
|
|
561
625
|
timeout?: number;
|
|
562
|
-
}): Promise<TableResult
|
|
626
|
+
}): Promise<TableResult<T>>;
|
|
563
627
|
/**
|
|
564
628
|
* SYNC: Checks if the table has been initialized.
|
|
565
629
|
* @returns true if init() has been called and completed, false otherwise
|
|
@@ -575,13 +639,13 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
575
639
|
*/
|
|
576
640
|
getRow: (filters: Record<string, FilterValue>, options?: {
|
|
577
641
|
exact?: boolean;
|
|
578
|
-
}) => SmartRow
|
|
642
|
+
}) => SmartRow<T>;
|
|
579
643
|
/**
|
|
580
644
|
* Gets a row by 0-based index on the current page.
|
|
581
645
|
* Throws error if table is not initialized.
|
|
582
646
|
* @param index 0-based row index
|
|
583
647
|
*/
|
|
584
|
-
getRowByIndex: (index: number) => SmartRow
|
|
648
|
+
getRowByIndex: (index: number) => SmartRow<T>;
|
|
585
649
|
/**
|
|
586
650
|
* ASYNC: Searches for a single row across pages using pagination.
|
|
587
651
|
* Auto-initializes the table if not already initialized.
|
|
@@ -591,7 +655,7 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
591
655
|
findRow: (filters: Record<string, FilterValue>, options?: {
|
|
592
656
|
exact?: boolean;
|
|
593
657
|
maxPages?: number;
|
|
594
|
-
}) => Promise<SmartRow
|
|
658
|
+
}) => Promise<SmartRow<T>>;
|
|
595
659
|
/**
|
|
596
660
|
* ASYNC: Searches for all matching rows across pages using pagination.
|
|
597
661
|
* Auto-initializes the table if not already initialized.
|
package/dist/useTable.js
CHANGED
|
@@ -196,6 +196,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
196
196
|
if (pagesJumped > 0) {
|
|
197
197
|
tableState.currentPageIndex += pagesJumped;
|
|
198
198
|
log(`_advancePage: ${primitive} advanced ${pagesJumped} page(s) — now at page ${tableState.currentPageIndex}`);
|
|
199
|
+
await (0, debugUtils_1.debugDelay)(config, 'pagination');
|
|
199
200
|
}
|
|
200
201
|
else {
|
|
201
202
|
log(`_advancePage: ${primitive} returned false — end of data`);
|
|
@@ -261,10 +262,35 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
261
262
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
262
263
|
},
|
|
263
264
|
countRows: async () => {
|
|
264
|
-
log("countRows: counting rows in current viewport");
|
|
265
265
|
await _autoInit();
|
|
266
|
-
const
|
|
267
|
-
|
|
266
|
+
const pag = config.strategies.pagination;
|
|
267
|
+
const hasPagination = config.maxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
|
|
268
|
+
if (!hasPagination) {
|
|
269
|
+
log("countRows: counting rows in current viewport (no pagination)");
|
|
270
|
+
return resolve(config.rowSelector, rootLocator).count();
|
|
271
|
+
}
|
|
272
|
+
log(`countRows: paginating up to ${config.maxPages} page(s)`);
|
|
273
|
+
const tracker = new elementTracker_1.ElementTracker('countRows');
|
|
274
|
+
let total = 0;
|
|
275
|
+
let pagesScanned = 1;
|
|
276
|
+
try {
|
|
277
|
+
while (true) {
|
|
278
|
+
const newIndices = await tracker.getUnseenIndices(resolve(config.rowSelector, rootLocator));
|
|
279
|
+
total += newIndices.length;
|
|
280
|
+
log(`countRows: page ${pagesScanned} — ${newIndices.length} row(s) (running total: ${total})`);
|
|
281
|
+
if (pagesScanned >= config.maxPages)
|
|
282
|
+
break;
|
|
283
|
+
if (!await _advancePage(false))
|
|
284
|
+
break;
|
|
285
|
+
pagesScanned++;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
await tracker.cleanup(rootLocator.page());
|
|
290
|
+
}
|
|
291
|
+
log(`countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
|
|
292
|
+
await result.reset();
|
|
293
|
+
return total;
|
|
268
294
|
},
|
|
269
295
|
mapColumn: async (columnName, options = {}) => {
|
|
270
296
|
log(`mapColumn: column="${columnName}" options=${safeStringify(options)}`);
|
|
@@ -318,8 +344,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
318
344
|
throw new Error('Initialization Error: You attempted to access a row before the table structure was mapped. Please call "await table.init()" once before using synchronous row access.');
|
|
319
345
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
320
346
|
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page(), rootLocator);
|
|
321
|
-
|
|
322
|
-
return _makeSmart(rowLocator, map, undefined); // sync path cannot compute real index
|
|
347
|
+
return _makeSmart(matchedRows, map, undefined); // sync path cannot compute real index
|
|
323
348
|
},
|
|
324
349
|
getRowByIndex: (index) => {
|
|
325
350
|
log(`getRowByIndex: index=${index}`);
|
|
@@ -393,6 +418,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
393
418
|
const effectiveMaxPages = config.maxPages;
|
|
394
419
|
const tracker = new elementTracker_1.ElementTracker('iterator');
|
|
395
420
|
const useBulk = false; // iterator has no options; default goNext
|
|
421
|
+
log(`iterator: starting (maxPages=${effectiveMaxPages})`);
|
|
396
422
|
try {
|
|
397
423
|
let rowIndex = 0;
|
|
398
424
|
let pagesScanned = 1;
|
|
@@ -402,7 +428,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
402
428
|
const pageRows = yield __await(rowLocators.all());
|
|
403
429
|
const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
|
|
404
430
|
for (const idx of newIndices) {
|
|
405
|
-
yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex,
|
|
431
|
+
yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex, tableState.currentPageIndex, barrier), index: rowIndex, rowIndex, pageIndex: tableState.currentPageIndex });
|
|
406
432
|
rowIndex++;
|
|
407
433
|
}
|
|
408
434
|
if (pagesScanned >= effectiveMaxPages)
|
|
@@ -429,6 +455,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
429
455
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
430
456
|
config,
|
|
431
457
|
getPage: () => rootLocator.page(),
|
|
458
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
432
459
|
}, callback, options);
|
|
433
460
|
},
|
|
434
461
|
map: async (callback, options = {}) => {
|
|
@@ -442,6 +469,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
442
469
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
443
470
|
config,
|
|
444
471
|
getPage: () => rootLocator.page(),
|
|
472
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
445
473
|
}, callback, options);
|
|
446
474
|
},
|
|
447
475
|
filter: async (predicate, options = {}) => {
|
|
@@ -455,6 +483,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
455
483
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
456
484
|
config,
|
|
457
485
|
getPage: () => rootLocator.page(),
|
|
486
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
458
487
|
}, predicate, options);
|
|
459
488
|
},
|
|
460
489
|
generateConfig: async () => {
|
|
@@ -7,6 +7,8 @@ export declare class NavigationBarrier {
|
|
|
7
7
|
private finishedCount;
|
|
8
8
|
private buckets;
|
|
9
9
|
constructor(total: number);
|
|
10
|
+
/** True when more than one row shares this barrier (scrollToRow would evict peers). */
|
|
11
|
+
isParallel(): boolean;
|
|
10
12
|
/**
|
|
11
13
|
* Synchronize all active rows at a specific column index.
|
|
12
14
|
* The last row to arrive will trigger the `moveAction`.
|
|
@@ -12,6 +12,10 @@ class NavigationBarrier {
|
|
|
12
12
|
this.buckets = new Map();
|
|
13
13
|
this.total = total;
|
|
14
14
|
}
|
|
15
|
+
/** True when more than one row shares this barrier (scrollToRow would evict peers). */
|
|
16
|
+
isParallel() {
|
|
17
|
+
return this.total > 1;
|
|
18
|
+
}
|
|
15
19
|
/**
|
|
16
20
|
* Synchronize all active rows at a specific column index.
|
|
17
21
|
* The last row to arrive will trigger the `moveAction`.
|
package/dist/utils.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.waitForCondition = void 0;
|
|
|
6
6
|
* Replaces the dependency on 'expect(...).toPass()' to ensure compatibility
|
|
7
7
|
* with environments where 'expect' is not globally available.
|
|
8
8
|
*/
|
|
9
|
+
// fallow-ignore-next-line unused-export
|
|
9
10
|
const waitForCondition = async (predicate, timeout, page) => {
|
|
10
11
|
const startTime = Date.now();
|
|
11
12
|
while (Date.now() - startTime < timeout) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.14.0",
|
|
4
4
|
"description": "Smart, column-aware table interactions for Playwright",
|
|
5
5
|
"author": "Cedrick Catalan",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"docs:build:all": "vitepress build docs",
|
|
29
29
|
"docs:verify-links": "node scripts/verify-vitepress-links.mjs",
|
|
30
30
|
"embed-version": "node scripts/embed-version.mjs",
|
|
31
|
+
"generate-og": "node scripts/generate-og.mjs",
|
|
31
32
|
"build": "pnpm run embed-version && pnpm run generate-types && pnpm run generate-config-types && pnpm run generate-docs && pnpm run generate-all-api-docs && pnpm run update-all-api-signatures && tsc",
|
|
32
33
|
"prepublishOnly": "pnpm run build",
|
|
33
34
|
"clean-port": "lsof -ti:3000 | xargs kill -9 || true",
|
|
@@ -83,6 +84,8 @@
|
|
|
83
84
|
"@playwright/test": "^1.40.0"
|
|
84
85
|
},
|
|
85
86
|
"devDependencies": {
|
|
87
|
+
"@commitlint/cli": "^21.0.0",
|
|
88
|
+
"@commitlint/config-conventional": "^21.0.0",
|
|
86
89
|
"@playwright/test": "^1.59.1",
|
|
87
90
|
"@stryker-mutator/core": "^9.6.0",
|
|
88
91
|
"@stryker-mutator/vitest-runner": "^9.6.0",
|
|
@@ -90,15 +93,11 @@
|
|
|
90
93
|
"@vitest/coverage-v8": "^3.2.4",
|
|
91
94
|
"@vitest/ui": "^3.2.4",
|
|
92
95
|
"happy-dom": "^20.8.3",
|
|
93
|
-
"@commitlint/cli": "^19.7.1",
|
|
94
|
-
"@commitlint/config-conventional": "^19.7.1",
|
|
95
96
|
"husky": "^9.1.7",
|
|
96
97
|
"typescript": "^6.0.2",
|
|
97
98
|
"vitepress": "^1.6.4",
|
|
99
|
+
"vitepress-plugin-tabs": "^0.9.0",
|
|
98
100
|
"vitest": "^3.2.4"
|
|
99
101
|
},
|
|
100
|
-
"dependencies": {
|
|
101
|
-
"@scarf/scarf": "^1.4.0"
|
|
102
|
-
},
|
|
103
102
|
"packageManager": "pnpm@10.33.2"
|
|
104
103
|
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { StrategyContext } from '../../types';
|
|
2
|
-
/**
|
|
3
|
-
* Primitive navigation functions for Glide Data Grid.
|
|
4
|
-
* These define HOW to move, not WHEN to move.
|
|
5
|
-
* The orchestration logic lives in _navigateToCell.
|
|
6
|
-
*/
|
|
7
|
-
export declare const glideGoUp: (context: StrategyContext) => Promise<void>;
|
|
8
|
-
export declare const glideGoDown: (context: StrategyContext) => Promise<void>;
|
|
9
|
-
export declare const glideGoLeft: (context: StrategyContext) => Promise<void>;
|
|
10
|
-
export declare const glideGoRight: (context: StrategyContext) => Promise<void>;
|
|
11
|
-
/** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
|
|
12
|
-
export declare const glideSeekColumnIndex: (context: StrategyContext, columnIndex: number) => Promise<void>;
|
|
13
|
-
/** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
|
|
14
|
-
export declare const glideSnapFirstColumnIntoView: (context: StrategyContext) => Promise<void>;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.glideSnapFirstColumnIntoView = exports.glideSeekColumnIndex = exports.glideGoRight = exports.glideGoLeft = exports.glideGoDown = exports.glideGoUp = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Primitive navigation functions for Glide Data Grid.
|
|
6
|
-
* These define HOW to move, not WHEN to move.
|
|
7
|
-
* The orchestration logic lives in _navigateToCell.
|
|
8
|
-
*/
|
|
9
|
-
const glideGoUp = async (context) => {
|
|
10
|
-
await context.page.keyboard.press('ArrowUp');
|
|
11
|
-
};
|
|
12
|
-
exports.glideGoUp = glideGoUp;
|
|
13
|
-
const glideGoDown = async (context) => {
|
|
14
|
-
await context.page.keyboard.press('ArrowDown');
|
|
15
|
-
};
|
|
16
|
-
exports.glideGoDown = glideGoDown;
|
|
17
|
-
// Horizontal: use page `.dvn-scroller` (canvas root is not its ancestor). Virtualized rows expose
|
|
18
|
-
// a small window of `td[aria-colindex]` that shifts with scrollLeft.
|
|
19
|
-
const nudgePageScroller = async (page, delta) => {
|
|
20
|
-
const scroller = page.locator('.dvn-scroller').first();
|
|
21
|
-
await scroller.evaluate((el, d) => {
|
|
22
|
-
el.scrollLeft += d;
|
|
23
|
-
}, delta);
|
|
24
|
-
};
|
|
25
|
-
const glideGoLeft = async (context) => {
|
|
26
|
-
await nudgePageScroller(context.page, -400);
|
|
27
|
-
};
|
|
28
|
-
exports.glideGoLeft = glideGoLeft;
|
|
29
|
-
const glideGoRight = async (context) => {
|
|
30
|
-
await nudgePageScroller(context.page, 400);
|
|
31
|
-
};
|
|
32
|
-
exports.glideGoRight = glideGoRight;
|
|
33
|
-
/** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
|
|
34
|
-
const glideSeekColumnIndex = async (context, columnIndex) => {
|
|
35
|
-
const { page } = context;
|
|
36
|
-
const scroller = page.locator('.dvn-scroller').first();
|
|
37
|
-
await scroller.evaluate((el, colIdx) => {
|
|
38
|
-
const max = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
39
|
-
const ratio = Math.min(1, Math.max(0, (colIdx + 0.5) / 64));
|
|
40
|
-
el.scrollLeft = Math.floor(ratio * max);
|
|
41
|
-
}, columnIndex);
|
|
42
|
-
await page.waitForTimeout(100);
|
|
43
|
-
};
|
|
44
|
-
exports.glideSeekColumnIndex = glideSeekColumnIndex;
|
|
45
|
-
/** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
|
|
46
|
-
const glideSnapFirstColumnIntoView = async (context) => {
|
|
47
|
-
const { page } = context;
|
|
48
|
-
await page.locator('.dvn-scroller').first().evaluate((el) => {
|
|
49
|
-
el.scrollLeft = 0;
|
|
50
|
-
});
|
|
51
|
-
await page.waitForTimeout(150);
|
|
52
|
-
};
|
|
53
|
-
exports.glideSnapFirstColumnIntoView = glideSnapFirstColumnIntoView;
|