@rickcedwhat/playwright-smart-table 6.21.0 → 6.22.0-next.28cae60

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.
@@ -13,7 +13,7 @@ export declare class RowFinder<T = any> {
13
13
  private tableState;
14
14
  private advancePage;
15
15
  private resolve;
16
- 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, barrier?: NavigationBarrier, rowSelector?: string) => SmartRow<T>, tableState?: {
16
+ 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, barrier?: NavigationBarrier, healSelector?: string) => SmartRow<T>, tableState?: {
17
17
  currentPageIndex: number;
18
18
  }, advancePage?: (useBulk: boolean) => Promise<boolean>);
19
19
  private splitFilters;
@@ -38,8 +38,8 @@ export declare class RowFinder<T = any> {
38
38
  * every row calling elementHandle() + evaluate() per row (O(n) CDP roundtrips, and
39
39
  * elementHandle() is soft-deprecated). We resolve the target once and let evaluateAll
40
40
  * compare it against the full row set in the browser. The row set is resolved through the
41
- * same selector/scope as everywhere else, so a string, function, or Locator rowSelector
42
- * all behave identically. (#350)
41
+ * same selector/scope as everywhere else, so a string or function rowSelector behave
42
+ * identically. (#350)
43
43
  */
44
44
  private throwIfAmbiguous;
45
45
  private scanDomPosition;
@@ -9,6 +9,7 @@ const navigationBarrier_1 = require("../utils/navigationBarrier");
9
9
  const rowResolution_1 = require("./rowResolution");
10
10
  const resolveCellLocator_1 = require("../utils/resolveCellLocator");
11
11
  const scanPages_1 = require("./scanPages");
12
+ const loadingWait_1 = require("../utils/loadingWait");
12
13
  class RowFinder {
13
14
  constructor(rootLocator, config, resolve, filterEngine, tableMapper, makeSmartRow, tableState = { currentPageIndex: 0 }, advancePage = async () => false) {
14
15
  this.rootLocator = rootLocator;
@@ -131,10 +132,7 @@ class RowFinder {
131
132
  page: this.rootLocator.page(),
132
133
  resolve: this.resolve
133
134
  };
134
- while (await isTableLoading(context)) {
135
- (0, debugUtils_1.logDebug)(this.config, 'verbose', 'Table is loading... waiting');
136
- await this.rootLocator.page().waitForTimeout(200);
137
- }
135
+ await (0, loadingWait_1.waitWhileTableLoading)(this.config, context, this.rootLocator.page(), 'waitForTableReady');
138
136
  }
139
137
  async findRows(filters = {}, options) {
140
138
  var _a, _b, _c;
@@ -271,8 +269,8 @@ class RowFinder {
271
269
  * every row calling elementHandle() + evaluate() per row (O(n) CDP roundtrips, and
272
270
  * elementHandle() is soft-deprecated). We resolve the target once and let evaluateAll
273
271
  * compare it against the full row set in the browser. The row set is resolved through the
274
- * same selector/scope as everywhere else, so a string, function, or Locator rowSelector
275
- * all behave identically. (#350)
272
+ * same selector/scope as everywhere else, so a string or function rowSelector behave
273
+ * identically. (#350)
276
274
  */
277
275
  async throwIfAmbiguous(rows, filters, map) {
278
276
  const sampleData = [];
@@ -7,7 +7,7 @@ export interface TableIterationEnv<T = any> {
7
7
  getRowLocators: () => Locator;
8
8
  getMap: () => Map<string, number>;
9
9
  advancePage: (useBulk: boolean) => Promise<boolean>;
10
- makeSmartRow: (rowLocator: Locator, map: Map<string, number>, rowIndex: number, tablePageIndex?: number, barrier?: NavigationBarrier, rowSelector?: string) => SmartRow<T>;
10
+ makeSmartRow: (rowLocator: Locator, map: Map<string, number>, rowIndex: number, tablePageIndex?: number, barrier?: NavigationBarrier, healSelector?: string) => SmartRow<T>;
11
11
  createSmartRowArray: (rows: SmartRow<T>[]) => SmartRowArray<T>;
12
12
  config: FinalTableConfig<T>;
13
13
  getPage: () => Page;
@@ -9,6 +9,7 @@ const navigationBarrier_1 = require("../utils/navigationBarrier");
9
9
  const mutex_1 = require("../utils/mutex");
10
10
  const rowResolution_1 = require("./rowResolution");
11
11
  const scanPages_1 = require("./scanPages");
12
+ const smartRowInternals_1 = require("../utils/smartRowInternals");
12
13
  function log(config, msg) {
13
14
  (0, debugUtils_1.logDebug)(config, 'verbose', msg);
14
15
  }
@@ -28,8 +29,8 @@ async function runMap(env, callback, options = {}, label = 'map') {
28
29
  const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
29
30
  const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
30
31
  const dedupeKeys = dedupeStrategy ? new Set() : null;
31
- const defaultMode = label === 'map' ? 'parallel' : 'sequential';
32
- const concurrency = (_d = (_c = options.concurrency) !== null && _c !== void 0 ? _c : env.config.concurrency) !== null && _d !== void 0 ? _d : defaultMode;
32
+ // Sequential by default for all iteration labels (#434) — parallel is opt-in for read-only maps.
33
+ const concurrency = (_d = (_c = options.concurrency) !== null && _c !== void 0 ? _c : env.config.concurrency) !== null && _d !== void 0 ? _d : 'sequential';
33
34
  const useBarrier = concurrency === 'synchronized';
34
35
  // Mutex must not pair with the navigation barrier: synchronized mode needs every row
35
36
  // to enter barrier.sync concurrently; serializing callbacks here deadlocks (first row waits
@@ -112,7 +113,7 @@ async function runMap(env, callback, options = {}, label = 'map') {
112
113
  const sr = env.makeSmartRow(pageRows[idx], map, logicalIndex, env.getCurrentPageIndex(), barrier, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
113
114
  // Mark as part of an iteration batch so toJSON's #366 re-pin uses rescan-only recovery
114
115
  // (no scroll-back) — scrolling here would disrupt sibling rows' positional locators.
115
- sr._inBatch = true;
116
+ (0, smartRowInternals_1.internals)(sr)._inBatch = true;
116
117
  return sr;
117
118
  }));
118
119
  // enumIndex = visit-order counter (ctx.index); row.rowIndex = logical (ctx.rowIndex).
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, SmartCell, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, SyntheticColumnDef, ColumnOverride, ColumnOverrideReadContext, TableStrategies, LoadingStrategy, ViewportStrategy, FilterStrategy, NavigationPrimitives, DedupeStrategy, ContentReadyStrategy, FillStrategy, PaginationStrategy, HeaderStrategy, } from './types';
3
+ export type { TableConfig, TableResult, SmartRow, SmartCell, Selector, TableSelector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, SyntheticColumnDef, ColumnOverride, ColumnOverrideReadContext, TableStrategies, LoadingStrategy, ViewportStrategy, FilterStrategy, NavigationPrimitives, DedupeStrategy, ContentReadyStrategy, FillStrategy, PaginationStrategy, HeaderStrategy, } from './types';
4
4
  export { Strategies } from './strategies';
5
5
  export * as presets from './presets';
6
6
  /**
@@ -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.21.0";
6
+ export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.22.0-next.28cae60";
@@ -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.21.0";
9
+ exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.22.0-next.28cae60";
@@ -27,9 +27,9 @@ export declare const Plugins: {
27
27
  };
28
28
  pagination: import("..").PaginationPrimitives;
29
29
  };
30
- headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
31
- rowSelector?: string | undefined;
32
- cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
30
+ headerSelector?: import("..").TableSelector | undefined;
31
+ rowSelector?: import("..").TableSelector | undefined;
32
+ cellSelector?: import("..").TableSelector | undefined;
33
33
  maxPages?: number | undefined;
34
34
  concurrency?: import("../types").RowIterationMode | undefined;
35
35
  headerTransformer?: ((args: {
@@ -68,9 +68,9 @@ export declare const Plugins: {
68
68
  locator: any;
69
69
  } | null>;
70
70
  };
71
- headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
72
- rowSelector?: string | undefined;
73
- cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
71
+ headerSelector?: import("..").TableSelector | undefined;
72
+ rowSelector?: import("..").TableSelector | undefined;
73
+ cellSelector?: import("..").TableSelector | undefined;
74
74
  maxPages?: number | undefined;
75
75
  concurrency?: import("../types").RowIterationMode | undefined;
76
76
  headerTransformer?: ((args: {
@@ -93,9 +93,9 @@ export declare const Plugins: {
93
93
  */
94
94
  MUI: {
95
95
  Strategies: import("..").TableStrategies | undefined;
96
- headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
97
- rowSelector?: string | undefined;
98
- cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
96
+ headerSelector?: import("..").TableSelector | undefined;
97
+ rowSelector?: import("..").TableSelector | undefined;
98
+ cellSelector?: import("..").TableSelector | undefined;
99
99
  maxPages?: number | undefined;
100
100
  concurrency?: import("../types").RowIterationMode | undefined;
101
101
  headerTransformer?: ((args: {
@@ -4,6 +4,12 @@ exports.muiDataGrid = exports.muiTable = void 0;
4
4
  exports.createMuiTable = createMuiTable;
5
5
  exports.createMuiDataGrid = createMuiDataGrid;
6
6
  const debugUtils_1 = require("../utils/debugUtils");
7
+ const cssSelector_1 = require("../utils/cssSelector");
8
+ // Fallbacks for in-browser queries when the configured selector is a function.
9
+ // Only DataGrid rows carry data-rowindex, so this can't match cells or headers.
10
+ const MUI_ROW_CSS = '[data-rowindex]';
11
+ const MUI_HEADER_CSS = '.MuiDataGrid-columnHeader';
12
+ const cssOr = (selector, fallback) => typeof selector === 'string' ? selector : fallback;
7
13
  const DEFAULT_LABELS = {
8
14
  nextPage: 'Go to next page',
9
15
  previousPage: 'Go to previous page',
@@ -146,22 +152,14 @@ function createMuiTable(opts) {
146
152
  return 'asc';
147
153
  return 'none';
148
154
  },
149
- doSort: async ({ columnName, direction, context }) => {
150
- var _a, _b;
155
+ doSort: async ({ columnName, context }) => {
156
+ // Trigger-only: core `sorting.apply` owns retries + state checks (#434).
151
157
  if (!context.getHeaderCell)
152
158
  return;
153
159
  const header = await context.getHeaderCell(columnName);
154
160
  const sortLabel = header.locator('.MuiTableSortLabel-root').first();
155
161
  const target = await sortLabel.isVisible().catch(() => false) ? sortLabel : header;
156
- let current = await ((_a = context.config.strategies.sorting) === null || _a === void 0 ? void 0 : _a.getSortState({ columnName, context })) || 'none';
157
- let attempts = 0;
158
- // Click until the state matches the target direction, max 3 times (none -> asc -> desc)
159
- while (current !== direction && attempts < 3) {
160
- await target.click();
161
- await context.page.waitForTimeout(100);
162
- current = await ((_b = context.config.strategies.sorting) === null || _b === void 0 ? void 0 : _b.getSortState({ columnName, context })) || 'none';
163
- attempts++;
164
- }
162
+ await target.click();
165
163
  }
166
164
  },
167
165
  dedupe: async (row) => {
@@ -303,28 +301,22 @@ function createMuiDataGrid(opts) {
303
301
  return 'desc';
304
302
  return 'none';
305
303
  },
306
- doSort: async ({ columnName, direction, context }) => {
307
- var _a, _b;
304
+ doSort: async ({ columnName, context }) => {
305
+ // Trigger-only: core `sorting.apply` owns retries + state checks (#434).
308
306
  if (!context.getHeaderCell)
309
307
  return;
310
308
  const header = await context.getHeaderCell(columnName);
311
- let current = await ((_a = context.config.strategies.sorting) === null || _a === void 0 ? void 0 : _a.getSortState({ columnName, context })) || 'none';
312
- let attempts = 0;
313
- while (current !== direction && attempts < 3) {
314
- const clickTarget = header.locator('.MuiDataGrid-columnHeaderTitleContainer').first();
315
- if (await clickTarget.isVisible()) {
316
- await clickTarget.click({ force: true });
317
- }
318
- else {
319
- await header.click({ force: true });
320
- }
321
- // Wait for stabilization (using DataGrid specific overlay check inside helper)
322
- const displayedRows = context.root.locator('.MuiTablePagination-displayedRows');
323
- const text = await displayedRows.innerText().catch(() => '');
324
- await waitForMuiPaginationStabilization(context, displayedRows, text);
325
- current = await ((_b = context.config.strategies.sorting) === null || _b === void 0 ? void 0 : _b.getSortState({ columnName, context })) || 'none';
326
- attempts++;
309
+ const clickTarget = header.locator('.MuiDataGrid-columnHeaderTitleContainer').first();
310
+ if (await clickTarget.isVisible()) {
311
+ await clickTarget.click({ force: true });
327
312
  }
313
+ else {
314
+ await header.click({ force: true });
315
+ }
316
+ // One stabilization wait after the click; core also polls isTableLoading.
317
+ const displayedRows = context.root.locator('.MuiTablePagination-displayedRows');
318
+ const text = await displayedRows.innerText().catch(() => '');
319
+ await waitForMuiPaginationStabilization(context, displayedRows, text);
328
320
  }
329
321
  },
330
322
  dedupe: async (row) => {
@@ -345,7 +337,7 @@ function createMuiDataGrid(opts) {
345
337
  // scroll methods use querySelector rather than closest().
346
338
  viewport: {
347
339
  getVisibleRowRange: async ({ root, config }) => {
348
- const rowSel = config.rowSelector;
340
+ const rowSel = cssOr(config.rowSelector, MUI_ROW_CSS);
349
341
  return root.evaluate((el, rowSel) => {
350
342
  const indices = Array.from(el.querySelectorAll(rowSel))
351
343
  .map(r => Number(r.getAttribute('data-rowindex')))
@@ -359,8 +351,13 @@ function createMuiDataGrid(opts) {
359
351
  // the DOM positions of rows overlapping the virtualScroller's bounds so map/forEach
360
352
  // skip the overscan rows MUI keeps mounted above/below the fold. The scroller is a
361
353
  // descendant of root, so querySelector (not closest).
362
- getVisibleRowIndices: async ({ root, config }) => {
363
- const rowSel = config.rowSelector;
354
+ getVisibleRowIndices: async ({ root, config, resolve }) => {
355
+ // Positions must index into the configured row set, so no CSS fallback here.
356
+ const rowSel = (0, cssSelector_1.cssSelectorOrWarn)(config, 'rowSelector', 'MUI DataGrid overscan filtering');
357
+ if (!rowSel) {
358
+ const count = await resolve(config.rowSelector, root).count();
359
+ return Array.from({ length: count }, (_, i) => i);
360
+ }
364
361
  return root.evaluate((el, rowSel) => {
365
362
  const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
366
363
  const rows = Array.from(el.querySelectorAll(rowSel));
@@ -381,7 +378,7 @@ function createMuiDataGrid(opts) {
381
378
  }, rowSel);
382
379
  },
383
380
  getVisibleColumnRange: async ({ root, config }) => {
384
- const rowSel = config.rowSelector;
381
+ const rowSel = cssOr(config.rowSelector, MUI_ROW_CSS);
385
382
  const cellSel = typeof config.cellSelector === 'string' ? config.cellSelector : '[aria-colindex]';
386
383
  return root.evaluate((el, { rowSel, cellSel }) => {
387
384
  const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
@@ -423,7 +420,7 @@ function createMuiDataGrid(opts) {
423
420
  }, { rowSel, cellSel });
424
421
  },
425
422
  scrollToRow: async ({ root, config }, rowIndex) => {
426
- const rowSel = config.rowSelector;
423
+ const rowSel = cssOr(config.rowSelector, MUI_ROW_CSS);
427
424
  await root.evaluate((el, { rowSel, idx }) => {
428
425
  const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
429
426
  if (!scroller)
@@ -477,10 +474,10 @@ function createMuiDataGrid(opts) {
477
474
  .catch(() => { });
478
475
  },
479
476
  scrollToColumn: async ({ root, config, page }, colIndex) => {
480
- const headerSel = typeof config.headerSelector === 'string' ? config.headerSelector : null;
477
+ const headerSel = cssOr(config.headerSelector, MUI_HEADER_CSS);
481
478
  await root.evaluate((el, { headerSel, idx }) => {
482
479
  const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
483
- if (!scroller || !headerSel)
480
+ if (!scroller)
484
481
  return;
485
482
  const headers = Array.from(el.querySelectorAll(headerSel));
486
483
  const targetAriaIdx = idx + 1; // aria-colindex is 1-based
package/dist/smartRow.js CHANGED
@@ -7,13 +7,20 @@ const stringUtils_1 = require("./utils/stringUtils");
7
7
  const debugUtils_1 = require("./utils/debugUtils");
8
8
  const paginationPath_1 = require("./utils/paginationPath");
9
9
  const sentinel_1 = require("./utils/sentinel");
10
+ const pageIndex_1 = require("./utils/pageIndex");
10
11
  /**
11
12
  * Internal helper to navigate to a cell with active cell optimization.
12
13
  * Uses `strategies.navigation` primitives (goUp/goDown/goLeft/goRight, optional snap/seek/goHome).
13
14
  * Returns the target cell locator after navigation.
14
15
  */
15
16
  const _navigateToCell = async (params) => {
16
- const { config, rootLocator, page, resolve, getHeaders, column, index, rowLocator, rowIndex, barrier, allowMissingCell } = params;
17
+ var _a;
18
+ const { config, rootLocator, page, resolve, getHeaders, column, index, rowLocator, barrier, allowMissingCell } = params;
19
+ // getRowByIndex supplies a position in the mounted row set. Resolve it while the
20
+ // selected row is still mounted, before horizontal scrolling can evict it.
21
+ const rowIndex = params.renderWindowPosition && config.strategies.viewport && config.strategies.resolveRowIndex
22
+ ? (_a = (await (0, rowResolution_1.resolveLogicalRowIndex)(rowLocator, config, () => undefined))) === null || _a === void 0 ? void 0 : _a.index
23
+ : params.rowIndex;
17
24
  const rowLabel = typeof rowIndex === 'number' ? `row ${rowIndex}` : 'row ?';
18
25
  (0, debugUtils_1.logDebug)(config, 'verbose', `_navigateToCell: resolving column "${column}" (colIndex ${index}), ${rowLabel}`);
19
26
  // Get active cell if strategy is available
@@ -357,6 +364,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
357
364
  index: idx,
358
365
  rowLocator,
359
366
  rowIndex,
367
+ renderWindowPosition,
360
368
  barrier: smart._barrier
361
369
  });
362
370
  // Run beforeCellRead hook (same as toJSON does after navigation).
@@ -418,6 +426,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
418
426
  index: idx,
419
427
  rowLocator,
420
428
  rowIndex,
429
+ renderWindowPosition,
421
430
  barrier: smart._barrier,
422
431
  allowMissingCell: !!(columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read),
423
432
  });
@@ -717,6 +726,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
717
726
  index: idx,
718
727
  rowLocator: stableRow,
719
728
  rowIndex,
729
+ renderWindowPosition,
720
730
  barrier: smart._barrier,
721
731
  allowMissingCell: !!mapper,
722
732
  });
@@ -833,6 +843,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
833
843
  index: colIdx,
834
844
  rowLocator,
835
845
  rowIndex,
846
+ renderWindowPosition,
836
847
  barrier: smart._barrier
837
848
  });
838
849
  const columnOverride = (_c = config.columnOverrides) === null || _c === void 0 ? void 0 : _c[colName];
@@ -885,7 +896,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
885
896
  const primitives = config.strategies.pagination;
886
897
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
887
898
  const getCurrent = () => parentTable.currentPageIndex;
888
- const setCurrent = (n) => { parentTable.currentPageIndex = n; };
899
+ const setCurrent = (n) => { (0, pageIndex_1.setCurrentPageIndex)(parentTable, n); };
889
900
  if (primitives.goToPage) {
890
901
  (0, debugUtils_1.logDebug)(config, 'info', `bringIntoView: Navigating to page ${tablePageIndex} (goToPage retry loop)`);
891
902
  await (0, paginationPath_1.executeNavigationWithGoToPageRetry)(tablePageIndex, primitives, context, getCurrent, setCurrent);
@@ -910,7 +921,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
910
921
  if (!ok)
911
922
  throw new Error(`bringIntoView: goNext failed before reaching page ${tablePageIndex}.`);
912
923
  }
913
- parentTable.currentPageIndex = tablePageIndex;
924
+ (0, pageIndex_1.setCurrentPageIndex)(parentTable, tablePageIndex);
914
925
  }
915
926
  else {
916
927
  (0, debugUtils_1.logDebug)(config, 'error', `Cannot bring row on page ${tablePageIndex} into view. No backwards pagination strategies (goToPage, goPrevious, goPreviousBulk, or goToFirst+goNext) provided.`);
@@ -8,9 +8,8 @@ exports.FilterStrategies = void 0;
8
8
  exports.FilterStrategies = {
9
9
  default: {
10
10
  apply({ rows, filter, colIndex, tableContext }) {
11
- const page = tableContext.page;
12
- const resolve = tableContext.resolve;
13
- const cellTemplate = resolve(tableContext.config.cellSelector, page);
11
+ const scopeRow = tableContext.page.locator(':scope');
12
+ const cellTemplate = tableContext.resolve(tableContext.config.cellSelector, scopeRow);
14
13
  const targetCell = cellTemplate.nth(colIndex);
15
14
  if (typeof filter.value === 'function') {
16
15
  return rows.filter({ has: filter.value(targetCell) });
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ViewportStrategies = void 0;
4
4
  const debugUtils_1 = require("../utils/debugUtils");
5
+ const cssSelector_1 = require("../utils/cssSelector");
5
6
  /**
6
7
  * Viewport strategies for tables where row and cell elements carry a DOM attribute
7
8
  * equal to their index (e.g. Braintrust logs table, TanStack-style grids).
@@ -46,7 +47,9 @@ const dataAttribute = (options) => {
46
47
  const scrollPadding = (_g = options === null || options === void 0 ? void 0 : options.scrollPadding) !== null && _g !== void 0 ? _g : 20;
47
48
  return {
48
49
  getVisibleColumnRange: async ({ root, config }) => {
49
- const rowSel = config.rowSelector;
50
+ const rowSel = (0, cssSelector_1.cssSelectorOrWarn)(config, 'rowSelector', 'dataAttribute viewport column range');
51
+ if (!rowSel)
52
+ return cssSelector_1.UNKNOWN_RANGE;
50
53
  const cellSel = typeof config.cellSelector === 'string' ? config.cellSelector : `[${colAttr}]`;
51
54
  const result = await root.evaluate((el, { rowSel, cellSel, colAttr, colOffset }) => {
52
55
  const firstRow = el.querySelector(rowSel);
@@ -65,8 +68,12 @@ const dataAttribute = (options) => {
65
68
  }
66
69
  return { first: result.first, last: result.last };
67
70
  },
68
- getVisibleRowIndices: async ({ root, config }) => {
69
- const rowSel = config.rowSelector;
71
+ getVisibleRowIndices: async ({ root, config, resolve }) => {
72
+ const rowSel = (0, cssSelector_1.cssSelectorOrWarn)(config, 'rowSelector', 'dataAttribute viewport overscan filtering');
73
+ if (!rowSel) {
74
+ const count = await resolve(config.rowSelector, root).count();
75
+ return Array.from({ length: count }, (_, i) => i);
76
+ }
70
77
  // Geometry-based (mirrors getVisibleRowRange's inclusive intersection), but returns
71
78
  // the rows' DOM positions so the iteration engine can drop overscan rows (#353/#357).
72
79
  return root.evaluate((el, { rowSel, containerSel }) => {
@@ -92,7 +99,9 @@ const dataAttribute = (options) => {
92
99
  }, { rowSel, containerSel });
93
100
  },
94
101
  getVisibleRowRange: async ({ root, config }) => {
95
- const rowSel = config.rowSelector;
102
+ const rowSel = (0, cssSelector_1.cssSelectorOrWarn)(config, 'rowSelector', 'dataAttribute viewport row range');
103
+ if (!rowSel)
104
+ return cssSelector_1.UNKNOWN_RANGE;
96
105
  const result = await root.evaluate((el, { rowSel, rowAttr, rowOffset, containerSel }) => {
97
106
  const rows = Array.from(el.querySelectorAll(rowSel));
98
107
  const container = containerSel
@@ -120,9 +129,11 @@ const dataAttribute = (options) => {
120
129
  }
121
130
  return { first: result.first, last: result.last };
122
131
  },
123
- scrollToColumn: async ({ root, config }, colIndex) => {
124
- const headerSel = typeof config.headerSelector === 'string' ? config.headerSelector : null;
125
- if (!containerSel || !headerSel)
132
+ scrollToColumn: async ({ root, config, resolve }, colIndex) => {
133
+ if (!containerSel)
134
+ return;
135
+ const headerSel = (0, cssSelector_1.cssSelectorOrWarn)(config, 'headerSelector', 'dataAttribute viewport scrollToColumn');
136
+ if (!headerSel)
126
137
  return;
127
138
  const cellSel = typeof config.cellSelector === 'string' ? config.cellSelector : `[${colAttr}]`;
128
139
  const canScroll = await root.evaluate((el, { containerSel, headerSel, cellSel, idx, columnWidth, scrollPadding }) => {
@@ -157,15 +168,20 @@ const dataAttribute = (options) => {
157
168
  if (!canScroll)
158
169
  return;
159
170
  // Wait for a cell at this column index to mount in any row
160
- await root
161
- .locator(`${config.rowSelector} [${colAttr}="${colIndex + colOffset}"]`)
171
+ const rows = typeof config.rowSelector === 'string'
172
+ ? root.locator(config.rowSelector)
173
+ : resolve(config.rowSelector, root);
174
+ await rows
175
+ .locator(`[${colAttr}="${colIndex + colOffset}"]`)
162
176
  .first()
163
177
  .waitFor({ state: 'attached', timeout: attachTimeout });
164
178
  },
165
179
  scrollToRow: async ({ root, config }, rowIndex) => {
166
180
  if (!containerSel)
167
181
  return;
168
- const rowSel = config.rowSelector;
182
+ const rowSel = (0, cssSelector_1.cssSelectorOrWarn)(config, 'rowSelector', 'dataAttribute viewport scrollToRow');
183
+ if (!rowSel)
184
+ return;
169
185
  const canScroll = await root.evaluate((el, { containerSel, rowSel, rowAttr, idx, rowOffset, rowHeight, scrollPadding }) => {
170
186
  const container = el.closest(containerSel);
171
187
  if (!container)