@nodebug/browser-element-finder 1.0.8 → 1.1.6

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/index.js CHANGED
@@ -21,14 +21,18 @@ var ElementFinder = (() => {
21
21
  var element_finder_exports = {};
22
22
  __export(element_finder_exports, {
23
23
  ELEMENT_DEFINITIONS: () => ELEMENT_DEFINITIONS,
24
- findElement: () => findElement,
24
+ findElementByAttributes: () => findElementByAttributes,
25
+ findElementByType: () => findElementByType,
26
+ findElements: () => findElements,
27
+ findProbableElements: () => findProbableElements,
25
28
  getAllElements: () => getAllElements,
26
29
  getAllFrames: () => getAllFrames,
27
30
  getBoundingBox: () => getBoundingBox,
28
31
  getSearchableAttributes: () => getSearchableAttributes,
32
+ getValidAttributes: () => getValidAttributes,
29
33
  getValidTypes: () => getValidTypes,
30
34
  highlight: () => highlight,
31
- matchesContent: () => matchesContent,
35
+ matchesAttribute: () => matchesAttribute,
32
36
  matchesType: () => matchesType,
33
37
  parseCondition: () => parseCondition,
34
38
  parseXPath: () => parseXPath,
@@ -37,25 +41,6 @@ var ElementFinder = (() => {
37
41
  unhighlight: () => unhighlight
38
42
  });
39
43
 
40
- // src/searchable-attributes.json
41
- var searchable_attributes_default = [
42
- "placeholder",
43
- "value",
44
- "data-test-id",
45
- "data-testid",
46
- "id",
47
- "resource-id",
48
- "name",
49
- "aria-label",
50
- "class",
51
- "hint",
52
- "title",
53
- "tooltip",
54
- "alt",
55
- "src",
56
- "aria-labelledby"
57
- ];
58
-
59
44
  // src/element-definitions.json
60
45
  var element_definitions_default = {
61
46
  link: "self::a or @role='link' or @href",
@@ -65,9 +50,11 @@ var ElementFinder = (() => {
65
50
  checkbox: "(self::input and @type='checkbox') or @role='checkbox'",
66
51
  switch: "(self::input and @type='checkbox') or @role='switch' or (self::button and (contains(@class, 'switch') or @data-state))",
67
52
  slider: "self::input[@type='range'] or @role='slider'",
53
+ datepicker: "self::input and @type='date'",
54
+ colorpicker: "self::input and @type='color'",
68
55
  radio: "(self::input and @type='radio') or @role='radio'",
69
56
  dropdown: "(self::select[descendant::option] or @role='combobox' or @role='listbox' or contains(@class, 'dropdown') or contains(@class, 'trigger') or ancestor::*[contains(@class, 'dropdown') or @role='combobox'])",
70
- textbox: "self::textarea or (self::input and (@type='text' or @type='password' or @type='search' or @type='email')) or @role='textbox'",
57
+ textbox: "self::textarea or (self::input and (@type='text' or @type='password' or @type='search' or @type='email' or @type='number' or @type='tel' or @type='url')) or @role='textbox'",
71
58
  file: "self::input and @type='file'",
72
59
  list: "self::ul or self::ol or @role='list'",
73
60
  listitem: "self::li or @role='listitem'",
@@ -78,10 +65,29 @@ var ElementFinder = (() => {
78
65
  table: "self::table or @role='table'",
79
66
  row: "self::tr or @role='row'",
80
67
  column: "self::td or self::th or @role='cell' or @role='gridcell' or @role='columnheader'",
68
+ cell: "self::td or @role='cell' or @role='gridcell'",
81
69
  image: "self::img or @role='img' or @alt",
82
70
  element: "true()"
83
71
  };
84
72
 
73
+ // src/searchable-attributes.json
74
+ var searchable_attributes_default = [
75
+ "placeholder",
76
+ "value",
77
+ "data-test-id",
78
+ "data-testid",
79
+ "id",
80
+ "resource-id",
81
+ "name",
82
+ "aria-label",
83
+ "hint",
84
+ "title",
85
+ "tooltip",
86
+ "alt",
87
+ "src",
88
+ "aria-labelledby"
89
+ ];
90
+
85
91
  // src/element-finder.js
86
92
  var REGEX_PATTERNS = {
87
93
  selfWithTag: /^self::([a-zA-Z0-9-]+)(?:\[([^\]]+)\])?$/,
@@ -94,29 +100,43 @@ var ElementFinder = (() => {
94
100
  operatorAnd: /^\s*\band\b\s*/i
95
101
  };
96
102
  var MAX_RECURSION_DEPTH = 100;
97
- function parseXPath(expr, el, depth = 0) {
98
- if (expr == null || el == null) {
99
- return false;
103
+ var TYPE_MATCHERS = /* @__PURE__ */ new Map();
104
+ for (const [type, expr] of Object.entries(element_definitions_default)) {
105
+ if (expr === "true()") {
106
+ TYPE_MATCHERS.set(type, () => true);
107
+ } else {
108
+ TYPE_MATCHERS.set(type, (el) => parseXPath(expr, el));
109
+ }
110
+ }
111
+ var SEARCHABLE_ATTRIBUTES = searchable_attributes_default;
112
+ function setSearchableAttributes(attributes) {
113
+ if (!Array.isArray(attributes)) {
114
+ throw new TypeError("attributes must be an array");
100
115
  }
116
+ SEARCHABLE_ATTRIBUTES = attributes;
117
+ }
118
+ function getSearchableAttributes() {
119
+ return [...SEARCHABLE_ATTRIBUTES];
120
+ }
121
+ function parseXPath(expr, el, depth = 0) {
122
+ if (expr == null || el == null) return false;
101
123
  if (depth > MAX_RECURSION_DEPTH) {
102
124
  throw new Error("XPath expression exceeds maximum recursion depth");
103
125
  }
104
126
  expr = expr.trim();
105
127
  if (expr === "true()") return true;
106
- if (expr.startsWith("(") && expr.endsWith(")")) {
107
- let parenDepth = 0;
128
+ if (expr[0] === "(" && expr[expr.length - 1] === ")") {
129
+ let parenDepth = 1;
108
130
  let matchedAll = true;
109
- for (let i = 0; i < expr.length; i++) {
131
+ for (let i = 1; i < expr.length - 1; i++) {
110
132
  if (expr[i] === "(") parenDepth++;
111
133
  else if (expr[i] === ")") parenDepth--;
112
- if (parenDepth === 0 && i < expr.length - 1) {
134
+ if (parenDepth === 0) {
113
135
  matchedAll = false;
114
136
  break;
115
137
  }
116
138
  }
117
- if (matchedAll) {
118
- return parseXPath(expr.slice(1, -1), el, depth + 1);
119
- }
139
+ if (matchedAll) return parseXPath(expr.slice(1, -1), el, depth + 1);
120
140
  }
121
141
  const orParts = splitByOperator(expr, "or");
122
142
  if (orParts.length > 1) {
@@ -171,41 +191,36 @@ var ElementFinder = (() => {
171
191
  return parts;
172
192
  }
173
193
  function parseCondition(expr, el, depth = 0) {
174
- if (expr == null || el == null) {
175
- return false;
176
- }
194
+ if (expr == null || el == null) return false;
177
195
  expr = expr.trim();
178
- let match = expr.match(REGEX_PATTERNS.selfWithTag);
179
- if (match) {
180
- const tagName = match[1].toUpperCase();
196
+ const selfMatch = expr.match(REGEX_PATTERNS.selfWithTag);
197
+ if (selfMatch) {
198
+ const tagName = selfMatch[1].toUpperCase();
181
199
  if (el.tagName !== tagName) return false;
182
- if (match[2]) {
183
- return parseXPath(match[2], el, depth + 1);
184
- }
185
- return true;
200
+ return selfMatch[2] ? parseXPath(selfMatch[2], el, depth + 1) : true;
186
201
  }
187
- match = expr.match(REGEX_PATTERNS.contains);
188
- if (match) {
189
- const attr = el.getAttribute(match[1]) || "";
190
- return attr.toLowerCase().includes(match[2].toLowerCase());
202
+ const containsMatch = expr.match(REGEX_PATTERNS.contains);
203
+ if (containsMatch) {
204
+ const attr = el.getAttribute(containsMatch[1]) || "";
205
+ return attr.toLowerCase().includes(containsMatch[2].toLowerCase());
191
206
  }
192
- match = expr.match(REGEX_PATTERNS.attrEquals);
193
- if (match) {
194
- return el.getAttribute(match[1]) === match[2];
207
+ const attrEqualsMatch = expr.match(REGEX_PATTERNS.attrEquals);
208
+ if (attrEqualsMatch) {
209
+ return el.getAttribute(attrEqualsMatch[1]) === attrEqualsMatch[2];
195
210
  }
196
- match = expr.match(REGEX_PATTERNS.attrExists);
197
- if (match) {
198
- return el.hasAttribute(match[1]);
211
+ const attrExistsMatch = expr.match(REGEX_PATTERNS.attrExists);
212
+ if (attrExistsMatch) {
213
+ return el.hasAttribute(attrExistsMatch[1]);
199
214
  }
200
- match = expr.match(REGEX_PATTERNS.descendant);
201
- if (match) {
202
- return el.querySelector(match[1]) !== null;
215
+ const descendantMatch = expr.match(REGEX_PATTERNS.descendant);
216
+ if (descendantMatch) {
217
+ return el.querySelector(descendantMatch[1]) !== null;
203
218
  }
204
- match = expr.match(REGEX_PATTERNS.ancestor);
205
- if (match) {
219
+ const ancestorMatch = expr.match(REGEX_PATTERNS.ancestor);
220
+ if (ancestorMatch) {
206
221
  let parent = el.parentElement;
207
222
  while (parent) {
208
- if (parseXPath(match[1], parent, depth + 1)) return true;
223
+ if (parseXPath(ancestorMatch[1], parent, depth + 1)) return true;
209
224
  parent = parent.parentElement;
210
225
  }
211
226
  return false;
@@ -213,26 +228,55 @@ var ElementFinder = (() => {
213
228
  return false;
214
229
  }
215
230
  var ELEMENT_DEFINITIONS = Object.freeze(element_definitions_default);
216
- var SEARCHABLE_ATTRIBUTES = searchable_attributes_default;
217
- function setSearchableAttributes(attributes) {
218
- if (!Array.isArray(attributes)) {
219
- throw new TypeError("attributes must be an array");
231
+ function getDirectText(el) {
232
+ let text = "";
233
+ for (let i = 0; i < el.childNodes.length; i++) {
234
+ const node = el.childNodes[i];
235
+ if (node.nodeType === Node.TEXT_NODE) {
236
+ text += node.textContent;
237
+ }
220
238
  }
221
- SEARCHABLE_ATTRIBUTES = attributes;
239
+ return text.trim();
222
240
  }
223
- function getSearchableAttributes() {
224
- return [...SEARCHABLE_ATTRIBUTES];
241
+ function isInsideStyleOrScript(el) {
242
+ if (el.tagName === "STYLE" || el.tagName === "SCRIPT") {
243
+ return true;
244
+ }
245
+ if (el.querySelector("STYLE, SCRIPT")) {
246
+ return true;
247
+ }
248
+ let parent = el.parentElement;
249
+ while (parent) {
250
+ if (parent.tagName === "STYLE" || parent.tagName === "SCRIPT") {
251
+ return true;
252
+ }
253
+ parent = parent.parentElement;
254
+ }
255
+ return false;
225
256
  }
226
- function matchesType(el, type) {
227
- if (el == null) return false;
228
- const expr = ELEMENT_DEFINITIONS[type];
229
- return expr ? parseXPath(expr, el) : false;
257
+ function getAriaLabelledByText(el) {
258
+ const labelledBy = el.getAttribute("aria-labelledby");
259
+ if (!labelledBy) return "";
260
+ const ids = labelledBy.split(/\s+/);
261
+ let text = "";
262
+ for (const id of ids) {
263
+ try {
264
+ const refEl = document.getElementById(id);
265
+ if (refEl) {
266
+ text += refEl.textContent;
267
+ }
268
+ } catch (e) {
269
+ }
270
+ }
271
+ return text;
230
272
  }
231
- function matchesContent(el, value, exact = false) {
273
+ function matchesAttribute(el, value, exact = false) {
232
274
  if (el == null) return false;
233
275
  if (value === void 0 || value === null || value === "") return true;
234
- const normalizedValue = value.toLowerCase().trim();
235
- for (const attr of SEARCHABLE_ATTRIBUTES) {
276
+ if (isInsideStyleOrScript(el)) return false;
277
+ const attrs = SEARCHABLE_ATTRIBUTES;
278
+ for (let i = 0; i < attrs.length; i++) {
279
+ const attr = attrs[i];
236
280
  let attrValue;
237
281
  try {
238
282
  attrValue = el.getAttribute(attr);
@@ -240,81 +284,69 @@ var ElementFinder = (() => {
240
284
  continue;
241
285
  }
242
286
  if (attrValue) {
243
- const normalized = attrValue.toLowerCase().trim();
244
- if (exact ? normalized === normalizedValue : normalized.includes(normalizedValue)) {
287
+ if (attr === "aria-labelledby") {
288
+ if (exact ? attrValue === value : attrValue.includes(value)) {
289
+ return true;
290
+ }
291
+ const resolvedText = getAriaLabelledByText(el);
292
+ if (resolvedText) {
293
+ if (exact ? resolvedText === value : resolvedText.includes(value)) {
294
+ return true;
295
+ }
296
+ }
297
+ } else if (exact ? attrValue === value : attrValue.includes(value)) {
245
298
  return true;
246
299
  }
247
300
  }
248
301
  }
249
- const directText = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent).join("").toLowerCase().trim();
250
- if (exact ? directText === normalizedValue : directText.includes(normalizedValue)) {
302
+ const directText = getDirectText(el);
303
+ if (exact ? directText === value : directText.includes(value)) {
251
304
  return true;
252
305
  }
253
- const textContent = el.textContent.toLowerCase().trim();
254
- if (exact ? textContent === normalizedValue : textContent.includes(normalizedValue)) {
306
+ const textContent = el.textContent;
307
+ if (exact ? textContent.trim() === value : textContent.includes(value)) {
255
308
  return true;
256
309
  }
257
- if (el.tagName === "SELECT") {
258
- const options = el.querySelectorAll("option");
259
- for (const option of options) {
260
- const optionText = option.textContent.toLowerCase().trim();
261
- if (exact ? optionText === normalizedValue : optionText.includes(normalizedValue)) {
262
- return true;
263
- }
264
- }
265
- }
266
310
  return false;
267
311
  }
268
- function getBoundingBox(el) {
269
- const rect = el.getBoundingClientRect();
270
- return {
271
- x: rect.x,
272
- y: rect.y,
273
- width: rect.width,
274
- height: rect.height,
275
- top: rect.top,
276
- bottom: rect.bottom,
277
- left: rect.left,
278
- right: rect.right,
279
- midx: rect.x + rect.width / 2,
280
- midy: rect.y + rect.height / 2,
281
- tagName: el.tagName.toLowerCase()
282
- };
312
+ function matchesType(el, type) {
313
+ if (el == null) return false;
314
+ const matcher = TYPE_MATCHERS.get(type);
315
+ return matcher ? matcher(el) : false;
283
316
  }
284
317
  function getAllElements(root = document) {
285
318
  const elements = [];
319
+ if (root == null) return elements;
286
320
  const rootNode = root.nodeType === Node.DOCUMENT_NODE ? root.documentElement : root;
287
321
  if (!rootNode) return elements;
288
- const walker = (rootNode.ownerDocument || rootNode).createTreeWalker(
289
- rootNode,
290
- NodeFilter.SHOW_ELEMENT,
291
- {
292
- acceptNode: (node2) => {
293
- if (node2.tagName === "SCRIPT" || node2.tagName === "STYLE") {
294
- return NodeFilter.FILTER_REJECT;
295
- }
296
- return NodeFilter.FILTER_ACCEPT;
297
- }
298
- }
299
- );
300
- let node;
301
- while (node = walker.nextNode()) {
322
+ const stack = [rootNode];
323
+ while (stack.length > 0) {
324
+ const node = stack.pop();
325
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
326
+ if (node.tagName === "SCRIPT" || node.tagName === "STYLE") continue;
302
327
  elements.push(node);
328
+ const children = node.children;
329
+ for (let i = children.length - 1; i >= 0; i--) {
330
+ stack.push(children[i]);
331
+ }
303
332
  try {
304
333
  if (node.shadowRoot) {
305
- elements.push(...getAllElements(node.shadowRoot));
334
+ const shadowChildren = node.shadowRoot.children;
335
+ for (let i = shadowChildren.length - 1; i >= 0; i--) {
336
+ stack.push(shadowChildren[i]);
337
+ }
306
338
  }
307
339
  } catch (e) {
308
340
  }
309
341
  }
310
342
  return elements;
311
343
  }
312
- function getAllFrames(root = window, maxFrames = Infinity) {
344
+ function getAllFrames(root = window) {
313
345
  const frames = [];
314
346
  try {
315
347
  frames.push({ window: root, document: root.document, isMainFrame: true, frameIndex: -1 });
316
348
  const iframes = root.document.querySelectorAll("iframe");
317
- for (let i = 0; i < iframes.length && frames.length < maxFrames; i++) {
349
+ for (let i = 0; i < iframes.length; i++) {
318
350
  const iframe = iframes[i];
319
351
  try {
320
352
  if (iframe.contentWindow && iframe.contentDocument) {
@@ -339,69 +371,23 @@ var ElementFinder = (() => {
339
371
  }
340
372
  return frames;
341
373
  }
342
- function expandColumnMatches(matches, text, exact, includeHidden) {
343
- var _a;
344
- if (!text) return matches;
345
- const expandedMatches = [];
346
- const seenElements = /* @__PURE__ */ new Set();
347
- for (const match of matches) {
348
- const el = match.element;
349
- const frame = match.frame;
350
- if (!seenElements.has(el)) {
351
- expandedMatches.push(match);
352
- seenElements.add(el);
353
- }
354
- if (el.tagName.toLowerCase() === "th") {
355
- const table = el.closest("table");
356
- if (!table) continue;
357
- const headerRow = el.closest("tr");
358
- if (!headerRow) continue;
359
- const headerCells = Array.from(headerRow.children);
360
- const colPositions = [];
361
- let currentCol = 0;
362
- for (const cell of headerCells) {
363
- const colspan = parseInt(cell.getAttribute("colspan")) || 1;
364
- colPositions.push({ cell, colStart: currentCol, colEnd: currentCol + colspan - 1 });
365
- currentCol += colspan;
366
- }
367
- const headerInfo = colPositions.find((info) => info.cell === el);
368
- if (!headerInfo) continue;
369
- const allRows = table.querySelectorAll("tr");
370
- for (const row of allRows) {
371
- const cells = Array.from(row.children);
372
- const rowColMap = /* @__PURE__ */ new Map();
373
- let rowCol = 0;
374
- for (const cell of cells) {
375
- const colspan = parseInt(cell.getAttribute("colspan")) || 1;
376
- for (let i = 0; i < colspan; i++) {
377
- rowColMap.set(rowCol + i, cell);
378
- }
379
- rowCol += colspan;
380
- }
381
- for (let col = headerInfo.colStart; col <= headerInfo.colEnd; col++) {
382
- const cell = rowColMap.get(col);
383
- if (!cell) continue;
384
- if (seenElements.has(cell)) continue;
385
- if (!includeHidden) {
386
- const cellWindow = ((_a = cell.ownerDocument) == null ? void 0 : _a.defaultView) || frame.window;
387
- const style = cellWindow.getComputedStyle(cell);
388
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
389
- continue;
390
- }
391
- if (cell.offsetWidth === 0 || cell.offsetHeight === 0) {
392
- continue;
393
- }
394
- }
395
- expandedMatches.push({ element: cell, frame });
396
- seenElements.add(cell);
397
- }
398
- }
399
- }
400
- }
401
- return expandedMatches;
374
+ function getBoundingBox(el) {
375
+ const rect = el.getBoundingClientRect();
376
+ return {
377
+ x: rect.x,
378
+ y: rect.y,
379
+ width: rect.width,
380
+ height: rect.height,
381
+ top: rect.top,
382
+ bottom: rect.bottom,
383
+ left: rect.left,
384
+ right: rect.right,
385
+ midx: rect.x + rect.width / 2,
386
+ midy: rect.y + rect.height / 2,
387
+ tagName: el.tagName.toLowerCase()
388
+ };
402
389
  }
403
- function findElement(type = "element", text = null, exact = false, includeHidden = false, parent = null, maxFrames = Infinity) {
404
- var _a;
390
+ function findElementByType(type = "element", parent = null) {
405
391
  if (type === null || type === void 0) {
406
392
  type = "element";
407
393
  }
@@ -413,22 +399,12 @@ var ElementFinder = (() => {
413
399
  return { elements: [] };
414
400
  }
415
401
  const matches = [];
416
- const frames = getAllFrames(window, maxFrames);
402
+ const frames = getAllFrames(window);
417
403
  for (const frame of frames) {
418
404
  const allElements = getAllElements(parent || frame.document);
419
- for (const el of allElements) {
405
+ for (let i = 0; i < allElements.length; i++) {
406
+ const el = allElements[i];
420
407
  if (type && !matchesType(el, type)) continue;
421
- if (text !== void 0 && !matchesContent(el, text, exact)) continue;
422
- if (!includeHidden) {
423
- const elWindow = ((_a = el.ownerDocument) == null ? void 0 : _a.defaultView) || frame.window;
424
- const style = elWindow.getComputedStyle(el);
425
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
426
- continue;
427
- }
428
- if (el.offsetWidth === 0 || el.offsetHeight === 0) {
429
- continue;
430
- }
431
- }
432
408
  matches.push({ element: el, frame });
433
409
  }
434
410
  }
@@ -441,18 +417,284 @@ var ElementFinder = (() => {
441
417
  const el = match.element;
442
418
  if (!excludedElements.has(el)) {
443
419
  innermostMatches.unshift(match);
444
- let parent2 = el.parentElement;
445
- while (parent2) {
446
- if (matchedElements.has(parent2)) {
447
- excludedElements.add(parent2);
420
+ let parentEl = el.parentElement;
421
+ while (parentEl) {
422
+ if (matchedElements.has(parentEl)) {
423
+ excludedElements.add(parentEl);
424
+ }
425
+ parentEl = parentEl.parentElement;
426
+ }
427
+ }
428
+ }
429
+ }
430
+ const qualified = innermostMatches.map((item) => {
431
+ const boundingBox = getBoundingBox(item.element);
432
+ const tagName = item.element.tagName.toLowerCase();
433
+ if (!item.frame.isMainFrame) {
434
+ return {
435
+ boundingBox,
436
+ tagName,
437
+ frameIndex: item.frame.frameIndex
438
+ };
439
+ }
440
+ return {
441
+ element: item.element,
442
+ boundingBox,
443
+ tagName,
444
+ frameIndex: item.frame.frameIndex
445
+ };
446
+ });
447
+ return { elements: qualified };
448
+ }
449
+ function findElementByAttributes(value, exact = false, parent = null) {
450
+ if (value === null || value === void 0) {
451
+ value = "";
452
+ }
453
+ if (typeof value !== "string") {
454
+ throw new TypeError(`value must be a string, got ${typeof value}`);
455
+ }
456
+ const matches = [];
457
+ const frames = getAllFrames(window);
458
+ for (const frame of frames) {
459
+ const allElements = getAllElements(parent || frame.document);
460
+ for (let i = 0; i < allElements.length; i++) {
461
+ const el = allElements[i];
462
+ if (!matchesAttribute(el, value, exact)) continue;
463
+ matches.push({ element: el, frame });
464
+ }
465
+ }
466
+ const filteredMatches = matches.filter((item) => {
467
+ const el = item.element;
468
+ const hasDirectMatch = hasOwnMatch(el, value, exact);
469
+ if (hasDirectMatch) return true;
470
+ for (const other of matches) {
471
+ if (other.element !== el && el.contains(other.element)) {
472
+ return false;
473
+ }
474
+ }
475
+ return true;
476
+ });
477
+ const qualified = filteredMatches.map((item) => {
478
+ const boundingBox = getBoundingBox(item.element);
479
+ const tagName = item.element.tagName.toLowerCase();
480
+ if (!item.frame.isMainFrame) {
481
+ return {
482
+ boundingBox,
483
+ tagName,
484
+ frameIndex: item.frame.frameIndex
485
+ };
486
+ }
487
+ return {
488
+ element: item.element,
489
+ boundingBox,
490
+ tagName,
491
+ frameIndex: item.frame.frameIndex
492
+ };
493
+ });
494
+ return { elements: qualified };
495
+ }
496
+ function hasOwnMatch(el, value, exact = false) {
497
+ if (value === void 0 || value === null || value === "") return true;
498
+ const attrs = SEARCHABLE_ATTRIBUTES;
499
+ for (let i = 0; i < attrs.length; i++) {
500
+ const attr = attrs[i];
501
+ let attrValue;
502
+ try {
503
+ attrValue = el.getAttribute(attr);
504
+ } catch (e) {
505
+ continue;
506
+ }
507
+ if (attrValue) {
508
+ if (attr === "aria-labelledby") {
509
+ if (exact ? attrValue === value : attrValue.includes(value)) {
510
+ return true;
511
+ }
512
+ const resolvedText = getAriaLabelledByText(el);
513
+ if (resolvedText) {
514
+ if (exact ? resolvedText === value : resolvedText.includes(value)) {
515
+ return true;
448
516
  }
449
- parent2 = parent2.parentElement;
450
517
  }
518
+ } else if (exact ? attrValue === value : attrValue.includes(value)) {
519
+ return true;
451
520
  }
452
521
  }
453
522
  }
454
- const expandedMatches = expandColumnMatches(innermostMatches, text, exact, includeHidden);
455
- const qualified = expandedMatches.map((item) => {
523
+ const directText = getDirectText(el);
524
+ if (exact ? directText === value : directText.includes(value)) {
525
+ return true;
526
+ }
527
+ return false;
528
+ }
529
+ function findElements(type = null, text = null, exact = false, parent = null) {
530
+ if (text === null || text === void 0) {
531
+ text = "";
532
+ }
533
+ if (type !== null && type !== void 0) {
534
+ if (typeof type !== "string") {
535
+ throw new TypeError(`type must be a string, got ${typeof type}`);
536
+ }
537
+ if (!ELEMENT_DEFINITIONS[type]) {
538
+ console.warn(`Unknown element type: ${type}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
539
+ return { elements: [] };
540
+ }
541
+ }
542
+ if (text !== "" && typeof text !== "string") {
543
+ throw new TypeError(`text must be a string, got ${typeof text}`);
544
+ }
545
+ const matches = [];
546
+ const frames = getAllFrames(window);
547
+ for (const frame of frames) {
548
+ const allElements = getAllElements(parent || frame.document);
549
+ for (let i = 0; i < allElements.length; i++) {
550
+ const el = allElements[i];
551
+ if (type !== null && type !== void 0 && !matchesType(el, type)) continue;
552
+ if (text !== "" && !matchesAttribute(el, text, exact)) continue;
553
+ matches.push({ element: el, frame });
554
+ }
555
+ }
556
+ const filteredMatches = text !== "" ? matches.filter((item) => {
557
+ const el = item.element;
558
+ const hasDirectMatch = hasOwnMatch(el, text, exact);
559
+ if (hasDirectMatch) return true;
560
+ for (const other of matches) {
561
+ if (other.element !== el && el.contains(other.element)) {
562
+ return false;
563
+ }
564
+ }
565
+ return true;
566
+ }) : matches;
567
+ const qualified = filteredMatches.map((item) => {
568
+ const boundingBox = getBoundingBox(item.element);
569
+ const tagName = item.element.tagName.toLowerCase();
570
+ if (!item.frame.isMainFrame) {
571
+ return {
572
+ boundingBox,
573
+ tagName,
574
+ frameIndex: item.frame.frameIndex
575
+ };
576
+ }
577
+ return {
578
+ element: item.element,
579
+ boundingBox,
580
+ tagName,
581
+ frameIndex: item.frame.frameIndex
582
+ };
583
+ });
584
+ return { elements: qualified };
585
+ }
586
+ function getParentElement(el) {
587
+ if (el.parentElement) {
588
+ return el.parentElement;
589
+ }
590
+ try {
591
+ const rootNode = el.getRootNode();
592
+ if (rootNode && rootNode.host) {
593
+ return rootNode.host;
594
+ }
595
+ } catch (e) {
596
+ }
597
+ return null;
598
+ }
599
+ function getSiblingElements(el) {
600
+ const parent = getParentElement(el);
601
+ if (!parent) return [];
602
+ if (parent.shadowRoot) {
603
+ try {
604
+ return Array.from(parent.shadowRoot.children);
605
+ } catch (e) {
606
+ return [];
607
+ }
608
+ }
609
+ return Array.from(parent.children);
610
+ }
611
+ function findNearbyElementType(el, targetType) {
612
+ let parent = getParentElement(el);
613
+ while (parent) {
614
+ if (matchesType(parent, targetType)) {
615
+ return parent;
616
+ }
617
+ parent = getParentElement(parent);
618
+ }
619
+ const immediateChildren = el.children || [];
620
+ for (const child of immediateChildren) {
621
+ if (matchesType(child, targetType)) {
622
+ return child;
623
+ }
624
+ }
625
+ const siblings = getSiblingElements(el);
626
+ for (const sibling of siblings) {
627
+ if (sibling !== el && matchesType(sibling, targetType)) {
628
+ return sibling;
629
+ }
630
+ }
631
+ return null;
632
+ }
633
+ function findProbableElements(elementType, attributeText, exact = false, parent = null) {
634
+ const hasType = elementType !== null && elementType !== void 0 && elementType !== "";
635
+ const hasText = attributeText !== null && attributeText !== void 0 && attributeText !== "";
636
+ if (hasType && !hasText) {
637
+ return findElementByType(elementType, parent);
638
+ }
639
+ if (!hasType && hasText) {
640
+ return findElementByAttributes(attributeText, exact, parent);
641
+ }
642
+ if (hasType) {
643
+ if (typeof elementType !== "string") {
644
+ throw new TypeError(`elementType must be a string, got ${typeof elementType}`);
645
+ }
646
+ if (!ELEMENT_DEFINITIONS[elementType]) {
647
+ console.warn(`Unknown element type: ${elementType}. Valid types: ${Object.keys(ELEMENT_DEFINITIONS).join(", ")}`);
648
+ return { elements: [] };
649
+ }
650
+ }
651
+ if (hasText) {
652
+ if (typeof attributeText !== "string") {
653
+ throw new TypeError(`attributeText must be a string, got ${typeof attributeText}`);
654
+ }
655
+ }
656
+ const matches = [];
657
+ const frames = getAllFrames(window);
658
+ for (const frame of frames) {
659
+ const allElements = getAllElements(parent || frame.document);
660
+ for (let i = 0; i < allElements.length; i++) {
661
+ const el = allElements[i];
662
+ if (hasType && !matchesType(el, elementType)) continue;
663
+ if (hasText && !matchesAttribute(el, attributeText, exact)) continue;
664
+ matches.push({ element: el, frame });
665
+ }
666
+ }
667
+ if (matches.length === 0 && hasType && hasText) {
668
+ const attributeMatches = [];
669
+ for (const frame of frames) {
670
+ const allElements = getAllElements(parent || frame.document);
671
+ for (let i = 0; i < allElements.length; i++) {
672
+ const el = allElements[i];
673
+ if (!matchesAttribute(el, attributeText, exact)) continue;
674
+ attributeMatches.push({ element: el, frame });
675
+ }
676
+ }
677
+ const foundElements = /* @__PURE__ */ new Set();
678
+ for (const match of attributeMatches) {
679
+ const nearbyElement = findNearbyElementType(match.element, elementType);
680
+ if (nearbyElement && !foundElements.has(nearbyElement)) {
681
+ foundElements.add(nearbyElement);
682
+ matches.push({ element: nearbyElement, frame: match.frame });
683
+ }
684
+ }
685
+ }
686
+ const filteredMatches = hasText ? matches.filter((item) => {
687
+ const el = item.element;
688
+ const hasDirectMatch = hasOwnMatch(el, attributeText, exact);
689
+ if (hasDirectMatch) return true;
690
+ for (const other of matches) {
691
+ if (other.element !== el && el.contains(other.element)) {
692
+ return false;
693
+ }
694
+ }
695
+ return true;
696
+ }) : matches;
697
+ const qualified = filteredMatches.map((item) => {
456
698
  const boundingBox = getBoundingBox(item.element);
457
699
  const tagName = item.element.tagName.toLowerCase();
458
700
  if (!item.frame.isMainFrame) {
@@ -480,7 +722,8 @@ var ElementFinder = (() => {
480
722
  }
481
723
  function highlight(elements, color = "red", width = 3) {
482
724
  const items = extractElements(elements);
483
- items.forEach((item) => {
725
+ for (let i = 0; i < items.length; i++) {
726
+ const item = items[i];
484
727
  const el = item.element ? item.element : item;
485
728
  if (el && el.style) {
486
729
  el.style.outline = `${width}px solid ${color}`;
@@ -488,11 +731,12 @@ var ElementFinder = (() => {
488
731
  el.style.boxShadow = `0 0 0 2px rgba(255, 255, 255, 0.8)`;
489
732
  el.classList.add("elementfinder-highlighted");
490
733
  }
491
- });
734
+ }
492
735
  }
493
736
  function unhighlight(elements) {
494
737
  const items = extractElements(elements);
495
- items.forEach((item) => {
738
+ for (let i = 0; i < items.length; i++) {
739
+ const item = items[i];
496
740
  const el = item.element ? item.element : item;
497
741
  if (el && el.style) {
498
742
  el.style.outline = "";
@@ -500,10 +744,13 @@ var ElementFinder = (() => {
500
744
  el.style.boxShadow = "";
501
745
  el.classList.remove("elementfinder-highlighted");
502
746
  }
503
- });
747
+ }
504
748
  }
505
749
  function getValidTypes() {
506
750
  return Object.keys(ELEMENT_DEFINITIONS);
507
751
  }
752
+ function getValidAttributes() {
753
+ return [...SEARCHABLE_ATTRIBUTES];
754
+ }
508
755
  return __toCommonJS(element_finder_exports);
509
756
  })();