@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/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@rickcedwhat/playwright-smart-table)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://coderabbit.ai)
|
|
7
8
|
|
|
8
9
|
## 📚 [Full Documentation →](https://rickcedwhat.github.io/playwright-smart-table/)
|
|
9
10
|
|
|
@@ -45,6 +46,7 @@ await table.forEach(async ({ row }) => {
|
|
|
45
46
|
npm install @rickcedwhat/playwright-smart-table
|
|
46
47
|
```
|
|
47
48
|
|
|
49
|
+
|
|
48
50
|
## Quick Start
|
|
49
51
|
|
|
50
52
|
```typescript
|
|
@@ -14,7 +14,6 @@ export declare class RowFinder<T = any> {
|
|
|
14
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;
|
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();
|
|
@@ -45,11 +42,11 @@ class RowFinder {
|
|
|
45
42
|
const allRows = [];
|
|
46
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;
|
|
47
44
|
let pagesScanned = 1;
|
|
48
|
-
this.
|
|
45
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: starting (maxPages=${effectiveMaxPages}, filters=${JSON.stringify(filtersRecord)})`);
|
|
49
46
|
const tracker = new elementTracker_1.ElementTracker('findRows');
|
|
50
47
|
try {
|
|
51
48
|
const collectMatches = async () => {
|
|
52
|
-
var _a, _b;
|
|
49
|
+
var _a, _b, _c, _d, _e;
|
|
53
50
|
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
54
51
|
// Only apply filters if we have them
|
|
55
52
|
if (Object.keys(filtersRecord).length > 0) {
|
|
@@ -59,17 +56,47 @@ class RowFinder {
|
|
|
59
56
|
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
60
57
|
const currentRows = await rowLocators.all();
|
|
61
58
|
const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
|
|
59
|
+
const rawRowTimeout = (_c = this.config.strategies.loading) === null || _c === void 0 ? void 0 : _c.rowLoadingTimeout;
|
|
60
|
+
// undefined = unset (legacy skip); 0 = no wait (immediate check); >0 = wait up to N ms
|
|
61
|
+
// Non-finite values are treated as unset (defensive guard against Infinity/NaN)
|
|
62
|
+
const rowLoadingTimeout = rawRowTimeout !== undefined && Number.isFinite(rawRowTimeout) && rawRowTimeout >= 0
|
|
63
|
+
? rawRowTimeout
|
|
64
|
+
: undefined;
|
|
65
|
+
const onRowLoadingTimeout = (_e = (_d = this.config.strategies.loading) === null || _d === void 0 ? void 0 : _d.onRowLoadingTimeout) !== null && _e !== void 0 ? _e : 'read-as-is';
|
|
62
66
|
let added = 0;
|
|
63
67
|
for (const idx of newIndices) {
|
|
64
68
|
const smartRow = this.makeSmartRow(currentRows[idx], map, allRows.length, this.tableState.currentPageIndex);
|
|
65
69
|
if (isRowLoading && await isRowLoading(smartRow)) {
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
if (rowLoadingTimeout === undefined) {
|
|
71
|
+
// No timeout configured (or invalid value) — legacy skip behavior
|
|
72
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: page ${this.tableState.currentPageIndex} — row skipped (isRowLoading=true, no timeout)`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// Wait up to rowLoadingTimeout ms (0 = one immediate re-check, no polling)
|
|
76
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: row ${allRows.length} — waiting up to ${rowLoadingTimeout}ms for row to load`);
|
|
77
|
+
const deadline = Date.now() + rowLoadingTimeout;
|
|
78
|
+
let resolved = !(await isRowLoading(smartRow)); // immediate check (handles timeout=0)
|
|
79
|
+
while (!resolved && Date.now() < deadline) {
|
|
80
|
+
await currentRows[idx].page().waitForTimeout(100);
|
|
81
|
+
resolved = !(await isRowLoading(smartRow));
|
|
82
|
+
}
|
|
83
|
+
if (!resolved) {
|
|
84
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: row ${allRows.length} — still loading after ${rowLoadingTimeout}ms, action: ${onRowLoadingTimeout}`);
|
|
85
|
+
if (onRowLoadingTimeout === 'skip')
|
|
86
|
+
continue;
|
|
87
|
+
if (onRowLoadingTimeout === 'throw') {
|
|
88
|
+
throw new Error(`[SmartTable] Row ${allRows.length} did not finish loading within ${rowLoadingTimeout}ms`);
|
|
89
|
+
}
|
|
90
|
+
// 'read-as-is': fall through and add the row
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: row ${allRows.length} — finished loading`);
|
|
94
|
+
}
|
|
68
95
|
}
|
|
69
96
|
allRows.push(smartRow);
|
|
70
97
|
added++;
|
|
71
98
|
}
|
|
72
|
-
this.
|
|
99
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
73
100
|
};
|
|
74
101
|
// Scan first page
|
|
75
102
|
await collectMatches();
|
|
@@ -90,25 +117,26 @@ class RowFinder {
|
|
|
90
117
|
paginationResult = await this.config.strategies.pagination.goNext(context);
|
|
91
118
|
}
|
|
92
119
|
else {
|
|
93
|
-
this.
|
|
120
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: no pagination primitive — stopping`);
|
|
94
121
|
break;
|
|
95
122
|
}
|
|
96
123
|
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
97
124
|
if (!didPaginate) {
|
|
98
|
-
this.
|
|
125
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: pagination returned false — end of data`);
|
|
99
126
|
break;
|
|
100
127
|
}
|
|
101
128
|
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
102
129
|
this.tableState.currentPageIndex += pagesJumped;
|
|
103
130
|
pagesScanned += pagesJumped;
|
|
104
|
-
this.
|
|
131
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
132
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'pagination');
|
|
105
133
|
await collectMatches();
|
|
106
134
|
}
|
|
107
135
|
}
|
|
108
136
|
finally {
|
|
109
137
|
await tracker.cleanup(this.rootLocator.page());
|
|
110
138
|
}
|
|
111
|
-
this.
|
|
139
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
|
|
112
140
|
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
113
141
|
}
|
|
114
142
|
async findRowLocator(filters, options = {}) {
|
|
@@ -116,7 +144,7 @@ class RowFinder {
|
|
|
116
144
|
const map = await this.tableMapper.getMap();
|
|
117
145
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
|
|
118
146
|
let pagesScanned = 1;
|
|
119
|
-
this.
|
|
147
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
120
148
|
while (true) {
|
|
121
149
|
// Check Loading
|
|
122
150
|
if ((_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
|
|
@@ -127,7 +155,7 @@ class RowFinder {
|
|
|
127
155
|
resolve: this.resolve
|
|
128
156
|
});
|
|
129
157
|
if (isLoading) {
|
|
130
|
-
this.
|
|
158
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', 'Table is loading... waiting');
|
|
131
159
|
await this.rootLocator.page().waitForTimeout(200);
|
|
132
160
|
continue;
|
|
133
161
|
}
|
|
@@ -135,7 +163,7 @@ class RowFinder {
|
|
|
135
163
|
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
136
164
|
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
|
|
137
165
|
const count = await matchedRows.count();
|
|
138
|
-
this.
|
|
166
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
|
|
139
167
|
if (count > 1) {
|
|
140
168
|
const sampleData = [];
|
|
141
169
|
try {
|
|
@@ -154,7 +182,7 @@ class RowFinder {
|
|
|
154
182
|
if (count === 1)
|
|
155
183
|
return matchedRows.first();
|
|
156
184
|
if (pagesScanned < effectiveMaxPages) {
|
|
157
|
-
this.
|
|
185
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Not found. Attempting pagination...`);
|
|
158
186
|
const context = {
|
|
159
187
|
root: this.rootLocator,
|
|
160
188
|
config: this.config,
|
|
@@ -171,7 +199,7 @@ class RowFinder {
|
|
|
171
199
|
paginationResult = await pagination.goNext(context);
|
|
172
200
|
}
|
|
173
201
|
else {
|
|
174
|
-
this.
|
|
202
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Pagination failed (no goNext or goNextBulk primitive).`);
|
|
175
203
|
return null;
|
|
176
204
|
}
|
|
177
205
|
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
@@ -179,10 +207,12 @@ class RowFinder {
|
|
|
179
207
|
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
180
208
|
this.tableState.currentPageIndex += pagesJumped;
|
|
181
209
|
pagesScanned += pagesJumped;
|
|
210
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRowLocator: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
211
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'pagination');
|
|
182
212
|
continue;
|
|
183
213
|
}
|
|
184
214
|
else {
|
|
185
|
-
this.
|
|
215
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Pagination failed (end of data).`);
|
|
186
216
|
}
|
|
187
217
|
}
|
|
188
218
|
return null;
|
|
@@ -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).
|
|
@@ -67,7 +67,7 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
67
67
|
log(env.config, `${label}: scanning page ${pagesScanned} — ${batchSize} new row(s)`);
|
|
68
68
|
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(batchSize) : undefined;
|
|
69
69
|
const actionMutex = useMutex ? new mutex_1.Mutex() : null;
|
|
70
|
-
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));
|
|
71
71
|
const processRow = async (row) => {
|
|
72
72
|
try {
|
|
73
73
|
if (stopped && row.rowIndex > stoppedIndex) {
|
|
@@ -86,7 +86,7 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
86
86
|
if (stopped && row.rowIndex > stoppedIndex)
|
|
87
87
|
return SKIP;
|
|
88
88
|
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
89
|
-
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) });
|
|
90
90
|
};
|
|
91
91
|
if (actionMutex) {
|
|
92
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.14.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.14.0";
|
|
@@ -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
|
},
|
|
@@ -102,11 +97,29 @@ const GlideDefaultStrategies = {
|
|
|
102
97
|
getActiveCell: glideGetActiveCell
|
|
103
98
|
};
|
|
104
99
|
/** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
|
|
100
|
+
// fallow-ignore-next-line unused-export
|
|
105
101
|
exports.GlideStrategies = Object.assign(Object.assign({}, GlideDefaultStrategies), { fillSimple: glideFillSimple });
|
|
102
|
+
/**
|
|
103
|
+
* Factory that returns a configured Glide preset.
|
|
104
|
+
* Use when your grid differs from the defaults:
|
|
105
|
+
* ```ts
|
|
106
|
+
* useTable(loc, { ...createGlide({ attachTimeout: 5000 }), maxPages: 5 })
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
function createGlide(options = {}) {
|
|
110
|
+
return {
|
|
111
|
+
headerSelector: 'table[role="grid"] thead tr th',
|
|
112
|
+
rowSelector: 'table[role="grid"] tbody tr',
|
|
113
|
+
cellSelector: 'td',
|
|
114
|
+
concurrency: 'sequential',
|
|
115
|
+
strategies: Object.assign(Object.assign({}, GlideDefaultStrategies), { viewport: (0, viewport_2.createGlideViewport)(options) })
|
|
116
|
+
};
|
|
117
|
+
}
|
|
106
118
|
/**
|
|
107
119
|
* Full preset for Glide Data Grid (selectors + default strategies only).
|
|
108
120
|
* Spread: useTable(loc, { ...Plugins.Glide, maxPages: 5 }).
|
|
109
121
|
* Strategies only (including fillSimple): useTable(loc, { rowSelector: '...', strategies: Plugins.Glide.Strategies }).
|
|
122
|
+
* For a non-64-column grid, use `createGlide({ columnCount })` instead.
|
|
110
123
|
*/
|
|
111
124
|
const GlidePreset = {
|
|
112
125
|
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/rdg.js
CHANGED
|
@@ -100,6 +100,7 @@ const RDGDefaultStrategies = {
|
|
|
100
100
|
pagination: rdgPaginationStrategy
|
|
101
101
|
};
|
|
102
102
|
/** Full strategies for React Data Grid. Use when you want to supply your own selectors: strategies: Plugins.RDG.Strategies */
|
|
103
|
+
// fallow-ignore-next-line unused-export
|
|
103
104
|
exports.RDGStrategies = RDGDefaultStrategies;
|
|
104
105
|
/**
|
|
105
106
|
* Full preset for React Data Grid (selectors + default strategies).
|