@rickcedwhat/playwright-smart-table 6.23.1 → 6.24.0-next.82106e1

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.
Files changed (37) hide show
  1. package/dist/engine/rowFinder.js +12 -9
  2. package/dist/engine/tableMapper.js +2 -1
  3. package/dist/packageVersion.d.ts +1 -1
  4. package/dist/packageVersion.js +1 -1
  5. package/dist/plugins/index.d.ts +48 -52
  6. package/dist/presets/glide/headers.d.ts +3 -1
  7. package/dist/presets/glide/headers.js +12 -47
  8. package/dist/presets/glide/index.d.ts +11 -12
  9. package/dist/presets/glide/index.js +0 -2
  10. package/dist/presets/glide/viewport.js +2 -1
  11. package/dist/presets/rdg.d.ts +14 -11
  12. package/dist/presets/rdg.js +0 -3
  13. package/dist/smartRow.js +18 -14
  14. package/dist/strategies/contentReady.d.ts +0 -1
  15. package/dist/strategies/headers.js +10 -53
  16. package/dist/strategies/index.d.ts +6 -7
  17. package/dist/strategies/pagination.d.ts +7 -4
  18. package/dist/strategies/pagination.js +27 -4
  19. package/dist/strategies/resolution.d.ts +1 -3
  20. package/dist/strategies/resolution.js +0 -28
  21. package/dist/strategies/sorting.js +1 -1
  22. package/dist/strategies/stabilization.d.ts +1 -1
  23. package/dist/strategies/viewport.d.ts +33 -1
  24. package/dist/useTable.js +12 -10
  25. package/dist/utils/debugUtils.js +4 -2
  26. package/dist/utils/headerScroll.d.ts +13 -0
  27. package/dist/utils/headerScroll.js +47 -0
  28. package/dist/utils/loadingWait.js +2 -1
  29. package/dist/utils/mergeTableConfig.js +6 -5
  30. package/dist/utils/navigationBarrier.d.ts +0 -2
  31. package/dist/utils/navigationBarrier.js +1 -4
  32. package/dist/utils/resolveCellLocator.js +4 -3
  33. package/package.json +7 -8
  34. package/dist/typeContext.d.ts +0 -6
  35. package/dist/typeContext.js +0 -1133
  36. package/dist/utils.d.ts +0 -7
  37. package/dist/utils.js +0 -21
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HeaderStrategies = void 0;
4
4
  const debugUtils_1 = require("../utils/debugUtils");
5
+ const headerScroll_1 = require("../utils/headerScroll");
5
6
  exports.HeaderStrategies = {
6
7
  /**
7
8
  * Default strategy: Returns only the headers currently visible in the DOM.
@@ -30,62 +31,18 @@ exports.HeaderStrategies = {
30
31
  */
31
32
  horizontalScroll: (options) => {
32
33
  return async (context) => {
33
- var _a, _b;
34
- const { resolve, config, root, page } = context;
35
- const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : 20;
36
- const scrollAmount = (_b = options === null || options === void 0 ? void 0 : options.scrollAmount) !== null && _b !== void 0 ? _b : 300;
37
- const collectedHeaders = new Set();
38
- const getVisible = async () => {
39
- const headerLoc = resolve(config.headerSelector, root);
40
- const texts = await headerLoc.allInnerTexts();
41
- return texts.map(t => t.trim());
42
- };
43
- let currentHeaders = await getVisible();
44
- currentHeaders.forEach(h => collectedHeaders.add(h));
45
34
  const selector = options === null || options === void 0 ? void 0 : options.selector;
46
35
  if (!selector) {
47
- (0, debugUtils_1.logDebug)(config, 'info', 'HeaderStrategies.horizontalScroll: no selector provided — returning visible headers only. Pass { selector } for the scroll container (e.g. ".dvn-scroller").');
48
- return Array.from(collectedHeaders);
36
+ (0, debugUtils_1.logDebug)(context.config, 'info', 'HeaderStrategies.horizontalScroll: no selector provided — returning visible headers only. Pass { selector } for the scroll container (e.g. ".dvn-scroller").');
37
+ return (0, headerScroll_1.readVisibleHeaders)(context);
49
38
  }
50
- const scrollerHandle = await root.evaluateHandle((el, sel) => {
51
- if (el.matches(sel))
52
- return el;
53
- const ancestor = el.closest(sel);
54
- if (ancestor)
55
- return ancestor;
56
- const child = el.querySelector(sel);
57
- if (child)
58
- return child;
59
- return null;
60
- }, selector);
61
- const isScrollerFound = await scrollerHandle.evaluate(el => !!el);
62
- if (isScrollerFound) {
63
- await scrollerHandle.evaluate(el => el.scrollLeft = 0);
64
- await page.waitForTimeout(200);
65
- for (let i = 0; i < limit; i++) {
66
- const sizeBefore = collectedHeaders.size;
67
- await scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
68
- await page.waitForTimeout(300);
69
- const newHeaders = await getVisible();
70
- newHeaders.forEach(h => collectedHeaders.add(h));
71
- if (collectedHeaders.size === sizeBefore) {
72
- await scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
73
- await page.waitForTimeout(300);
74
- const retryHeaders = await getVisible();
75
- retryHeaders.forEach(h => collectedHeaders.add(h));
76
- if (collectedHeaders.size === sizeBefore)
77
- break;
78
- }
79
- }
80
- }
81
- else {
82
- (0, debugUtils_1.logDebug)(config, 'info', `HeaderStrategies.horizontalScroll: Could not find scroller matching "${selector}". Returning visible headers.`);
83
- }
84
- if (isScrollerFound) {
85
- await scrollerHandle.evaluate(el => el.scrollLeft = 0);
86
- await page.waitForTimeout(200);
87
- }
88
- return Array.from(collectedHeaders);
39
+ // Self, then ancestor, then descendant — scoped to the table, never document-wide.
40
+ const findScroller = () => context.root.evaluateHandle((el, sel) => { var _a; return el.matches(sel) ? el : (_a = el.closest(sel)) !== null && _a !== void 0 ? _a : el.querySelector(sel); }, selector);
41
+ return (0, headerScroll_1.scrollAndCollectHeaders)(context, findScroller, {
42
+ limit: options === null || options === void 0 ? void 0 : options.limit,
43
+ scrollAmount: options === null || options === void 0 ? void 0 : options.scrollAmount,
44
+ notFoundMessage: `HeaderStrategies.horizontalScroll: Could not find scroller matching "${selector}". Returning visible headers.`,
45
+ });
89
46
  };
90
47
  }
91
48
  };
@@ -3,7 +3,6 @@ export * from './sorting';
3
3
  export * from './columns';
4
4
  export * from './headers';
5
5
  export * from './fill';
6
- export type { ColumnResolutionStrategy } from './resolution';
7
6
  export * from './dedupe';
8
7
  export * from './loading';
9
8
  export * from './stabilization';
@@ -29,13 +28,13 @@ export declare const Strategies: {
29
28
  }, options?: {
30
29
  nextBulkPages?: number;
31
30
  previousBulkPages?: number;
32
- numberOfPages?: number | ((root: import("@playwright/test").Locator) => number | Promise<number>);
33
- detectCurrentPage?: (root: import("@playwright/test").Locator) => number | Promise<number>;
31
+ numberOfPages?: number | ((root: import('@playwright/test').Locator) => number | Promise<number>);
32
+ detectCurrentPage?: (root: import('@playwright/test').Locator) => number | Promise<number>;
34
33
  stabilization?: import("./stabilization").StabilizationStrategy;
35
34
  timeout?: number;
36
35
  }) => import("..").PaginationStrategy;
37
36
  infiniteScroll: (options?: {
38
- action?: "scroll" | "js-scroll";
37
+ action?: 'scroll' | 'js-scroll';
39
38
  scrollTarget?: import("..").Selector;
40
39
  scrollAmount?: number;
41
40
  stabilization?: import("./stabilization").StabilizationStrategy;
@@ -81,7 +80,7 @@ export declare const Strategies: {
81
80
  };
82
81
  Stabilization: {
83
82
  contentChanged: (options?: {
84
- scope?: "all" | "first";
83
+ scope?: 'all' | 'first';
85
84
  timeout?: number;
86
85
  }) => import("./stabilization").StabilizationStrategy;
87
86
  rowCountIncreased: (options?: {
@@ -102,10 +101,10 @@ export declare const Strategies: {
102
101
  textStable: (options?: {
103
102
  timeout?: number;
104
103
  interval?: number;
105
- }) => import("./contentReady").ContentReadyStrategy;
104
+ }) => import("..").ContentReadyStrategy;
106
105
  mutationSettled: (options?: {
107
106
  timeout?: number;
108
107
  quietPeriod?: number;
109
- }) => import("./contentReady").ContentReadyStrategy;
108
+ }) => import("..").ContentReadyStrategy;
110
109
  };
111
110
  };
@@ -19,8 +19,8 @@ export declare const PaginationStrategies: {
19
19
  }, options?: {
20
20
  nextBulkPages?: number;
21
21
  previousBulkPages?: number;
22
- numberOfPages?: number | ((root: import("@playwright/test").Locator) => number | Promise<number>);
23
- detectCurrentPage?: (root: import("@playwright/test").Locator) => number | Promise<number>;
22
+ numberOfPages?: number | ((root: import('@playwright/test').Locator) => number | Promise<number>);
23
+ detectCurrentPage?: (root: import('@playwright/test').Locator) => number | Promise<number>;
24
24
  stabilization?: StabilizationStrategy;
25
25
  timeout?: number;
26
26
  }) => PaginationStrategy;
@@ -30,13 +30,16 @@ export declare const PaginationStrategies: {
30
30
  *
31
31
  * @param options.action 'scroll' (mouse wheel) or 'js-scroll' (direct scrollTop).
32
32
  * @param options.scrollTarget Selector for the scroll container (defaults to table root).
33
- * @param options.scrollAmount Amount to scroll in pixels (default 500).
33
+ * @param options.scrollAmount Amount to scroll in pixels. Defaults to 80% of the scroll target's
34
+ * visible height, so a step never jumps past the rendered window of a virtualized list.
35
+ * An explicit value larger than the visible height logs a one-time warning, because
36
+ * virtualizers never mount the rows that get skipped.
34
37
  * @param options.stabilization Strategy to determine if new content loaded.
35
38
  * Defaults to `rowCountIncreased` (simple append).
36
39
  * Use `contentChanged` for virtualization.
37
40
  */
38
41
  infiniteScroll: (options?: {
39
- action?: "scroll" | "js-scroll";
42
+ action?: 'scroll' | 'js-scroll';
40
43
  scrollTarget?: Selector;
41
44
  scrollAmount?: number;
42
45
  stabilization?: StabilizationStrategy;
@@ -67,23 +67,46 @@ exports.PaginationStrategies = {
67
67
  *
68
68
  * @param options.action 'scroll' (mouse wheel) or 'js-scroll' (direct scrollTop).
69
69
  * @param options.scrollTarget Selector for the scroll container (defaults to table root).
70
- * @param options.scrollAmount Amount to scroll in pixels (default 500).
70
+ * @param options.scrollAmount Amount to scroll in pixels. Defaults to 80% of the scroll target's
71
+ * visible height, so a step never jumps past the rendered window of a virtualized list.
72
+ * An explicit value larger than the visible height logs a one-time warning, because
73
+ * virtualizers never mount the rows that get skipped.
71
74
  * @param options.stabilization Strategy to determine if new content loaded.
72
75
  * Defaults to `rowCountIncreased` (simple append).
73
76
  * Use `contentChanged` for virtualization.
74
77
  */
75
78
  infiniteScroll: (options = {}) => {
76
- var _a, _b;
79
+ var _a;
77
80
  // Default stabilization: Wait for row count to increase (Append mode)
78
81
  const stabilization = (_a = options.stabilization) !== null && _a !== void 0 ? _a : stabilization_1.StabilizationStrategies.rowCountIncreased({ timeout: options.timeout });
79
- const amount = (_b = options.scrollAmount) !== null && _b !== void 0 ? _b : 500;
82
+ let warnedOversizedStep = false;
80
83
  const createScroller = (directionMultiplier) => {
81
84
  return async (context) => {
82
85
  const { root, resolve, page } = context;
83
86
  const scrollTarget = options.scrollTarget
84
87
  ? resolve(options.scrollTarget, root)
85
88
  : root;
86
- const beforeScrollTop = await scrollTarget.evaluate((el) => el.scrollTop);
89
+ const metrics = await scrollTarget.evaluate((el) => {
90
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
91
+ return {
92
+ scrollTop: el.scrollTop,
93
+ // A non-scrolling target scrolls the page, so the window bounds what is rendered.
94
+ visibleHeight: el.clientHeight > 0 ? Math.min(el.clientHeight, viewportHeight) : viewportHeight,
95
+ isScrollContainer: el.scrollHeight > el.clientHeight + 1,
96
+ };
97
+ });
98
+ const beforeScrollTop = metrics.scrollTop;
99
+ let amount;
100
+ if (options.scrollAmount !== undefined) {
101
+ amount = options.scrollAmount;
102
+ if (!warnedOversizedStep && metrics.isScrollContainer && amount > metrics.visibleHeight) {
103
+ warnedOversizedStep = true;
104
+ console.warn(`[SmartTable] infiniteScroll scrollAmount (${amount}px) is larger than the scroll target's visible height (${Math.round(metrics.visibleHeight)}px). Virtualized lists only render rows near the viewport, so rows between steps are never mounted and will be silently skipped. Omit scrollAmount to step by the visible height, or pass a smaller value.`);
105
+ }
106
+ }
107
+ else {
108
+ amount = Math.floor(metrics.visibleHeight * 0.8) || 500;
109
+ }
87
110
  const doScroll = async () => {
88
111
  const box = await scrollTarget.boundingBox();
89
112
  const scrollValue = amount * directionMultiplier;
@@ -1,4 +1,5 @@
1
1
  import { StrategyContext } from '../types';
2
+ /** @deprecated Not used by the library. Will be removed in v7. */
2
3
  export interface ColumnResolutionStrategy {
3
4
  /**
4
5
  * Resolves a column name (string or Regex) to a column index.
@@ -17,6 +18,3 @@ export interface ColumnResolutionStrategy {
17
18
  headerMap: Map<string, number>;
18
19
  }): string;
19
20
  }
20
- export declare const ResolutionStrategies: {
21
- default: ColumnResolutionStrategy;
22
- };
@@ -1,30 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ResolutionStrategies = void 0;
4
- exports.ResolutionStrategies = {
5
- default: {
6
- resolveIndex: ({ query, headerMap }) => {
7
- // 1. Exact / String Match
8
- if (typeof query === 'string') {
9
- if (headerMap.has(query))
10
- return headerMap.get(query);
11
- }
12
- // 2. Regex Match
13
- if (query instanceof RegExp) {
14
- for (const [colName, idx] of headerMap.entries()) {
15
- if (query.test(colName))
16
- return idx;
17
- }
18
- return undefined;
19
- }
20
- // 3. (Optional) Fuzzy String Match fallback could go here
21
- // But for strict default strategy, we might want to keep it simple first
22
- // The original code didn't do fuzzy *resolution* logic inside the get(), it just did strict get().
23
- // The fuzzy logic was only for *suggestions* on error.
24
- return undefined;
25
- },
26
- resolveName: ({ query }) => {
27
- return query.toString();
28
- }
29
- }
30
- };
@@ -12,7 +12,7 @@ exports.SortingStrategies = {
12
12
  */
13
13
  AriaSort: () => {
14
14
  return {
15
- async doSort({ columnName, direction, context }) {
15
+ async doSort({ columnName, context }) {
16
16
  // getHeaderCell is always present on TableContext after table is initialized
17
17
  const targetHeader = await context.getHeaderCell(columnName);
18
18
  // The table engine handles verify-and-retry. We only provide the trigger here.
@@ -9,7 +9,7 @@ export declare const StabilizationStrategies: {
9
9
  * Waits for the visible text of the table rows to change.
10
10
  */
11
11
  contentChanged: (options?: {
12
- scope?: "all" | "first";
12
+ scope?: 'all' | 'first';
13
13
  timeout?: number;
14
14
  }) => StabilizationStrategy;
15
15
  /**
@@ -50,6 +50,38 @@ export type DataAttributeViewportOptions = {
50
50
  */
51
51
  columnOffset?: number;
52
52
  };
53
+ /**
54
+ * Viewport strategies for tables where row and cell elements carry a DOM attribute
55
+ * equal to their index (e.g. Braintrust logs table, TanStack-style grids).
56
+ *
57
+ * Derives selectors from the table config so no duplication is needed:
58
+ * - getVisibleColumnRange — reads column attribute from cells in the first visible row
59
+ * - getVisibleRowRange — reads row attribute from all visible row elements
60
+ * - scrollToColumn — aligns header's left edge to container left, waits for cell mount
61
+ * - scrollToRow — scrolls row into view, waits for row mount
62
+ *
63
+ * **Important:** `rowAttribute` must be present on the elements matched by `rowSelector`,
64
+ * not on child cells. If row elements lack the attribute, `getVisibleRowRange` returns
65
+ * `{first:0, last:0}` and logs a warning at the `error` log level.
66
+ *
67
+ * @example
68
+ * // TanStack / Braintrust — pass your scroll container explicitly (no Tailwind default)
69
+ * viewport: Strategies.Viewport.dataAttribute({
70
+ * scrollContainer: 'div[class*="overflow-auto"]',
71
+ * })
72
+ *
73
+ * @example
74
+ * // ARIA grid (aria-rowindex / aria-colindex, 1-based)
75
+ * viewport: Strategies.Viewport.dataAttribute({
76
+ * scrollContainer: '.MuiDataGrid-virtualScroller',
77
+ * rowAttribute: 'aria-rowindex',
78
+ * columnAttribute: 'aria-colindex',
79
+ * rowOffset: 1,
80
+ * columnOffset: 1,
81
+ * })
82
+ */
83
+ declare const dataAttribute: (options?: DataAttributeViewportOptions) => ViewportStrategy;
53
84
  export declare const ViewportStrategies: {
54
- dataAttribute: (options?: DataAttributeViewportOptions) => ViewportStrategy;
85
+ dataAttribute: typeof dataAttribute;
55
86
  };
87
+ export {};
package/dist/useTable.js CHANGED
@@ -38,7 +38,7 @@ const smartRowInternals_1 = require("./utils/smartRowInternals");
38
38
  // Helper to safely serialize objects containing functions for logging
39
39
  const safeStringify = (obj) => {
40
40
  try {
41
- return JSON.stringify(obj, (key, value) => {
41
+ return JSON.stringify(obj, (_key, value) => {
42
42
  if (typeof value === 'function') {
43
43
  return '[Function]';
44
44
  }
@@ -319,13 +319,14 @@ const useTable = (rootLocator, configOptions = {}) => {
319
319
  return resolve(config.headerSelector, rootLocator).nth(idx);
320
320
  },
321
321
  countRows: async (filters, options) => {
322
- var _a, _b, _c, _d, _e;
322
+ var _a, _b, _c, _d;
323
+ var _e;
323
324
  if (tableState.empty)
324
325
  return 0;
325
326
  await _autoInit();
326
327
  const filtersRecord = (filters !== null && filters !== void 0 ? filters : {});
327
328
  const hasFilters = Object.keys(filtersRecord).length > 0;
328
- const effectiveMaxPages = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
329
+ const effectiveMaxPages = (_e = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _e !== void 0 ? _e : config.maxPages;
329
330
  const pag = config.strategies.pagination;
330
331
  const hasPagination = effectiveMaxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
331
332
  const domFilters = {};
@@ -333,10 +334,10 @@ const useTable = (rootLocator, configOptions = {}) => {
333
334
  const syntheticFilters = {};
334
335
  if (hasFilters) {
335
336
  for (const [key, value] of Object.entries(filtersRecord)) {
336
- if ((_b = config.syntheticColumns) === null || _b === void 0 ? void 0 : _b[key]) {
337
+ if ((_a = config.syntheticColumns) === null || _a === void 0 ? void 0 : _a[key]) {
337
338
  syntheticFilters[key] = value;
338
339
  }
339
- else if ((_d = (_c = config.columnOverrides) === null || _c === void 0 ? void 0 : _c[key]) === null || _d === void 0 ? void 0 : _d.read) {
340
+ else if ((_c = (_b = config.columnOverrides) === null || _b === void 0 ? void 0 : _b[key]) === null || _c === void 0 ? void 0 : _c.read) {
340
341
  overrideFilters[key] = value;
341
342
  }
342
343
  else {
@@ -378,7 +379,7 @@ const useTable = (rootLocator, configOptions = {}) => {
378
379
  return count;
379
380
  };
380
381
  // Wait for table to finish loading before counting
381
- const isTableLoading = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.isTableLoading;
382
+ const isTableLoading = (_d = config.strategies.loading) === null || _d === void 0 ? void 0 : _d.isTableLoading;
382
383
  if (isTableLoading) {
383
384
  const ctx = createStrategyContext();
384
385
  await (0, loadingWait_1.waitWhileTableLoading)(config, ctx, rootLocator.page(), 'countRows');
@@ -597,7 +598,8 @@ const useTable = (rootLocator, configOptions = {}) => {
597
598
  },
598
599
  sorting: {
599
600
  apply: async (columnName, direction) => {
600
- var _a, _b, _c, _d, _e, _f;
601
+ var _a, _b;
602
+ var _c, _d, _e, _f;
601
603
  await _autoInit();
602
604
  if (!config.strategies.sorting)
603
605
  throw new Error('No sorting strategy has been configured.');
@@ -612,15 +614,15 @@ const useTable = (rootLocator, configOptions = {}) => {
612
614
  }
613
615
  await config.strategies.sorting.doSort({ columnName, direction, context });
614
616
  if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
615
- const timeout = (_c = (_b = config.strategies.loading.sortStabilizationTimeout) !== null && _b !== void 0 ? _b : config.strategies.loading.loadingTimeout) !== null && _c !== void 0 ? _c : 10000;
616
- const poll = (_d = config.strategies.loading.sortStabilizationPollInterval) !== null && _d !== void 0 ? _d : 100;
617
+ const timeout = (_d = (_c = config.strategies.loading.sortStabilizationTimeout) !== null && _c !== void 0 ? _c : config.strategies.loading.loadingTimeout) !== null && _d !== void 0 ? _d : 10000;
618
+ const poll = (_e = config.strategies.loading.sortStabilizationPollInterval) !== null && _e !== void 0 ? _e : 100;
617
619
  await (0, loadingWait_1.waitWhileTableLoading)(config, context, rootLocator.page(), 'sorting.apply', {
618
620
  timeout,
619
621
  pollInterval: poll,
620
622
  });
621
623
  }
622
624
  else {
623
- const fallback = (_f = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.sortStabilizationFallbackDelay) !== null && _f !== void 0 ? _f : 200;
625
+ const fallback = (_f = (_b = config.strategies.loading) === null || _b === void 0 ? void 0 : _b.sortStabilizationFallbackDelay) !== null && _f !== void 0 ? _f : 200;
624
626
  await rootLocator.page().waitForTimeout(fallback);
625
627
  }
626
628
  await (0, debugUtils_1.debugDelay)(config, 'default');
@@ -8,7 +8,8 @@ exports.warnIfDebugInCI = warnIfDebugInCI;
8
8
  * Get delay for specific action type
9
9
  */
10
10
  function getDebugDelay(config, actionType) {
11
- var _a, _b, _c;
11
+ var _a;
12
+ var _b, _c;
12
13
  if (!((_a = config.debug) === null || _a === void 0 ? void 0 : _a.slow))
13
14
  return 0;
14
15
  if (typeof config.debug.slow === 'number') {
@@ -29,7 +30,8 @@ async function debugDelay(config, actionType) {
29
30
  * Log debug message based on log level
30
31
  */
31
32
  function logDebug(config, level, message, data) {
32
- var _a, _b;
33
+ var _a;
34
+ var _b;
33
35
  const logLevel = (_b = (_a = config.debug) === null || _a === void 0 ? void 0 : _a.logLevel) !== null && _b !== void 0 ? _b : 'none';
34
36
  const levels = { none: 0, error: 1, info: 2, verbose: 3 };
35
37
  if (levels[logLevel] >= levels[level]) {
@@ -0,0 +1,13 @@
1
+ import type { JSHandle } from '@playwright/test';
2
+ import type { StrategyContext } from '../types';
3
+ export declare const readVisibleHeaders: ({ resolve, config, root }: StrategyContext) => Promise<string[]>;
4
+ /**
5
+ * Horizontal header discovery shared by `HeaderStrategies.horizontalScroll` and the Glide preset:
6
+ * reset the scroller to the left, step right collecting headers until two consecutive steps add
7
+ * nothing (or `limit` is hit), then scroll back. Callers differ only in how they find the scroller.
8
+ */
9
+ export declare function scrollAndCollectHeaders(context: StrategyContext, findScroller: () => Promise<JSHandle<Element | null>>, options: {
10
+ limit?: number;
11
+ scrollAmount?: number;
12
+ notFoundMessage: string;
13
+ }): Promise<string[]>;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readVisibleHeaders = void 0;
4
+ exports.scrollAndCollectHeaders = scrollAndCollectHeaders;
5
+ const debugUtils_1 = require("./debugUtils");
6
+ const readVisibleHeaders = async ({ resolve, config, root }) => {
7
+ const texts = await resolve(config.headerSelector, root).allInnerTexts();
8
+ return texts.map(t => t.trim());
9
+ };
10
+ exports.readVisibleHeaders = readVisibleHeaders;
11
+ /**
12
+ * Horizontal header discovery shared by `HeaderStrategies.horizontalScroll` and the Glide preset:
13
+ * reset the scroller to the left, step right collecting headers until two consecutive steps add
14
+ * nothing (or `limit` is hit), then scroll back. Callers differ only in how they find the scroller.
15
+ */
16
+ async function scrollAndCollectHeaders(context, findScroller, options) {
17
+ var _a, _b;
18
+ const { config, page } = context;
19
+ const limit = (_a = options.limit) !== null && _a !== void 0 ? _a : 20;
20
+ const scrollAmount = (_b = options.scrollAmount) !== null && _b !== void 0 ? _b : 300;
21
+ const collectedHeaders = new Set(await (0, exports.readVisibleHeaders)(context));
22
+ const scrollerHandle = await findScroller();
23
+ const isScrollerFound = await scrollerHandle.evaluate(el => !!el);
24
+ if (!isScrollerFound) {
25
+ (0, debugUtils_1.logDebug)(config, 'info', options.notFoundMessage);
26
+ return Array.from(collectedHeaders);
27
+ }
28
+ const collectAfterStep = async () => {
29
+ await scrollerHandle.evaluate((el, amount) => el.scrollLeft += amount, scrollAmount);
30
+ await page.waitForTimeout(300);
31
+ (await (0, exports.readVisibleHeaders)(context)).forEach(h => collectedHeaders.add(h));
32
+ };
33
+ await scrollerHandle.evaluate(el => el.scrollLeft = 0);
34
+ await page.waitForTimeout(200);
35
+ for (let i = 0; i < limit; i++) {
36
+ const sizeBefore = collectedHeaders.size;
37
+ await collectAfterStep();
38
+ if (collectedHeaders.size === sizeBefore) {
39
+ await collectAfterStep();
40
+ if (collectedHeaders.size === sizeBefore)
41
+ break;
42
+ }
43
+ }
44
+ await scrollerHandle.evaluate(el => el.scrollLeft = 0);
45
+ await page.waitForTimeout(200);
46
+ return Array.from(collectedHeaders);
47
+ }
@@ -12,7 +12,8 @@ const debugUtils_1 = require("./debugUtils");
12
12
  * 3. 10_000 ms default
13
13
  */
14
14
  async function waitWhileTableLoading(config, context, page, label, options) {
15
- var _a, _b, _c, _d, _e;
15
+ var _a;
16
+ var _b, _c, _d, _e;
16
17
  const isTableLoading = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading;
17
18
  if (!isTableLoading)
18
19
  return;
@@ -22,7 +22,8 @@ exports.mergeTableConfig = mergeTableConfig;
22
22
  * });
23
23
  */
24
24
  function mergeTableConfig(base, overrides) {
25
- var _a, _b, _c, _d;
25
+ var _a, _b;
26
+ var _c, _d;
26
27
  const result = Object.assign(Object.assign({}, base), overrides);
27
28
  // Deep-merge strategies one level down
28
29
  if (base.strategies || overrides.strategies) {
@@ -31,12 +32,12 @@ function mergeTableConfig(base, overrides) {
31
32
  // so that e.g. base.strategies.loading and overrides.strategies.loading are combined.
32
33
  // Function values (pagination, header, dedupe, getCellLocator, etc.) are replaced, not merged.
33
34
  const strategyKeys = new Set([
34
- ...Object.keys((_a = base.strategies) !== null && _a !== void 0 ? _a : {}),
35
- ...Object.keys((_b = overrides.strategies) !== null && _b !== void 0 ? _b : {}),
35
+ ...Object.keys((_c = base.strategies) !== null && _c !== void 0 ? _c : {}),
36
+ ...Object.keys((_d = overrides.strategies) !== null && _d !== void 0 ? _d : {}),
36
37
  ]);
37
38
  for (const key of strategyKeys) {
38
- const baseVal = (_c = base.strategies) === null || _c === void 0 ? void 0 : _c[key];
39
- const overrideVal = (_d = overrides.strategies) === null || _d === void 0 ? void 0 : _d[key];
39
+ const baseVal = (_a = base.strategies) === null || _a === void 0 ? void 0 : _a[key];
40
+ const overrideVal = (_b = overrides.strategies) === null || _b === void 0 ? void 0 : _b[key];
40
41
  if (baseVal !== undefined &&
41
42
  overrideVal !== undefined &&
42
43
  typeof baseVal === 'object' &&
@@ -4,8 +4,6 @@ export declare class NavigationBarrier {
4
4
  private buckets;
5
5
  private recoveryMutex;
6
6
  constructor(total: number);
7
- /** True when more than one row shares this barrier (scrollToRow would evict peers). */
8
- isParallel(): boolean;
9
7
  /**
10
8
  * Synchronize all active rows at a specific column index.
11
9
  * The last row to arrive (or the last markFinished call) triggers the `moveAction`.
@@ -9,10 +9,6 @@ class NavigationBarrier {
9
9
  this.recoveryMutex = null;
10
10
  this.total = total;
11
11
  }
12
- /** True when more than one row shares this barrier (scrollToRow would evict peers). */
13
- isParallel() {
14
- return this.total > 1;
15
- }
16
12
  /**
17
13
  * Synchronize all active rows at a specific column index.
18
14
  * The last row to arrive (or the last markFinished call) triggers the `moveAction`.
@@ -55,6 +51,7 @@ class NavigationBarrier {
55
51
  * The fn receives a fresh `targetReached` check; if the previous row's scroll
56
52
  * already brought this row into view, fn can no-op immediately.
57
53
  */
54
+ // fallow-ignore-next-line unused-class-member
58
55
  async runExclusiveRecovery(fn) {
59
56
  if (!this.recoveryMutex)
60
57
  this.recoveryMutex = new mutex_1.Mutex();
@@ -7,10 +7,11 @@ exports.resolveCellLocatorForFilter = resolveCellLocatorForFilter;
7
7
  * otherwise `cellSelector` + `.nth(columnIndex)`.
8
8
  */
9
9
  function resolveCellLocator(args) {
10
- var _a, _b;
10
+ var _a;
11
+ var _b;
11
12
  const { config, resolve, row, root, columnName, columnIndex, rowIndex } = args;
12
- const page = (_a = args.page) !== null && _a !== void 0 ? _a : root.page();
13
- if ((_b = config.strategies) === null || _b === void 0 ? void 0 : _b.getCellLocator) {
13
+ const page = (_b = args.page) !== null && _b !== void 0 ? _b : root.page();
14
+ if ((_a = config.strategies) === null || _a === void 0 ? void 0 : _a.getCellLocator) {
14
15
  return config.strategies.getCellLocator({
15
16
  row,
16
17
  root,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.23.1",
3
+ "version": "6.24.0-next.82106e1",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",
@@ -18,7 +18,6 @@
18
18
  "dist"
19
19
  ],
20
20
  "scripts": {
21
- "generate-types": "node scripts/embed-types.mjs",
22
21
  "generate-config-types": "node scripts/embed-config-types.mjs",
23
22
  "generate-docs": "node scripts/generate-readme.mjs",
24
23
  "generate-all-api-docs": "node scripts/generate-all-api-docs.mjs",
@@ -29,7 +28,7 @@
29
28
  "docs:verify-links": "node scripts/verify-vitepress-links.mjs",
30
29
  "embed-version": "node scripts/embed-version.mjs",
31
30
  "generate-og": "node scripts/generate-og.mjs",
32
- "build": "pnpm run embed-version && pnpm run generate-types && pnpm run generate-config-types && pnpm run generate-docs && pnpm run generate-all-api-docs && pnpm run update-all-api-signatures && tsc && node scripts/check-no-runtime-playwright.mjs",
31
+ "build": "pnpm run embed-version && pnpm run generate-config-types && pnpm run generate-docs && pnpm run generate-all-api-docs && pnpm run update-all-api-signatures && tsc && node scripts/check-no-runtime-playwright.mjs",
33
32
  "prepublishOnly": "pnpm run build",
34
33
  "clean-port": "lsof -ti:3000 | xargs kill -9 || true",
35
34
  "pretest": "pnpm run clean-port",
@@ -38,6 +37,7 @@
38
37
  "test:unit": "vitest run --coverage --reporter=verbose --reporter=html",
39
38
  "test:unit:ui": "vitest --ui",
40
39
  "test:mutate": "pnpm exec stryker run",
40
+ "fallow": "fallow dead-code",
41
41
  "pretest:e2e": "pnpm run clean-port",
42
42
  "posttest:e2e": "pnpm run clean-port",
43
43
  "test:e2e": "pnpm exec playwright test",
@@ -53,9 +53,7 @@
53
53
  },
54
54
  "./types": {
55
55
  "types": "./dist/types.d.ts",
56
- "import": "./dist/index.js",
57
- "require": "./dist/index.js",
58
- "default": "./dist/index.js"
56
+ "default": "./dist/types.js"
59
57
  },
60
58
  "./presets": {
61
59
  "types": "./dist/presets/index.d.ts",
@@ -121,12 +119,13 @@
121
119
  "@playwright/test": "^1.59.1",
122
120
  "@stryker-mutator/core": "^9.6.0",
123
121
  "@stryker-mutator/vitest-runner": "^9.6.0",
124
- "@types/node": "^25.5.2",
122
+ "@types/node": "^26.6.2",
125
123
  "@vitest/coverage-v8": "^3.2.6",
126
124
  "@vitest/ui": "^3.2.6",
125
+ "fallow": "3.31.0",
127
126
  "happy-dom": "^20.10.6",
128
127
  "husky": "^9.1.7",
129
- "typescript": "^6.0.2",
128
+ "typescript": "^7.0.2",
130
129
  "vitepress": "^1.6.4",
131
130
  "vitepress-plugin-tabs": "^0.9.0",
132
131
  "vitest": "^3.2.6"