@rickcedwhat/playwright-smart-table 6.25.0-next.fa652fc → 6.25.0-next.ffb9625
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/cellNavigation.js +7 -4
- package/dist/engine/rowFinder.d.ts +1 -1
- package/dist/engine/rowFinder.js +3 -7
- package/dist/engine/rowResolution.d.ts +2 -2
- package/dist/engine/rowResolution.js +11 -7
- package/dist/engine/rowSnapshot.js +14 -8
- package/dist/engine/tableMapper.js +16 -5
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/types.d.ts +7 -3
- package/dist/useTable.js +0 -50
- package/dist/utils/deadline.d.ts +2 -0
- package/dist/utils/deadline.js +19 -0
- package/dist/utils/memoizeViewport.d.ts +6 -0
- package/dist/utils/memoizeViewport.js +28 -0
- package/dist/utils/smartRowArray.d.ts +1 -1
- package/dist/utils/smartRowArray.js +4 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.navigateToCell = void 0;
|
|
4
4
|
const rowResolution_1 = require("./rowResolution");
|
|
5
5
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
6
|
+
const memoizeViewport_1 = require("../utils/memoizeViewport");
|
|
6
7
|
const log = (config, msg) => (0, debugUtils_1.logDebug)(config, 'verbose', msg);
|
|
7
8
|
/**
|
|
8
9
|
* Navigates to a cell with active cell optimization.
|
|
@@ -49,9 +50,9 @@ const navigateToCell = async (params) => {
|
|
|
49
50
|
};
|
|
50
51
|
// Viewport-oracle phase: direct positioning for 2D virtualized tables.
|
|
51
52
|
// Runs before navigation primitives; falls through to them if cell still not accessible.
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
54
|
-
const viaViewport = await navigateViaViewport(nav, viewport, rowIndex);
|
|
53
|
+
const cache = config.strategies.viewport && (0, memoizeViewport_1.memoizeViewport)(config.strategies.viewport);
|
|
54
|
+
if (cache && typeof rowIndex === 'number') {
|
|
55
|
+
const viaViewport = await navigateViaViewport(nav, cache.viewport, rowIndex, cache.clear);
|
|
55
56
|
if (viaViewport)
|
|
56
57
|
return viaViewport;
|
|
57
58
|
}
|
|
@@ -119,7 +120,7 @@ async function readIfInView(nav, viewport, rowIndex) {
|
|
|
119
120
|
* Viewport phase. Returns the cell locator, or null to fall through to navigation primitives
|
|
120
121
|
* (only when `strategies.navigation` is configured; otherwise throws on a miss).
|
|
121
122
|
*/
|
|
122
|
-
async function navigateViaViewport(nav, viewport, rowIndex) {
|
|
123
|
+
async function navigateViaViewport(nav, viewport, rowIndex, clearCache) {
|
|
123
124
|
const { config, context, index } = nav;
|
|
124
125
|
const inView = await readIfInView(nav, viewport, rowIndex);
|
|
125
126
|
if (inView)
|
|
@@ -135,6 +136,8 @@ async function navigateViaViewport(nav, viewport, rowIndex) {
|
|
|
135
136
|
await viewport.scrollToColumn(context, index);
|
|
136
137
|
}
|
|
137
138
|
});
|
|
139
|
+
// A peer may have performed the coordinated scroll. Drop this row's pre-scroll geometry too.
|
|
140
|
+
clearCache();
|
|
138
141
|
await recoverEvictedRow(nav, viewport, rowIndex);
|
|
139
142
|
if (await nav.targetReached()) {
|
|
140
143
|
return nav.getCellLocator();
|
|
@@ -3,7 +3,7 @@ import { FinalTableConfig, Selector, SmartRow, FilterValue } from '../types';
|
|
|
3
3
|
import { FilterEngine } from '../filterEngine';
|
|
4
4
|
import { TableMapper } from './tableMapper';
|
|
5
5
|
import { SmartRowArray } from '../utils/smartRowArray';
|
|
6
|
-
import { NavigationBarrier } from '../utils/navigationBarrier';
|
|
6
|
+
import type { NavigationBarrier } from '../utils/navigationBarrier';
|
|
7
7
|
export declare class RowFinder<T = any> {
|
|
8
8
|
private rootLocator;
|
|
9
9
|
private config;
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -5,7 +5,6 @@ const debugUtils_1 = require("../utils/debugUtils");
|
|
|
5
5
|
const smartRowArray_1 = require("../utils/smartRowArray");
|
|
6
6
|
const elementTracker_1 = require("../utils/elementTracker");
|
|
7
7
|
const sentinel_1 = require("../utils/sentinel");
|
|
8
|
-
const navigationBarrier_1 = require("../utils/navigationBarrier");
|
|
9
8
|
const rowResolution_1 = require("./rowResolution");
|
|
10
9
|
const resolveCellLocator_1 = require("../utils/resolveCellLocator");
|
|
11
10
|
const scanPages_1 = require("./scanPages");
|
|
@@ -172,26 +171,23 @@ class RowFinder {
|
|
|
172
171
|
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
173
172
|
const currentRows = await rowLocators.all();
|
|
174
173
|
let added = 0;
|
|
175
|
-
const useBarrier = this.config.concurrency === 'synchronized' && newIndices.length > 1;
|
|
176
|
-
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(newIndices.length) : undefined;
|
|
177
174
|
for (const idx of newIndices) {
|
|
178
175
|
const resolved = await (0, rowResolution_1.resolveLogicalRowIndex)(currentRows[idx], this.config, () => allRows.length);
|
|
179
176
|
const rowLocator = this.nameFilteredRow(currentRows[idx], resolved, filtersRecord);
|
|
180
|
-
|
|
177
|
+
// These rows outlive the scan and may be consumed individually. Only
|
|
178
|
+
// iteration engines that own every callback can attach a batch barrier.
|
|
179
|
+
const smartRow = this.makeSmartRow(rowLocator, map, resolved === null || resolved === void 0 ? void 0 : resolved.index, this.tableState.currentPageIndex, undefined, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
|
|
181
180
|
const loadingOutcome = await (0, rowResolution_1.resolveRowLoading)(smartRow, this.config.strategies.loading, 'skip', (msg) => (0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: ${msg}`));
|
|
182
181
|
if (loadingOutcome !== 'process') {
|
|
183
|
-
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
184
182
|
if (loadingOutcome === 'throw') {
|
|
185
183
|
throw new Error(`[SmartTable] Row ${allRows.length} did not finish loading within ${(_a = this.config.strategies.loading) === null || _a === void 0 ? void 0 : _a.rowLoadingTimeout}ms`);
|
|
186
184
|
}
|
|
187
185
|
continue;
|
|
188
186
|
}
|
|
189
187
|
if (hasOverrideFilters && !await this.matchesOverrideFilters(currentRows[idx], overrideFilters, map, (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false)) {
|
|
190
|
-
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
191
188
|
continue;
|
|
192
189
|
}
|
|
193
190
|
if (hasSyntheticFilters && !await this.matchesSyntheticFilters(currentRows[idx], syntheticFilters, map, (_d = options === null || options === void 0 ? void 0 : options.exact) !== null && _d !== void 0 ? _d : false)) {
|
|
194
|
-
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
195
191
|
continue;
|
|
196
192
|
}
|
|
197
193
|
allRows.push(smartRow);
|
|
@@ -29,7 +29,7 @@ export type RowLoadingOutcome = 'process' | 'skip' | 'throw';
|
|
|
29
29
|
* 'skip'`), while `map`/`forEach`/`filter` process it as-is (`'process'`) — and map, in that
|
|
30
30
|
* case, never even probes `isRowLoading`. Both are preserved exactly.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Timeout returns a decision so each caller keeps its own barrier bookkeeping and error
|
|
33
|
+
* message. Predicate errors propagate unchanged.
|
|
34
34
|
*/
|
|
35
35
|
export declare function resolveRowLoading(row: SmartRow, loading: LoadingStrategy | undefined, noTimeoutAction: 'skip' | 'process', log?: (msg: string) => void): Promise<RowLoadingOutcome>;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.normalizeRowIndexResult = normalizeRowIndexResult;
|
|
4
4
|
exports.resolveLogicalRowIndex = resolveLogicalRowIndex;
|
|
5
5
|
exports.resolveRowLoading = resolveRowLoading;
|
|
6
|
+
const deadline_1 = require("../utils/deadline");
|
|
6
7
|
/** Extract the numeric index from a {@link RowIndexResult}. */
|
|
7
8
|
function normalizeRowIndexResult(result) {
|
|
8
9
|
return typeof result === 'number' ? { index: result } : result;
|
|
@@ -36,8 +37,8 @@ async function resolveLogicalRowIndex(row, config, fallback) {
|
|
|
36
37
|
* 'skip'`), while `map`/`forEach`/`filter` process it as-is (`'process'`) — and map, in that
|
|
37
38
|
* case, never even probes `isRowLoading`. Both are preserved exactly.
|
|
38
39
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* Timeout returns a decision so each caller keeps its own barrier bookkeeping and error
|
|
41
|
+
* message. Predicate errors propagate unchanged.
|
|
41
42
|
*/
|
|
42
43
|
async function resolveRowLoading(row, loading, noTimeoutAction, log) {
|
|
43
44
|
var _a;
|
|
@@ -53,18 +54,21 @@ async function resolveRowLoading(row, loading, noTimeoutAction, log) {
|
|
|
53
54
|
// map's fast path: with no timeout and a process-default, it never probes isRowLoading.
|
|
54
55
|
if (timeout === undefined && noTimeoutAction === 'process')
|
|
55
56
|
return 'process';
|
|
56
|
-
|
|
57
|
+
const deadline = Date.now() + (timeout !== null && timeout !== void 0 ? timeout : Infinity);
|
|
58
|
+
const check = () => (0, deadline_1.withinDeadline)(() => isRowLoading(row), deadline, true);
|
|
59
|
+
if (!(await check()))
|
|
57
60
|
return 'process';
|
|
58
61
|
// Row is loading with no timeout configured → legacy skip (findRows).
|
|
59
62
|
if (timeout === undefined)
|
|
60
63
|
return 'skip';
|
|
61
64
|
log === null || log === void 0 ? void 0 : log(`row ${row.rowIndex} — waiting up to ${timeout}ms for row to load`);
|
|
62
|
-
|
|
63
|
-
let resolved = !(await isRowLoading(row)); // immediate check (handles timeout=0)
|
|
65
|
+
let resolved = (timeout === 0 || Date.now() < deadline) && !(await check());
|
|
64
66
|
while (!resolved && Date.now() < deadline) {
|
|
65
67
|
// Page accessed lazily (only while actually polling), matching the pre-refactor code.
|
|
66
|
-
await row.page().waitForTimeout(100);
|
|
67
|
-
|
|
68
|
+
await row.page().waitForTimeout(Math.min(100, deadline - Date.now()));
|
|
69
|
+
if (Date.now() >= deadline)
|
|
70
|
+
break;
|
|
71
|
+
resolved = !(await check());
|
|
68
72
|
}
|
|
69
73
|
if (resolved)
|
|
70
74
|
return 'process';
|
|
@@ -6,6 +6,7 @@ exports.readRowLive = readRowLive;
|
|
|
6
6
|
const rowResolution_1 = require("./rowResolution");
|
|
7
7
|
const cellNavigation_1 = require("./cellNavigation");
|
|
8
8
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
9
|
+
const deadline_1 = require("../utils/deadline");
|
|
9
10
|
/** `getHeaderCell` for strategy hooks: the table's when available, else an index lookup. */
|
|
10
11
|
function headerCellGetter(env) {
|
|
11
12
|
const { table } = env;
|
|
@@ -283,7 +284,9 @@ function cellLoadingTimeoutOf(config) {
|
|
|
283
284
|
async function pollUntil(page, deadline, intervalMs, check, initial) {
|
|
284
285
|
let done = initial;
|
|
285
286
|
while (!done && Date.now() < deadline) {
|
|
286
|
-
await page.waitForTimeout(intervalMs);
|
|
287
|
+
await page.waitForTimeout(Math.min(intervalMs, deadline - Date.now()));
|
|
288
|
+
if (Date.now() >= deadline)
|
|
289
|
+
break;
|
|
287
290
|
done = await check();
|
|
288
291
|
}
|
|
289
292
|
return done;
|
|
@@ -293,11 +296,13 @@ async function pollUntil(page, deadline, intervalMs, check, initial) {
|
|
|
293
296
|
* indicator mounts. A single false-negative then reads "" and skips
|
|
294
297
|
* onCellLoadingTimeout. Only grace when the cell still looks empty.
|
|
295
298
|
*/
|
|
296
|
-
async function graceForLoadingIndicator(page, targetCell, isLoading,
|
|
297
|
-
const peek = await targetCell.evaluate((el) => (el.textContent || '').trim()).catch(() => '');
|
|
299
|
+
async function graceForLoadingIndicator(page, targetCell, isLoading, deadline) {
|
|
300
|
+
const peek = await (0, deadline_1.withinDeadline)(() => targetCell.evaluate((el) => (el.textContent || '').trim()).catch(() => ''), deadline, null);
|
|
301
|
+
if (peek === null)
|
|
302
|
+
return true; // Deadline expired: apply the cell loading timeout policy.
|
|
298
303
|
if (peek !== '')
|
|
299
304
|
return false;
|
|
300
|
-
return pollUntil(page, Date.now() +
|
|
305
|
+
return pollUntil(page, Math.min(Date.now() + 250, deadline), 25, isLoading, false);
|
|
301
306
|
}
|
|
302
307
|
/** What to do when a cell is still loading after `cellLoadingTimeout`. */
|
|
303
308
|
async function onStillLoading(env, targetCell, col, timeout) {
|
|
@@ -328,18 +333,19 @@ async function waitForCellLoaded(env, targetCell, col) {
|
|
|
328
333
|
if (!isCellLoading)
|
|
329
334
|
return { read: true };
|
|
330
335
|
const page = env.rootLocator.page();
|
|
331
|
-
const isLoading = () => isCellLoading(targetCell, col, smart);
|
|
332
336
|
const timeout = cellLoadingTimeoutOf(config);
|
|
337
|
+
const deadline = Date.now() + (timeout !== null && timeout !== void 0 ? timeout : Infinity);
|
|
338
|
+
const isLoading = () => (0, deadline_1.withinDeadline)(() => isCellLoading(targetCell, col, smart), deadline, true);
|
|
333
339
|
let loading = await isLoading();
|
|
334
340
|
if (!loading && timeout !== undefined && timeout > 0) {
|
|
335
|
-
loading = await graceForLoadingIndicator(page, targetCell, isLoading,
|
|
341
|
+
loading = await graceForLoadingIndicator(page, targetCell, isLoading, deadline);
|
|
336
342
|
}
|
|
337
343
|
if (!loading || timeout === undefined)
|
|
338
344
|
return { read: true };
|
|
339
345
|
(0, debugUtils_1.logDebug)(config, 'verbose', `toJSON: cell "${col}" — waiting up to ${timeout}ms`);
|
|
340
|
-
const deadline = Date.now() + timeout;
|
|
341
346
|
const isLoaded = async () => !(await isLoading());
|
|
342
|
-
const
|
|
347
|
+
const initial = (timeout === 0 || Date.now() < deadline) && await isLoaded();
|
|
348
|
+
const cellResolved = await pollUntil(page, deadline, 100, isLoaded, initial);
|
|
343
349
|
return cellResolved ? { read: true } : onStillLoading(env, targetCell, col, timeout);
|
|
344
350
|
}
|
|
345
351
|
/**
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TableMapper = void 0;
|
|
4
4
|
const headers_1 = require("../strategies/headers");
|
|
5
5
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
6
|
+
const deadline_1 = require("../utils/deadline");
|
|
6
7
|
class TableMapper {
|
|
7
8
|
constructor(rootLocator, config, resolve) {
|
|
8
9
|
this._headerMap = null;
|
|
@@ -21,9 +22,11 @@ class TableMapper {
|
|
|
21
22
|
this.log('Mapping headers...');
|
|
22
23
|
const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
|
|
23
24
|
const startTime = Date.now();
|
|
25
|
+
const deadline = startTime + headerTimeout;
|
|
26
|
+
const remaining = () => Math.max(0, deadline - Date.now());
|
|
24
27
|
if (this.config.autoScroll) {
|
|
25
28
|
try {
|
|
26
|
-
await this.rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
|
|
29
|
+
await (0, deadline_1.withinDeadline)(() => this.rootLocator.scrollIntoViewIfNeeded({ timeout: Math.max(1, Math.min(1000, remaining())) }), deadline, undefined);
|
|
27
30
|
}
|
|
28
31
|
catch (e) { }
|
|
29
32
|
}
|
|
@@ -39,22 +42,29 @@ class TableMapper {
|
|
|
39
42
|
while (Date.now() - startTime < headerTimeout) {
|
|
40
43
|
// 1. Wait for visibility
|
|
41
44
|
try {
|
|
42
|
-
await headerLoc.first().waitFor({ state: 'visible', timeout: 200 });
|
|
45
|
+
await (0, deadline_1.withinDeadline)(() => headerLoc.first().waitFor({ state: 'visible', timeout: Math.max(1, Math.min(200, remaining())) }), deadline, undefined);
|
|
43
46
|
}
|
|
44
47
|
catch (e) {
|
|
45
48
|
// Continue to check existing/loading state even if not strictly "visible" yet
|
|
46
49
|
}
|
|
50
|
+
if (remaining() <= 0)
|
|
51
|
+
break;
|
|
47
52
|
// 2. Check Smart Loading State
|
|
48
53
|
if ((_a = this.config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isHeaderLoading) {
|
|
49
|
-
const isStable = !(await this.config.strategies.loading.isHeaderLoading(context));
|
|
54
|
+
const isStable = !(await (0, deadline_1.withinDeadline)(() => this.config.strategies.loading.isHeaderLoading(context), deadline, true));
|
|
50
55
|
if (!isStable) {
|
|
51
56
|
this.log('Headers are loading/unstable... waiting');
|
|
52
|
-
|
|
57
|
+
if (remaining() > 0)
|
|
58
|
+
await new Promise(r => setTimeout(r, Math.min(100, remaining())));
|
|
53
59
|
continue;
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
// 3. Attempt Scan
|
|
63
|
+
if (remaining() <= 0)
|
|
64
|
+
break;
|
|
57
65
|
try {
|
|
66
|
+
// Preserve long-running discovery strategies (e.g. horizontal header scans).
|
|
67
|
+
// The deadline bounds readiness and retries, not a strategy's own operation.
|
|
58
68
|
const rawHeaders = await strategy(context);
|
|
59
69
|
const entries = await this.processHeaders(rawHeaders);
|
|
60
70
|
// Success — validate before caching so a collision doesn't leave stale state
|
|
@@ -72,7 +82,8 @@ class TableMapper {
|
|
|
72
82
|
catch (e) {
|
|
73
83
|
lastError = e;
|
|
74
84
|
this.log(`Header mapping failed. Attempt ${Math.floor((Date.now() - startTime) / 100)}: ${e.message}`);
|
|
75
|
-
|
|
85
|
+
if (remaining() > 0)
|
|
86
|
+
await new Promise(r => setTimeout(r, Math.min(100, remaining())));
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
89
|
throw lastError || new Error(`Timed out waiting for headers after ${headerTimeout}ms`);
|
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.25.0-next.
|
|
6
|
+
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.25.0-next.ffb9625";
|
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.25.0-next.
|
|
9
|
+
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.25.0-next.ffb9625";
|
package/dist/types.d.ts
CHANGED
|
@@ -210,9 +210,9 @@ export interface ViewportStrategy {
|
|
|
210
210
|
scrollToColumn?: (context: TableContext, colIndex: number) => Promise<void>;
|
|
211
211
|
/**
|
|
212
212
|
* When true, disables the automatic memoization of getVisibleColumnRange and
|
|
213
|
-
* getVisibleRowRange results
|
|
214
|
-
*
|
|
215
|
-
*
|
|
213
|
+
* getVisibleRowRange results within a single cell-navigation operation. Geometry
|
|
214
|
+
* is refreshed for each new read and around scroll calls. Row membership is not
|
|
215
|
+
* memoized by the library. Caches never persist across scans, resets, or revalidation.
|
|
216
216
|
*/
|
|
217
217
|
disableCache?: boolean;
|
|
218
218
|
}
|
|
@@ -526,6 +526,7 @@ export interface LoadingStrategy {
|
|
|
526
526
|
* How long (ms) to wait for a loading row to resolve before applying onRowLoadingTimeout.
|
|
527
527
|
* Honored by findRows AND by map/forEach/filter, where the wait runs before the dedupe
|
|
528
528
|
* strategy so content-based dedupe keys see the row's final loaded state.
|
|
529
|
+
* Includes time spent awaiting the loading predicate, even if it never settles.
|
|
529
530
|
* When not set (backward-compatible behavior): findRows skips loading rows immediately;
|
|
530
531
|
* map/forEach/filter process them as-is.
|
|
531
532
|
*/
|
|
@@ -546,6 +547,7 @@ export interface LoadingStrategy {
|
|
|
546
547
|
isCellLoading?: (cell: import('@playwright/test').Locator, columnName: string, row: SmartRow) => Promise<boolean>;
|
|
547
548
|
/**
|
|
548
549
|
* How long (ms) to wait for a loading cell to resolve before applying onCellLoadingTimeout.
|
|
550
|
+
* Includes predicate evaluation and any initial rendering grace period.
|
|
549
551
|
* When not set and isCellLoading returns true, the cell is read as-is immediately.
|
|
550
552
|
*/
|
|
551
553
|
cellLoadingTimeout?: number;
|
|
@@ -910,6 +912,8 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
910
912
|
/**
|
|
911
913
|
* ASYNC: Searches for all matching rows across pages using pagination.
|
|
912
914
|
* Auto-initializes the table if not already initialized.
|
|
915
|
+
* Returned rows can be read independently, including with `concurrency: 'synchronized'`.
|
|
916
|
+
* Use synchronized `map()` for coordinated column scrolling across virtualized rows.
|
|
913
917
|
* @param filters - The filter criteria to match (omit or pass {} for all rows)
|
|
914
918
|
* @param options - Search options (exact, maxPages). `useBulkPagination` defaults to `false`: pages advance one at a time via `goNext` so no intermediate page is skipped. Set it to `true` to opt into `goNextBulk` (faster, but skips the rows on jumped-over pages). `signal` cancels the page scan; the call rejects with `signal.reason`.
|
|
915
919
|
*/
|
package/dist/useTable.js
CHANGED
|
@@ -53,52 +53,6 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text }) => text, autoScroll: true, onReset: async () => { } }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
|
|
56
|
-
// Automatically memoize viewport range oracles so user-provided functions are
|
|
57
|
-
// pure DOM queries with no cache bookkeeping. Invalidated after each scroll call.
|
|
58
|
-
let _clearViewportCache = null;
|
|
59
|
-
if (config.strategies.viewport && !config.strategies.viewport.disableCache) {
|
|
60
|
-
const vp = config.strategies.viewport;
|
|
61
|
-
let colRangeCache = null;
|
|
62
|
-
let rowRangeCache = null;
|
|
63
|
-
let rowIndicesCache = null;
|
|
64
|
-
_clearViewportCache = () => {
|
|
65
|
-
colRangeCache = null;
|
|
66
|
-
rowRangeCache = null;
|
|
67
|
-
rowIndicesCache = null;
|
|
68
|
-
};
|
|
69
|
-
config.strategies.viewport = Object.assign(Object.assign({}, vp), { getVisibleColumnRange: vp.getVisibleColumnRange
|
|
70
|
-
? async (ctx) => {
|
|
71
|
-
if (!colRangeCache)
|
|
72
|
-
colRangeCache = await vp.getVisibleColumnRange(ctx);
|
|
73
|
-
return colRangeCache;
|
|
74
|
-
}
|
|
75
|
-
: undefined, getVisibleRowRange: vp.getVisibleRowRange
|
|
76
|
-
? async (ctx) => {
|
|
77
|
-
if (!rowRangeCache)
|
|
78
|
-
rowRangeCache = await vp.getVisibleRowRange(ctx);
|
|
79
|
-
return rowRangeCache;
|
|
80
|
-
}
|
|
81
|
-
: undefined, getVisibleRowIndices: vp.getVisibleRowIndices
|
|
82
|
-
? async (ctx) => {
|
|
83
|
-
if (!rowIndicesCache)
|
|
84
|
-
rowIndicesCache = await vp.getVisibleRowIndices(ctx);
|
|
85
|
-
return rowIndicesCache;
|
|
86
|
-
}
|
|
87
|
-
: undefined, scrollToColumn: vp.scrollToColumn
|
|
88
|
-
? async (ctx, colIndex) => {
|
|
89
|
-
colRangeCache = null;
|
|
90
|
-
rowRangeCache = null;
|
|
91
|
-
rowIndicesCache = null;
|
|
92
|
-
await vp.scrollToColumn(ctx, colIndex);
|
|
93
|
-
}
|
|
94
|
-
: undefined, scrollToRow: vp.scrollToRow
|
|
95
|
-
? async (ctx, rowIndex) => {
|
|
96
|
-
rowRangeCache = null;
|
|
97
|
-
rowIndicesCache = null;
|
|
98
|
-
await vp.scrollToRow(ctx, rowIndex);
|
|
99
|
-
}
|
|
100
|
-
: undefined });
|
|
101
|
-
}
|
|
102
56
|
const resolve = (item, parent) => {
|
|
103
57
|
if (typeof item === 'string')
|
|
104
58
|
return parent.locator(item);
|
|
@@ -213,10 +167,6 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
213
167
|
if (pagesJumped > 0) {
|
|
214
168
|
tableState.currentPageIndex += pagesJumped;
|
|
215
169
|
log(`_advancePage: ${primitive} advanced ${pagesJumped} page(s) — now at page ${tableState.currentPageIndex}`);
|
|
216
|
-
// Invalidate viewport caches: after a page advance the row set changes and the
|
|
217
|
-
// column renderer may recalculate (e.g. scrollbar appearance changes available width).
|
|
218
|
-
if (_clearViewportCache)
|
|
219
|
-
_clearViewportCache();
|
|
220
170
|
await (0, debugUtils_1.debugDelay)(config, 'pagination');
|
|
221
171
|
}
|
|
222
172
|
else {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withinDeadline = withinDeadline;
|
|
4
|
+
/** Bound one asynchronous check by an existing deadline, without cancelling its underlying work. */
|
|
5
|
+
async function withinDeadline(check, deadline, expiredValue) {
|
|
6
|
+
if (!Number.isFinite(deadline))
|
|
7
|
+
return check();
|
|
8
|
+
let timer;
|
|
9
|
+
try {
|
|
10
|
+
const expired = new Promise(resolve => {
|
|
11
|
+
timer = setTimeout(() => resolve(expiredValue), Math.max(0, deadline - Date.now()));
|
|
12
|
+
});
|
|
13
|
+
// A late rejection is handled by the race even after the deadline wins.
|
|
14
|
+
return await Promise.race([check(), expired]);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ViewportStrategy } from '../types';
|
|
2
|
+
/** Cache geometry only for one navigation; scans and later reads use the original oracles. */
|
|
3
|
+
export declare function memoizeViewport(viewport: ViewportStrategy): {
|
|
4
|
+
viewport: ViewportStrategy;
|
|
5
|
+
clear: () => void;
|
|
6
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoizeViewport = memoizeViewport;
|
|
4
|
+
/** Cache geometry only for one navigation; scans and later reads use the original oracles. */
|
|
5
|
+
function memoizeViewport(viewport) {
|
|
6
|
+
if (viewport.disableCache)
|
|
7
|
+
return { viewport, clear: () => { } };
|
|
8
|
+
let columns;
|
|
9
|
+
let rows;
|
|
10
|
+
const clear = () => { columns = undefined; rows = undefined; };
|
|
11
|
+
const wrapScroll = (scroll) => scroll && (async (context, index) => {
|
|
12
|
+
clear();
|
|
13
|
+
try {
|
|
14
|
+
await scroll.call(viewport, context, index);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
clear();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
clear,
|
|
22
|
+
viewport: Object.assign(Object.assign({}, viewport), { getVisibleColumnRange: viewport.getVisibleColumnRange
|
|
23
|
+
? context => columns !== null && columns !== void 0 ? columns : (columns = viewport.getVisibleColumnRange(context))
|
|
24
|
+
: undefined, getVisibleRowRange: viewport.getVisibleRowRange
|
|
25
|
+
? context => rows !== null && rows !== void 0 ? rows : (rows = viewport.getVisibleRowRange(context))
|
|
26
|
+
: undefined, scrollToColumn: wrapScroll(viewport.scrollToColumn), scrollToRow: wrapScroll(viewport.scrollToRow) }),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -4,7 +4,7 @@ import { SmartRow } from '../types';
|
|
|
4
4
|
*/
|
|
5
5
|
export interface SmartRowArray<T = any> extends Array<SmartRow<T>> {
|
|
6
6
|
/**
|
|
7
|
-
* Converts all rows to JSON objects
|
|
7
|
+
* Converts all rows to JSON objects, reading sequentially to avoid competing viewport moves.
|
|
8
8
|
*/
|
|
9
9
|
toJSON(): Promise<T[]>;
|
|
10
10
|
}
|
|
@@ -7,7 +7,10 @@ exports.createSmartRowArray = createSmartRowArray;
|
|
|
7
7
|
function createSmartRowArray(rows) {
|
|
8
8
|
const arr = rows;
|
|
9
9
|
arr.toJSON = async () => {
|
|
10
|
-
|
|
10
|
+
const results = [];
|
|
11
|
+
for (const row of rows)
|
|
12
|
+
results.push(await row.toJSON());
|
|
13
|
+
return results;
|
|
11
14
|
};
|
|
12
15
|
return arr;
|
|
13
16
|
}
|
package/package.json
CHANGED