@rickcedwhat/playwright-smart-table 6.9.0 → 6.10.1
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 +9 -6
- package/dist/engine/rowFinder.js +153 -168
- package/dist/engine/tableIteration.js +105 -120
- package/dist/engine/tableMapper.js +88 -103
- package/dist/minimalConfigContext.d.ts +1 -1
- package/dist/minimalConfigContext.js +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/columns.js +23 -32
- package/dist/presets/glide/headers.js +18 -27
- package/dist/presets/glide/index.d.ts +9 -9
- package/dist/presets/glide/index.js +24 -33
- package/dist/presets/mui.js +197 -124
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +32 -41
- package/dist/smartRow.js +209 -101
- package/dist/strategies/columns.js +2 -11
- package/dist/strategies/dedupe.js +3 -12
- package/dist/strategies/fill.js +20 -29
- package/dist/strategies/headers.js +22 -31
- package/dist/strategies/index.d.ts +7 -0
- package/dist/strategies/index.js +3 -0
- package/dist/strategies/loading.js +24 -33
- package/dist/strategies/pagination.d.ts +10 -0
- package/dist/strategies/pagination.js +49 -30
- package/dist/strategies/sorting.js +18 -31
- package/dist/strategies/stabilization.js +28 -37
- package/dist/strategies/viewport.d.ts +52 -0
- package/dist/strategies/viewport.js +123 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +160 -5
- package/dist/types.d.ts +153 -5
- package/dist/useTable.js +142 -76
- package/dist/utils/debugUtils.js +5 -16
- package/dist/utils/elementTracker.js +27 -40
- package/dist/utils/mutex.js +5 -16
- package/dist/utils/navigationBarrier.js +25 -36
- package/dist/utils/paginationPath.d.ts +4 -1
- package/dist/utils/paginationPath.js +164 -141
- package/dist/utils/smartRowArray.js +2 -11
- package/dist/utils.js +4 -13
- package/package.json +13 -8
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.runForEach = runForEach;
|
|
13
4
|
exports.runMap = runMap;
|
|
@@ -21,134 +12,128 @@ function log(config, msg) {
|
|
|
21
12
|
}
|
|
22
13
|
const SKIP = Symbol('skip');
|
|
23
14
|
/** Delegates to {@link runMap}; void results are discarded. */
|
|
24
|
-
function runForEach(
|
|
25
|
-
|
|
26
|
-
yield runMap(env, callback, options, 'forEach');
|
|
27
|
-
});
|
|
15
|
+
async function runForEach(env, callback, options = {}) {
|
|
16
|
+
await runMap(env, callback, options, 'forEach');
|
|
28
17
|
}
|
|
29
18
|
/**
|
|
30
19
|
* Row iteration for map (and forEach/filter via label).
|
|
31
20
|
* Concurrency: `parallel` | `synchronized` | `sequential` (see RowIterationOptions).
|
|
32
21
|
*/
|
|
33
|
-
function runMap(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
22
|
+
async function runMap(env, callback, options = {}, label = 'map') {
|
|
23
|
+
var _a, _b, _c, _d, _e;
|
|
24
|
+
const map = env.getMap();
|
|
25
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
26
|
+
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
27
|
+
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
28
|
+
const defaultMode = label === 'map' ? 'parallel' : 'sequential';
|
|
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 !== 'sequential';
|
|
31
|
+
// Mutex must not pair with the navigation barrier: synchronized mode needs every row
|
|
32
|
+
// to enter barrier.sync concurrently; serializing callbacks here deadlocks (first row waits
|
|
33
|
+
// for batchSize peers that never reach the barrier).
|
|
34
|
+
const useMutex = concurrency === 'sequential';
|
|
35
|
+
const useBulk = (_e = options.useBulkPagination) !== null && _e !== void 0 ? _e : false;
|
|
36
|
+
const tracker = new elementTracker_1.ElementTracker(label);
|
|
37
|
+
log(env.config, `${label}: starting (maxPages=${effectiveMaxPages}, mode=${concurrency}, dedupe=${!!dedupeStrategy})`);
|
|
38
|
+
const results = [];
|
|
39
|
+
try {
|
|
40
|
+
let rowIndex = 0;
|
|
41
|
+
let stopped = false;
|
|
42
|
+
let stoppedIndex = Infinity;
|
|
43
|
+
let pagesScanned = 1;
|
|
44
|
+
const stop = (idx) => {
|
|
45
|
+
if (!stopped) {
|
|
46
|
+
log(env.config, `${label}: stop() called at row ${idx} — halting`);
|
|
47
|
+
stopped = true;
|
|
48
|
+
stoppedIndex = idx;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
while (!stopped) {
|
|
52
|
+
const rowLocators = env.getRowLocators();
|
|
53
|
+
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
54
|
+
const pageRows = await rowLocators.all();
|
|
55
|
+
const batchSize = newIndices.length;
|
|
56
|
+
if (batchSize === 0) {
|
|
57
|
+
log(env.config, `${label}: page ${pagesScanned} — no new row(s) found`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
log(env.config, `${label}: scanning page ${pagesScanned} — ${batchSize} new row(s)`);
|
|
61
|
+
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(batchSize) : undefined;
|
|
62
|
+
const actionMutex = useMutex ? new mutex_1.Mutex() : null;
|
|
63
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i, pagesScanned - 1, barrier));
|
|
64
|
+
const processRow = async (row) => {
|
|
65
|
+
try {
|
|
66
|
+
if (stopped && row.rowIndex > stoppedIndex) {
|
|
67
|
+
return SKIP;
|
|
68
|
+
}
|
|
69
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
70
|
+
const key = await dedupeStrategy(row);
|
|
71
|
+
if (dedupeKeys.has(key)) {
|
|
72
|
+
log(env.config, `${label}: dedupe skip key="${key}"`);
|
|
79
73
|
return SKIP;
|
|
80
74
|
}
|
|
81
|
-
|
|
82
|
-
const key = yield dedupeStrategy(row);
|
|
83
|
-
if (dedupeKeys.has(key)) {
|
|
84
|
-
log(env.config, `${label}: dedupe skip key="${key}"`);
|
|
85
|
-
return SKIP;
|
|
86
|
-
}
|
|
87
|
-
dedupeKeys.add(key);
|
|
88
|
-
}
|
|
89
|
-
// Execute callback (optionally serialized via mutex)
|
|
90
|
-
const runCallback = () => __awaiter(this, void 0, void 0, function* () {
|
|
91
|
-
if (stopped && row.rowIndex > stoppedIndex)
|
|
92
|
-
return SKIP;
|
|
93
|
-
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
94
|
-
return yield callback({ row, rowIndex: row.rowIndex, stop: () => stop(row.rowIndex) });
|
|
95
|
-
});
|
|
96
|
-
if (actionMutex) {
|
|
97
|
-
return yield actionMutex.run(runCallback);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
return yield runCallback();
|
|
101
|
-
}
|
|
75
|
+
dedupeKeys.add(key);
|
|
102
76
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
77
|
+
// Execute callback (optionally serialized via mutex)
|
|
78
|
+
const runCallback = async () => {
|
|
79
|
+
if (stopped && row.rowIndex > stoppedIndex)
|
|
80
|
+
return SKIP;
|
|
81
|
+
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
82
|
+
return await callback({ row, rowIndex: row.rowIndex, stop: () => stop(row.rowIndex) });
|
|
83
|
+
};
|
|
84
|
+
if (actionMutex) {
|
|
85
|
+
return await actionMutex.run(runCallback);
|
|
106
86
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (concurrency === 'sequential') {
|
|
110
|
-
for (const row of smartRows) {
|
|
111
|
-
pageResults.push(yield processRow(row));
|
|
87
|
+
else {
|
|
88
|
+
return await runCallback();
|
|
112
89
|
}
|
|
113
90
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
91
|
+
finally {
|
|
92
|
+
// Ensure the barrier is notified once per row, even on error or skip.
|
|
93
|
+
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
117
94
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
results.push(r);
|
|
95
|
+
};
|
|
96
|
+
const pageResults = [];
|
|
97
|
+
if (concurrency === 'sequential') {
|
|
98
|
+
for (const row of smartRows) {
|
|
99
|
+
pageResults.push(await processRow(row));
|
|
124
100
|
}
|
|
125
|
-
rowIndex += batchSize;
|
|
126
101
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!(yield env.advancePage(useBulk))) {
|
|
131
|
-
log(env.config, `${label}: no more pages — done`);
|
|
132
|
-
break;
|
|
102
|
+
else {
|
|
103
|
+
const results = await Promise.all(smartRows.map(processRow));
|
|
104
|
+
pageResults.push(...results);
|
|
133
105
|
}
|
|
134
|
-
|
|
106
|
+
for (let i = 0; i < pageResults.length; i++) {
|
|
107
|
+
if (smartRows[i].rowIndex > stoppedIndex)
|
|
108
|
+
break;
|
|
109
|
+
const r = pageResults[i];
|
|
110
|
+
if (r !== SKIP)
|
|
111
|
+
results.push(r);
|
|
112
|
+
}
|
|
113
|
+
rowIndex += batchSize;
|
|
135
114
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
115
|
+
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
116
|
+
break;
|
|
117
|
+
log(env.config, `${label}: advancing to next page (${pagesScanned} → ${pagesScanned + 1})`);
|
|
118
|
+
if (!await env.advancePage(useBulk)) {
|
|
119
|
+
log(env.config, `${label}: no more pages — done`);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
pagesScanned++;
|
|
140
123
|
}
|
|
141
|
-
|
|
142
|
-
}
|
|
124
|
+
log(env.config, `${label}: complete — ${results.length} result(s) across ${pagesScanned} page(s)`);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
await tracker.cleanup(env.getPage());
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
143
130
|
}
|
|
144
131
|
/** Filter via map; returns matched rows as {@link SmartRowArray}. */
|
|
145
|
-
function runFilter(
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return env.createSmartRowArray(results);
|
|
153
|
-
});
|
|
132
|
+
async function runFilter(env, predicate, options = {}) {
|
|
133
|
+
const mapCallback = async (ctx) => {
|
|
134
|
+
const matched = await predicate(ctx);
|
|
135
|
+
return matched ? ctx.row : SKIP;
|
|
136
|
+
};
|
|
137
|
+
const results = await runMap(env, mapCallback, options, 'filter');
|
|
138
|
+
return env.createSmartRowArray(results);
|
|
154
139
|
}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.TableMapper = void 0;
|
|
13
4
|
const headers_1 = require("../strategies/headers");
|
|
@@ -22,69 +13,65 @@ class TableMapper {
|
|
|
22
13
|
log(msg) {
|
|
23
14
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', msg);
|
|
24
15
|
}
|
|
25
|
-
getMap(timeout) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
yield this.rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
|
|
36
|
-
}
|
|
37
|
-
catch (e) { }
|
|
16
|
+
async getMap(timeout) {
|
|
17
|
+
var _a;
|
|
18
|
+
if (this._headerMap)
|
|
19
|
+
return this._headerMap;
|
|
20
|
+
this.log('Mapping headers...');
|
|
21
|
+
const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
if (this.config.autoScroll) {
|
|
24
|
+
try {
|
|
25
|
+
await this.rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
|
|
38
26
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// 3. Attempt Scan
|
|
66
|
-
try {
|
|
67
|
-
const rawHeaders = yield strategy(context);
|
|
68
|
-
const entries = yield this.processHeaders(rawHeaders);
|
|
69
|
-
// Success
|
|
70
|
-
this._headerMap = new Map(entries);
|
|
71
|
-
this.log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
72
|
-
return this._headerMap;
|
|
73
|
-
}
|
|
74
|
-
catch (e) {
|
|
75
|
-
lastError = e;
|
|
76
|
-
this.log(`Header mapping failed. Attempt ${Math.floor((Date.now() - startTime) / 100)}: ${e.message}`);
|
|
77
|
-
yield new Promise(r => setTimeout(r, 100));
|
|
27
|
+
catch (e) { }
|
|
28
|
+
}
|
|
29
|
+
const headerLoc = this.resolve(this.config.headerSelector, this.rootLocator);
|
|
30
|
+
const strategy = this.config.strategies.header || headers_1.HeaderStrategies.visible;
|
|
31
|
+
const context = {
|
|
32
|
+
root: this.rootLocator,
|
|
33
|
+
config: this.config,
|
|
34
|
+
page: this.rootLocator.page(),
|
|
35
|
+
resolve: this.resolve
|
|
36
|
+
};
|
|
37
|
+
let lastError = null;
|
|
38
|
+
while (Date.now() - startTime < headerTimeout) {
|
|
39
|
+
// 1. Wait for visibility
|
|
40
|
+
try {
|
|
41
|
+
await headerLoc.first().waitFor({ state: 'visible', timeout: 200 });
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
// Continue to check existing/loading state even if not strictly "visible" yet
|
|
45
|
+
}
|
|
46
|
+
// 2. Check Smart Loading State
|
|
47
|
+
if ((_a = this.config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isHeaderLoading) {
|
|
48
|
+
const isStable = !(await this.config.strategies.loading.isHeaderLoading(context));
|
|
49
|
+
if (!isStable) {
|
|
50
|
+
this.log('Headers are loading/unstable... waiting');
|
|
51
|
+
await new Promise(r => setTimeout(r, 100));
|
|
52
|
+
continue;
|
|
78
53
|
}
|
|
79
54
|
}
|
|
80
|
-
|
|
81
|
-
|
|
55
|
+
// 3. Attempt Scan
|
|
56
|
+
try {
|
|
57
|
+
const rawHeaders = await strategy(context);
|
|
58
|
+
const entries = await this.processHeaders(rawHeaders);
|
|
59
|
+
// Success
|
|
60
|
+
this._headerMap = new Map(entries);
|
|
61
|
+
this.log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
62
|
+
return this._headerMap;
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
lastError = e;
|
|
66
|
+
this.log(`Header mapping failed. Attempt ${Math.floor((Date.now() - startTime) / 100)}: ${e.message}`);
|
|
67
|
+
await new Promise(r => setTimeout(r, 100));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw lastError || new Error(`Timed out waiting for headers after ${headerTimeout}ms`);
|
|
82
71
|
}
|
|
83
|
-
remapHeaders() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
yield this.getMap();
|
|
87
|
-
});
|
|
72
|
+
async remapHeaders() {
|
|
73
|
+
this._headerMap = null;
|
|
74
|
+
await this.getMap();
|
|
88
75
|
}
|
|
89
76
|
getMapSync() {
|
|
90
77
|
return this._headerMap;
|
|
@@ -95,43 +82,41 @@ class TableMapper {
|
|
|
95
82
|
clear() {
|
|
96
83
|
this._headerMap = null;
|
|
97
84
|
}
|
|
98
|
-
processHeaders(rawHeaders) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
text
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
this.log(`Header mapping index ${i}: "${text}"`);
|
|
113
|
-
entries.push([text, i]);
|
|
114
|
-
seenHeaders.add(text);
|
|
115
|
-
}
|
|
116
|
-
// Validation: Check for empty table
|
|
117
|
-
if (entries.length === 0) {
|
|
118
|
-
throw new Error(`Initialization Error: No columns found using selector "${this.config.headerSelector}". Check your selector or ensure the table is visible.`);
|
|
119
|
-
}
|
|
120
|
-
// Validation: Check for duplicates
|
|
121
|
-
const seen = new Set();
|
|
122
|
-
const duplicates = new Set();
|
|
123
|
-
for (const [name] of entries) {
|
|
124
|
-
if (seen.has(name)) {
|
|
125
|
-
duplicates.add(name);
|
|
126
|
-
}
|
|
127
|
-
seen.add(name);
|
|
85
|
+
async processHeaders(rawHeaders) {
|
|
86
|
+
const seenHeaders = new Set();
|
|
87
|
+
const entries = [];
|
|
88
|
+
for (let i = 0; i < rawHeaders.length; i++) {
|
|
89
|
+
let text = rawHeaders[i].trim() || `__col_${i}`;
|
|
90
|
+
if (this.config.headerTransformer) {
|
|
91
|
+
text = await this.config.headerTransformer({
|
|
92
|
+
text,
|
|
93
|
+
index: i,
|
|
94
|
+
locator: this.resolve(this.config.headerSelector, this.rootLocator).nth(i),
|
|
95
|
+
seenHeaders
|
|
96
|
+
});
|
|
128
97
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
98
|
+
this.log(`Header mapping index ${i}: "${text}"`);
|
|
99
|
+
entries.push([text, i]);
|
|
100
|
+
seenHeaders.add(text);
|
|
101
|
+
}
|
|
102
|
+
// Validation: Check for empty table
|
|
103
|
+
if (entries.length === 0) {
|
|
104
|
+
throw new Error(`Initialization Error: No columns found using selector "${this.config.headerSelector}". Check your selector or ensure the table is visible.`);
|
|
105
|
+
}
|
|
106
|
+
// Validation: Check for duplicates
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const duplicates = new Set();
|
|
109
|
+
for (const [name] of entries) {
|
|
110
|
+
if (seen.has(name)) {
|
|
111
|
+
duplicates.add(name);
|
|
132
112
|
}
|
|
133
|
-
|
|
134
|
-
}
|
|
113
|
+
seen.add(name);
|
|
114
|
+
}
|
|
115
|
+
if (duplicates.size > 0) {
|
|
116
|
+
const dupList = Array.from(duplicates).map(d => `"${d}"`).join(', ');
|
|
117
|
+
throw new Error(`Initialization Error: Duplicate column names found: ${dupList}. Use 'headerTransformer' to rename duplicate columns.`);
|
|
118
|
+
}
|
|
119
|
+
return entries;
|
|
135
120
|
}
|
|
136
121
|
}
|
|
137
122
|
exports.TableMapper = TableMapper;
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* This file is generated by scripts/embed-config-types.mjs
|
|
4
4
|
* It contains minimal type definitions for config generation prompts.
|
|
5
5
|
*/
|
|
6
|
-
export declare const MINIMAL_CONFIG_CONTEXT = "\n/**\n * Flexible selector type - can be a CSS string or function returning a Locator.\n * @example\n * // String selector\n * rowSelector: 'tbody tr'\n * \n * // Function selector\n *
|
|
6
|
+
export declare const MINIMAL_CONFIG_CONTEXT = "\n/**\n * Flexible selector type - can be a CSS string or function returning a Locator.\n * @example\n * // String selector\n * rowSelector: 'tbody tr'\n * \n * // Function selector\n * headerSelector: (root) => root.locator('[role=\"columnheader\"]')\n */\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\n/**\n * Debug configuration for development and troubleshooting\n */\nexport type DebugConfig = {\n slow?: number | {\n pagination?: number;\n getCell?: number;\n findRow?: number;\n default?: number;\n };\n logLevel?: 'verbose' | 'info' | 'error' | 'none';\n};\n\n/**\n * Configuration options for useTable - focus on selectors and basic setup\n */\nexport interface TableConfig<T = any> {\n /** CSS selector or function for table headers */\n headerSelector?: string | ((root: Locator) => Locator);\n \n /** CSS selector or function for table rows */\n rowSelector?: string | ((root: Locator) => Locator);\n \n /** CSS selector or function for cells within a row */\n cellSelector?: string | ((row: Locator) => Locator);\n \n /** Transform header text (e.g., normalize, deduplicate) */\n headerTransformer?: (args: { \n text: string; \n index: number; \n locator: Locator;\n seenHeaders: Set<string>;\n }) => string | Promise<string>;\n \n /** Automatically scroll to table on init (default: true) */\n autoScroll?: boolean;\n \n /** Debug options for development and troubleshooting */\n debug?: DebugConfig;\n \n /** Advanced: Custom strategies for pagination, sorting, navigation, etc. */\n strategies?: TableStrategies;\n}\n\n/**\n * Example usage:\n */\n// const table = useTable(page.locator('table'), {\n// headerSelector: 'thead th',\n// rowSelector: 'tbody tr',\n// cellSelector: 'td',\n// headerTransformer: ({ text }) => text.trim()\n// });\n\n";
|
|
@@ -14,7 +14,7 @@ exports.MINIMAL_CONFIG_CONTEXT = `
|
|
|
14
14
|
* rowSelector: 'tbody tr'
|
|
15
15
|
*
|
|
16
16
|
* // Function selector
|
|
17
|
-
*
|
|
17
|
+
* headerSelector: (root) => root.locator('[role="columnheader"]')
|
|
18
18
|
*/
|
|
19
19
|
export type Selector = string | ((root: Locator | Page) => Locator);
|
|
20
20
|
|
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.10.1";
|
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.10.1";
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.glideSnapFirstColumnIntoView = exports.glideSeekColumnIndex = exports.glideGoRight = exports.glideGoLeft = exports.glideGoDown = exports.glideGoUp = void 0;
|
|
13
4
|
/**
|
|
@@ -15,48 +6,48 @@ exports.glideSnapFirstColumnIntoView = exports.glideSeekColumnIndex = exports.gl
|
|
|
15
6
|
* These define HOW to move, not WHEN to move.
|
|
16
7
|
* The orchestration logic lives in _navigateToCell.
|
|
17
8
|
*/
|
|
18
|
-
const glideGoUp = (context) =>
|
|
19
|
-
|
|
20
|
-
}
|
|
9
|
+
const glideGoUp = async (context) => {
|
|
10
|
+
await context.page.keyboard.press('ArrowUp');
|
|
11
|
+
};
|
|
21
12
|
exports.glideGoUp = glideGoUp;
|
|
22
|
-
const glideGoDown = (context) =>
|
|
23
|
-
|
|
24
|
-
}
|
|
13
|
+
const glideGoDown = async (context) => {
|
|
14
|
+
await context.page.keyboard.press('ArrowDown');
|
|
15
|
+
};
|
|
25
16
|
exports.glideGoDown = glideGoDown;
|
|
26
17
|
// Horizontal: use page `.dvn-scroller` (canvas root is not its ancestor). Virtualized rows expose
|
|
27
18
|
// a small window of `td[aria-colindex]` that shifts with scrollLeft.
|
|
28
|
-
const nudgePageScroller = (page, delta) =>
|
|
19
|
+
const nudgePageScroller = async (page, delta) => {
|
|
29
20
|
const scroller = page.locator('.dvn-scroller').first();
|
|
30
|
-
|
|
21
|
+
await scroller.evaluate((el, d) => {
|
|
31
22
|
el.scrollLeft += d;
|
|
32
23
|
}, delta);
|
|
33
|
-
}
|
|
34
|
-
const glideGoLeft = (context) =>
|
|
35
|
-
|
|
36
|
-
}
|
|
24
|
+
};
|
|
25
|
+
const glideGoLeft = async (context) => {
|
|
26
|
+
await nudgePageScroller(context.page, -400);
|
|
27
|
+
};
|
|
37
28
|
exports.glideGoLeft = glideGoLeft;
|
|
38
|
-
const glideGoRight = (context) =>
|
|
39
|
-
|
|
40
|
-
}
|
|
29
|
+
const glideGoRight = async (context) => {
|
|
30
|
+
await nudgePageScroller(context.page, 400);
|
|
31
|
+
};
|
|
41
32
|
exports.glideGoRight = glideGoRight;
|
|
42
33
|
/** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
|
|
43
|
-
const glideSeekColumnIndex = (context, columnIndex) =>
|
|
34
|
+
const glideSeekColumnIndex = async (context, columnIndex) => {
|
|
44
35
|
const { page } = context;
|
|
45
36
|
const scroller = page.locator('.dvn-scroller').first();
|
|
46
|
-
|
|
37
|
+
await scroller.evaluate((el, colIdx) => {
|
|
47
38
|
const max = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
48
39
|
const ratio = Math.min(1, Math.max(0, (colIdx + 0.5) / 64));
|
|
49
40
|
el.scrollLeft = Math.floor(ratio * max);
|
|
50
41
|
}, columnIndex);
|
|
51
|
-
|
|
52
|
-
}
|
|
42
|
+
await page.waitForTimeout(100);
|
|
43
|
+
};
|
|
53
44
|
exports.glideSeekColumnIndex = glideSeekColumnIndex;
|
|
54
45
|
/** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
|
|
55
|
-
const glideSnapFirstColumnIntoView = (context) =>
|
|
46
|
+
const glideSnapFirstColumnIntoView = async (context) => {
|
|
56
47
|
const { page } = context;
|
|
57
|
-
|
|
48
|
+
await page.locator('.dvn-scroller').first().evaluate((el) => {
|
|
58
49
|
el.scrollLeft = 0;
|
|
59
50
|
});
|
|
60
|
-
|
|
61
|
-
}
|
|
51
|
+
await page.waitForTimeout(150);
|
|
52
|
+
};
|
|
62
53
|
exports.glideSnapFirstColumnIntoView = glideSnapFirstColumnIntoView;
|