@mcp-b/smart-dom-reader-server 0.1.0

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.
@@ -0,0 +1,1917 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) =>
6
+ typeof require !== 'undefined'
7
+ ? require
8
+ : typeof Proxy !== 'undefined'
9
+ ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== 'undefined' ? require : a)[b],
11
+ })
12
+ : x)(function (x) {
13
+ if (typeof require !== 'undefined') return require.apply(this, arguments);
14
+ throw Error(`Dynamic require of "${x}" is not supported`);
15
+ });
16
+ var __esm = (fn, res) =>
17
+ function __init() {
18
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])((fn = 0))), res;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all) __defProp(target, name, { get: all[name], enumerable: true });
22
+ };
23
+ var __copyProps = (to, from, except, desc) => {
24
+ if ((from && typeof from === 'object') || typeof from === 'function') {
25
+ for (const key of __getOwnPropNames(from))
26
+ if (!__hasOwnProp.call(to, key) && key !== except)
27
+ __defProp(to, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
30
+ });
31
+ }
32
+ return to;
33
+ };
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, '__esModule', { value: true }), mod);
35
+
36
+ // src/content-detection.ts
37
+ var ContentDetection;
38
+ var init_content_detection = __esm({
39
+ 'src/content-detection.ts'() {
40
+ ContentDetection = class {
41
+ /**
42
+ * Find the main content area of a page
43
+ * Inspired by dom-to-semantic-markdown's approach
44
+ */
45
+ static findMainContent(doc) {
46
+ const mainElement = doc.querySelector('main, [role="main"]');
47
+ if (mainElement) {
48
+ return mainElement;
49
+ }
50
+ if (!doc.body) {
51
+ return doc.documentElement;
52
+ }
53
+ return this.detectMainContent(doc.body);
54
+ }
55
+ /**
56
+ * Detect main content using scoring algorithm
57
+ */
58
+ static detectMainContent(rootElement) {
59
+ const candidates = [];
60
+ const minScore = 15;
61
+ this.collectCandidates(rootElement, candidates, minScore);
62
+ if (candidates.length === 0) {
63
+ return rootElement;
64
+ }
65
+ candidates.sort((a, b) => this.calculateContentScore(b) - this.calculateContentScore(a));
66
+ let bestCandidate = candidates[0];
67
+ for (let i = 1; i < candidates.length; i++) {
68
+ const isIndependent = !candidates.some(
69
+ (other, j) => j !== i && other.contains(candidates[i])
70
+ );
71
+ if (
72
+ isIndependent &&
73
+ this.calculateContentScore(candidates[i]) > this.calculateContentScore(bestCandidate)
74
+ ) {
75
+ bestCandidate = candidates[i];
76
+ }
77
+ }
78
+ return bestCandidate;
79
+ }
80
+ /**
81
+ * Collect content candidates
82
+ */
83
+ static collectCandidates(element, candidates, minScore) {
84
+ const score = this.calculateContentScore(element);
85
+ if (score >= minScore) {
86
+ candidates.push(element);
87
+ }
88
+ Array.from(element.children).forEach((child) => {
89
+ this.collectCandidates(child, candidates, minScore);
90
+ });
91
+ }
92
+ /**
93
+ * Calculate content score for an element
94
+ */
95
+ static calculateContentScore(element) {
96
+ let score = 0;
97
+ const semanticClasses = [
98
+ 'article',
99
+ 'content',
100
+ 'main-container',
101
+ 'main',
102
+ 'main-content',
103
+ 'post',
104
+ 'entry',
105
+ ];
106
+ const semanticIds = ['content', 'main', 'article', 'post', 'entry'];
107
+ semanticClasses.forEach((cls) => {
108
+ if (element.classList.contains(cls)) {
109
+ score += 10;
110
+ }
111
+ });
112
+ semanticIds.forEach((id) => {
113
+ if (element.id?.toLowerCase().includes(id)) {
114
+ score += 10;
115
+ }
116
+ });
117
+ const tag = element.tagName.toLowerCase();
118
+ const highValueTags = ['article', 'main', 'section'];
119
+ if (highValueTags.includes(tag)) {
120
+ score += 8;
121
+ }
122
+ const paragraphs = element.getElementsByTagName('p').length;
123
+ score += Math.min(paragraphs * 2, 10);
124
+ const headings = element.querySelectorAll('h1, h2, h3').length;
125
+ score += Math.min(headings * 3, 9);
126
+ const textLength = element.textContent?.trim().length || 0;
127
+ if (textLength > 300) {
128
+ score += Math.min(Math.floor(textLength / 300) * 2, 10);
129
+ }
130
+ const linkDensity = this.calculateLinkDensity(element);
131
+ if (linkDensity < 0.3) {
132
+ score += 5;
133
+ } else if (linkDensity > 0.5) {
134
+ score -= 5;
135
+ }
136
+ if (
137
+ element.hasAttribute('data-main') ||
138
+ element.hasAttribute('data-content') ||
139
+ element.hasAttribute('itemprop')
140
+ ) {
141
+ score += 8;
142
+ }
143
+ const role = element.getAttribute('role');
144
+ if (role === 'main' || role === 'article') {
145
+ score += 10;
146
+ }
147
+ if (
148
+ element.matches(
149
+ 'aside, nav, header, footer, .sidebar, .navigation, .menu, .ad, .advertisement'
150
+ )
151
+ ) {
152
+ score -= 10;
153
+ }
154
+ const forms = element.getElementsByTagName('form').length;
155
+ if (forms > 2) {
156
+ score -= 5;
157
+ }
158
+ return Math.max(0, score);
159
+ }
160
+ /**
161
+ * Calculate link density in an element
162
+ */
163
+ static calculateLinkDensity(element) {
164
+ const links = element.getElementsByTagName('a');
165
+ let linkTextLength = 0;
166
+ for (const link of Array.from(links)) {
167
+ linkTextLength += link.textContent?.length || 0;
168
+ }
169
+ const totalTextLength = element.textContent?.length || 1;
170
+ return linkTextLength / totalTextLength;
171
+ }
172
+ /**
173
+ * Check if an element is likely navigation
174
+ */
175
+ static isNavigation(element) {
176
+ const tag = element.tagName.toLowerCase();
177
+ if (tag === 'nav' || element.getAttribute('role') === 'navigation') {
178
+ return true;
179
+ }
180
+ const navPatterns = [/nav/i, /menu/i, /sidebar/i, /toolbar/i];
181
+ const classesAndId = `${element.className} ${element.id}`.toLowerCase();
182
+ return navPatterns.some((pattern) => pattern.test(classesAndId));
183
+ }
184
+ /**
185
+ * Check if element is likely supplementary content
186
+ */
187
+ static isSupplementary(element) {
188
+ const tag = element.tagName.toLowerCase();
189
+ if (tag === 'aside' || element.getAttribute('role') === 'complementary') {
190
+ return true;
191
+ }
192
+ const supplementaryPatterns = [
193
+ /sidebar/i,
194
+ /widget/i,
195
+ /related/i,
196
+ /advertisement/i,
197
+ /social/i,
198
+ ];
199
+ const classesAndId = `${element.className} ${element.id}`.toLowerCase();
200
+ return supplementaryPatterns.some((pattern) => pattern.test(classesAndId));
201
+ }
202
+ /**
203
+ * Detect page landmarks
204
+ */
205
+ static detectLandmarks(doc) {
206
+ const landmarks = {
207
+ navigation: [],
208
+ main: [],
209
+ complementary: [],
210
+ contentinfo: [],
211
+ banner: [],
212
+ search: [],
213
+ form: [],
214
+ region: [],
215
+ };
216
+ const landmarkSelectors = {
217
+ navigation: 'nav, [role="navigation"]',
218
+ main: 'main, [role="main"]',
219
+ complementary: 'aside, [role="complementary"]',
220
+ contentinfo: 'footer, [role="contentinfo"]',
221
+ banner: 'header, [role="banner"]',
222
+ search: '[role="search"]',
223
+ form: 'form[aria-label], form[aria-labelledby], [role="form"]',
224
+ region: 'section[aria-label], section[aria-labelledby], [role="region"]',
225
+ };
226
+ for (const [landmark, selector] of Object.entries(landmarkSelectors)) {
227
+ const elements = doc.querySelectorAll(selector);
228
+ landmarks[landmark] = Array.from(elements);
229
+ }
230
+ return landmarks;
231
+ }
232
+ };
233
+ },
234
+ });
235
+
236
+ // src/selectors.ts
237
+ var SelectorGenerator;
238
+ var init_selectors = __esm({
239
+ 'src/selectors.ts'() {
240
+ SelectorGenerator = class {
241
+ /**
242
+ * Generate multiple selector strategies for an element
243
+ */
244
+ static generateSelectors(element) {
245
+ const doc = element.ownerDocument || document;
246
+ const candidates = [];
247
+ if (element.id && this.isUniqueId(element.id, doc)) {
248
+ candidates.push({ type: 'id', value: `#${CSS.escape(element.id)}`, score: 100 });
249
+ }
250
+ const testId = this.getDataTestId(element);
251
+ if (testId) {
252
+ const v = `[data-testid="${CSS.escape(testId)}"]`;
253
+ candidates.push({
254
+ type: 'data-testid',
255
+ value: v,
256
+ score: 90 + (this.isUniqueSelectorSafe(v, doc) ? 5 : 0),
257
+ });
258
+ }
259
+ const role = element.getAttribute('role');
260
+ const aria = element.getAttribute('aria-label');
261
+ if (role && aria) {
262
+ const v = `[role="${CSS.escape(role)}"][aria-label="${CSS.escape(aria)}"]`;
263
+ candidates.push({
264
+ type: 'role-aria',
265
+ value: v,
266
+ score: 85 + (this.isUniqueSelectorSafe(v, doc) ? 5 : 0),
267
+ });
268
+ }
269
+ const nameAttr = element.getAttribute('name');
270
+ if (nameAttr) {
271
+ const v = `[name="${CSS.escape(nameAttr)}"]`;
272
+ candidates.push({
273
+ type: 'name',
274
+ value: v,
275
+ score: 78 + (this.isUniqueSelectorSafe(v, doc) ? 5 : 0),
276
+ });
277
+ }
278
+ const pathCss = this.generateCSSSelector(element, doc);
279
+ const structuralPenalty = (pathCss.match(/:nth-child\(/g) || []).length * 10;
280
+ const classBonus = pathCss.includes('.') ? 8 : 0;
281
+ const pathScore = Math.max(0, 70 + classBonus - structuralPenalty);
282
+ candidates.push({ type: 'class-path', value: pathCss, score: pathScore });
283
+ const xpath = this.generateXPath(element, doc);
284
+ candidates.push({ type: 'xpath', value: xpath, score: 40 });
285
+ const textBased = this.generateTextBasedSelector(element);
286
+ if (textBased) candidates.push({ type: 'text', value: textBased, score: 30 });
287
+ candidates.sort((a, b) => b.score - a.score);
288
+ const bestCss =
289
+ candidates.find((c) => c.type !== 'xpath' && c.type !== 'text')?.value || pathCss;
290
+ return {
291
+ css: bestCss,
292
+ xpath,
293
+ textBased,
294
+ dataTestId: testId || void 0,
295
+ ariaLabel: aria || void 0,
296
+ candidates,
297
+ };
298
+ }
299
+ /**
300
+ * Generate a unique CSS selector for an element
301
+ */
302
+ static generateCSSSelector(element, doc) {
303
+ if (element.id && this.isUniqueId(element.id, doc)) {
304
+ return `#${CSS.escape(element.id)}`;
305
+ }
306
+ const testId = this.getDataTestId(element);
307
+ if (testId) {
308
+ return `[data-testid="${CSS.escape(testId)}"]`;
309
+ }
310
+ const path = [];
311
+ let current = element;
312
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
313
+ let selector = current.nodeName.toLowerCase();
314
+ if (current.id && this.isUniqueId(current.id, doc)) {
315
+ selector = `#${CSS.escape(current.id)}`;
316
+ path.unshift(selector);
317
+ break;
318
+ }
319
+ const classes = this.getMeaningfulClasses(current);
320
+ if (classes.length > 0) {
321
+ selector += `.${classes.map((c) => CSS.escape(c)).join('.')}`;
322
+ }
323
+ const siblings = current.parentElement?.children;
324
+ if (siblings && siblings.length > 1) {
325
+ const index = Array.from(siblings).indexOf(current);
326
+ if (index > 0 || !this.isUniqueSelector(selector, current.parentElement)) {
327
+ selector += `:nth-child(${index + 1})`;
328
+ }
329
+ }
330
+ path.unshift(selector);
331
+ current = current.parentElement;
332
+ }
333
+ return this.optimizePath(path, element, doc);
334
+ }
335
+ /**
336
+ * Generate XPath for an element
337
+ */
338
+ static generateXPath(element, doc) {
339
+ if (element.id && this.isUniqueId(element.id, doc)) {
340
+ return `//*[@id="${element.id}"]`;
341
+ }
342
+ const path = [];
343
+ let current = element;
344
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
345
+ const tagName = current.nodeName.toLowerCase();
346
+ if (current.id && this.isUniqueId(current.id, doc)) {
347
+ path.unshift(`//*[@id="${current.id}"]`);
348
+ break;
349
+ }
350
+ let xpath = tagName;
351
+ const siblings = current.parentElement?.children;
352
+ if (siblings) {
353
+ const sameTagSiblings = Array.from(siblings).filter(
354
+ (s) => s.nodeName.toLowerCase() === tagName
355
+ );
356
+ if (sameTagSiblings.length > 1) {
357
+ const index = sameTagSiblings.indexOf(current) + 1;
358
+ xpath += `[${index}]`;
359
+ }
360
+ }
361
+ path.unshift(xpath);
362
+ current = current.parentElement;
363
+ }
364
+ return `//${path.join('/')}`;
365
+ }
366
+ /**
367
+ * Generate a text-based selector for buttons and links
368
+ */
369
+ static generateTextBasedSelector(element) {
370
+ const text = element.textContent?.trim();
371
+ if (!text || text.length > 50) return void 0;
372
+ const tag = element.nodeName.toLowerCase();
373
+ if (['button', 'a', 'label'].includes(tag)) {
374
+ const escapedText = text.replace(/['"\\]/g, '\\$&');
375
+ return `${tag}:contains("${escapedText}")`;
376
+ }
377
+ return void 0;
378
+ }
379
+ /**
380
+ * Get data-testid or similar attributes
381
+ */
382
+ static getDataTestId(element) {
383
+ return (
384
+ element.getAttribute('data-testid') ||
385
+ element.getAttribute('data-test-id') ||
386
+ element.getAttribute('data-test') ||
387
+ element.getAttribute('data-cy') ||
388
+ void 0
389
+ );
390
+ }
391
+ /**
392
+ * Check if an ID is unique in the document
393
+ */
394
+ static isUniqueId(id, doc) {
395
+ return doc.querySelectorAll(`#${CSS.escape(id)}`).length === 1;
396
+ }
397
+ /**
398
+ * Check if a selector is unique within a container
399
+ */
400
+ static isUniqueSelector(selector, container) {
401
+ try {
402
+ return container.querySelectorAll(selector).length === 1;
403
+ } catch {
404
+ return false;
405
+ }
406
+ }
407
+ static isUniqueSelectorSafe(selector, doc) {
408
+ try {
409
+ return doc.querySelectorAll(selector).length === 1;
410
+ } catch {
411
+ return false;
412
+ }
413
+ }
414
+ /**
415
+ * Get meaningful classes (filtering out utility classes)
416
+ */
417
+ static getMeaningfulClasses(element) {
418
+ const classes = Array.from(element.classList);
419
+ const utilityPatterns = [
420
+ /^(p|m|w|h|text|bg|border|flex|grid|col|row)-/,
421
+ /^(xs|sm|md|lg|xl|2xl):/,
422
+ /^(hover|focus|active|disabled|checked):/,
423
+ /^js-/,
424
+ /^is-/,
425
+ /^has-/,
426
+ ];
427
+ return classes
428
+ .filter((cls) => {
429
+ if (cls.length < 3) return false;
430
+ return !utilityPatterns.some((pattern) => pattern.test(cls));
431
+ })
432
+ .slice(0, 2);
433
+ }
434
+ /**
435
+ * Optimize the selector path by removing unnecessary parts
436
+ */
437
+ static optimizePath(path, element, doc) {
438
+ for (let i = 0; i < path.length - 1; i++) {
439
+ const shortPath = path.slice(i).join(' > ');
440
+ try {
441
+ const matches = doc.querySelectorAll(shortPath);
442
+ if (matches.length === 1 && matches[0] === element) {
443
+ return shortPath;
444
+ }
445
+ } catch {}
446
+ }
447
+ return path.join(' > ');
448
+ }
449
+ /**
450
+ * Get a human-readable path description
451
+ */
452
+ static getContextPath(element) {
453
+ const path = [];
454
+ let current = element;
455
+ let depth = 0;
456
+ const maxDepth = 5;
457
+ while (current && current !== element.ownerDocument?.body && depth < maxDepth) {
458
+ const tag = current.nodeName.toLowerCase();
459
+ let descriptor = tag;
460
+ if (current.id) {
461
+ descriptor = `${tag}#${current.id}`;
462
+ } else if (current.className && typeof current.className === 'string') {
463
+ const firstClass = current.className.split(' ')[0];
464
+ if (firstClass) {
465
+ descriptor = `${tag}.${firstClass}`;
466
+ }
467
+ }
468
+ const role = current.getAttribute('role');
469
+ if (role) {
470
+ descriptor += `[role="${role}"]`;
471
+ }
472
+ path.unshift(descriptor);
473
+ current = current.parentElement;
474
+ depth++;
475
+ }
476
+ return path;
477
+ }
478
+ };
479
+ },
480
+ });
481
+
482
+ // src/traversal.ts
483
+ var DOMTraversal;
484
+ var init_traversal = __esm({
485
+ 'src/traversal.ts'() {
486
+ init_selectors();
487
+ DOMTraversal = class {
488
+ static INTERACTIVE_SELECTORS = [
489
+ 'button',
490
+ 'a[href]',
491
+ 'input:not([type="hidden"])',
492
+ 'textarea',
493
+ 'select',
494
+ '[role="button"]',
495
+ '[onclick]',
496
+ '[contenteditable="true"]',
497
+ 'summary',
498
+ '[tabindex]:not([tabindex="-1"])',
499
+ ];
500
+ static SEMANTIC_SELECTORS = [
501
+ 'h1',
502
+ 'h2',
503
+ 'h3',
504
+ 'h4',
505
+ 'h5',
506
+ 'h6',
507
+ 'article',
508
+ 'section',
509
+ 'nav',
510
+ 'aside',
511
+ 'main',
512
+ 'header',
513
+ 'footer',
514
+ 'form',
515
+ 'table',
516
+ 'ul',
517
+ 'ol',
518
+ 'img[alt]',
519
+ 'figure',
520
+ 'video',
521
+ 'audio',
522
+ '[role="navigation"]',
523
+ '[role="main"]',
524
+ '[role="complementary"]',
525
+ '[role="contentinfo"]',
526
+ ];
527
+ /**
528
+ * Check if element is visible
529
+ */
530
+ static isVisible(element, computedStyle) {
531
+ const rect = element.getBoundingClientRect();
532
+ const style =
533
+ computedStyle || element.ownerDocument?.defaultView?.getComputedStyle(element);
534
+ if (!style) return false;
535
+ return !!(
536
+ rect.width > 0 &&
537
+ rect.height > 0 &&
538
+ style.display !== 'none' &&
539
+ style.visibility !== 'hidden' &&
540
+ style.opacity !== '0' &&
541
+ element.offsetParent !== null
542
+ );
543
+ }
544
+ /**
545
+ * Check if element is in viewport
546
+ */
547
+ static isInViewport(element, viewport) {
548
+ const rect = element.getBoundingClientRect();
549
+ const view = viewport || {
550
+ width: element.ownerDocument?.defaultView?.innerWidth || 0,
551
+ height: element.ownerDocument?.defaultView?.innerHeight || 0,
552
+ };
553
+ return (
554
+ rect.top < view.height && rect.bottom > 0 && rect.left < view.width && rect.right > 0
555
+ );
556
+ }
557
+ /**
558
+ * Check if element passes filter criteria
559
+ */
560
+ static passesFilter(element, filter) {
561
+ if (!filter) return true;
562
+ const htmlElement = element;
563
+ if (filter.excludeSelectors?.length) {
564
+ for (const selector of filter.excludeSelectors) {
565
+ if (element.matches(selector)) return false;
566
+ }
567
+ }
568
+ if (filter.includeSelectors?.length) {
569
+ let matches = false;
570
+ for (const selector of filter.includeSelectors) {
571
+ if (element.matches(selector)) {
572
+ matches = true;
573
+ break;
574
+ }
575
+ }
576
+ if (!matches) return false;
577
+ }
578
+ if (filter.tags?.length && !filter.tags.includes(element.tagName.toLowerCase())) {
579
+ return false;
580
+ }
581
+ const textContent = htmlElement.textContent?.toLowerCase() || '';
582
+ if (filter.textContains?.length) {
583
+ let hasText = false;
584
+ for (const text of filter.textContains) {
585
+ if (textContent.includes(text.toLowerCase())) {
586
+ hasText = true;
587
+ break;
588
+ }
589
+ }
590
+ if (!hasText) return false;
591
+ }
592
+ if (filter.textMatches?.length) {
593
+ let matches = false;
594
+ for (const pattern of filter.textMatches) {
595
+ if (pattern.test(textContent)) {
596
+ matches = true;
597
+ break;
598
+ }
599
+ }
600
+ if (!matches) return false;
601
+ }
602
+ if (filter.hasAttributes?.length) {
603
+ for (const attr of filter.hasAttributes) {
604
+ if (!element.hasAttribute(attr)) return false;
605
+ }
606
+ }
607
+ if (filter.attributeValues) {
608
+ for (const [attr, value] of Object.entries(filter.attributeValues)) {
609
+ const attrValue = element.getAttribute(attr);
610
+ if (!attrValue) return false;
611
+ if (typeof value === 'string') {
612
+ if (attrValue !== value) return false;
613
+ } else if (value instanceof RegExp) {
614
+ if (!value.test(attrValue)) return false;
615
+ }
616
+ }
617
+ }
618
+ if (filter.withinSelectors?.length) {
619
+ let isWithin = false;
620
+ for (const selector of filter.withinSelectors) {
621
+ if (element.closest(selector)) {
622
+ isWithin = true;
623
+ break;
624
+ }
625
+ }
626
+ if (!isWithin) return false;
627
+ }
628
+ if (filter.interactionTypes?.length) {
629
+ const interaction = this.getInteractionInfo(element);
630
+ let hasInteraction = false;
631
+ for (const type of filter.interactionTypes) {
632
+ if (interaction[type]) {
633
+ hasInteraction = true;
634
+ break;
635
+ }
636
+ }
637
+ if (!hasInteraction) return false;
638
+ }
639
+ if (filter.nearText) {
640
+ const parent = element.parentElement;
641
+ if (
642
+ !parent ||
643
+ !parent.textContent?.toLowerCase().includes(filter.nearText.toLowerCase())
644
+ ) {
645
+ return false;
646
+ }
647
+ }
648
+ return true;
649
+ }
650
+ /**
651
+ * Extract element information
652
+ */
653
+ static extractElement(element, options, depth = 0) {
654
+ if (options.maxDepth && depth > options.maxDepth) {
655
+ return null;
656
+ }
657
+ if (!options.includeHidden && !this.isVisible(element)) {
658
+ return null;
659
+ }
660
+ if (options.viewportOnly && !this.isInViewport(element)) {
661
+ return null;
662
+ }
663
+ if (!this.passesFilter(element, options.filter)) {
664
+ return null;
665
+ }
666
+ const htmlElement = element;
667
+ const extracted = {
668
+ tag: element.tagName.toLowerCase(),
669
+ text: this.getElementText(element, options),
670
+ selector: SelectorGenerator.generateSelectors(element),
671
+ attributes: this.getRelevantAttributes(element, options),
672
+ context: this.getElementContext(element),
673
+ interaction: this.getInteractionInfo(element),
674
+ // bounds removed to save tokens
675
+ };
676
+ if (options.mode === 'full' && this.isSemanticContainer(element)) {
677
+ const children = [];
678
+ if (options.includeShadowDOM && htmlElement.shadowRoot) {
679
+ const shadowChildren = this.extractChildren(htmlElement.shadowRoot, options, depth + 1);
680
+ children.push(...shadowChildren);
681
+ }
682
+ const regularChildren = this.extractChildren(element, options, depth + 1);
683
+ children.push(...regularChildren);
684
+ if (children.length > 0) {
685
+ extracted.children = children;
686
+ }
687
+ }
688
+ return extracted;
689
+ }
690
+ /**
691
+ * Extract children elements
692
+ */
693
+ static extractChildren(container, options, depth) {
694
+ const children = [];
695
+ const elements = container.querySelectorAll('*');
696
+ for (const child of Array.from(elements)) {
697
+ if (this.hasExtractedAncestor(child, elements)) {
698
+ continue;
699
+ }
700
+ const extracted = this.extractElement(child, options, depth);
701
+ if (extracted) {
702
+ children.push(extracted);
703
+ }
704
+ }
705
+ return children;
706
+ }
707
+ /**
708
+ * Check if element has an ancestor that was already extracted
709
+ */
710
+ static hasExtractedAncestor(element, extractedElements) {
711
+ let parent = element.parentElement;
712
+ while (parent) {
713
+ if (Array.from(extractedElements).includes(parent)) {
714
+ return true;
715
+ }
716
+ parent = parent.parentElement;
717
+ }
718
+ return false;
719
+ }
720
+ /**
721
+ * Get relevant attributes for an element
722
+ */
723
+ static getRelevantAttributes(element, options) {
724
+ const relevant = [
725
+ 'id',
726
+ 'class',
727
+ 'name',
728
+ 'type',
729
+ 'value',
730
+ 'placeholder',
731
+ 'href',
732
+ 'src',
733
+ 'alt',
734
+ 'title',
735
+ 'action',
736
+ 'method',
737
+ 'aria-label',
738
+ 'aria-describedby',
739
+ 'aria-controls',
740
+ 'role',
741
+ 'disabled',
742
+ 'readonly',
743
+ 'required',
744
+ 'checked',
745
+ 'min',
746
+ 'max',
747
+ 'pattern',
748
+ 'step',
749
+ 'autocomplete',
750
+ 'data-testid',
751
+ 'data-test',
752
+ 'data-cy',
753
+ ];
754
+ const attributes = {};
755
+ const attrTruncate = options.attributeTruncateLength ?? 100;
756
+ const dataAttrTruncate = options.dataAttributeTruncateLength ?? 50;
757
+ for (const attr of relevant) {
758
+ const value = element.getAttribute(attr);
759
+ if (value) {
760
+ attributes[attr] =
761
+ value.length > attrTruncate ? `${value.substring(0, attrTruncate)}...` : value;
762
+ }
763
+ }
764
+ for (const attr of element.attributes) {
765
+ if (attr.name.startsWith('data-') && !relevant.includes(attr.name)) {
766
+ attributes[attr.name] =
767
+ attr.value.length > dataAttrTruncate
768
+ ? `${attr.value.substring(0, dataAttrTruncate)}...`
769
+ : attr.value;
770
+ }
771
+ }
772
+ return attributes;
773
+ }
774
+ /**
775
+ * Get element context information
776
+ */
777
+ static getElementContext(element) {
778
+ const context = {
779
+ parentChain: SelectorGenerator.getContextPath(element),
780
+ };
781
+ const form = element.closest('form');
782
+ if (form) {
783
+ context.nearestForm = SelectorGenerator.generateSelectors(form).css;
784
+ }
785
+ const section = element.closest('section, [role="region"]');
786
+ if (section) {
787
+ context.nearestSection = SelectorGenerator.generateSelectors(section).css;
788
+ }
789
+ const main = element.closest('main, [role="main"]');
790
+ if (main) {
791
+ context.nearestMain = SelectorGenerator.generateSelectors(main).css;
792
+ }
793
+ const nav = element.closest('nav, [role="navigation"]');
794
+ if (nav) {
795
+ context.nearestNav = SelectorGenerator.generateSelectors(nav).css;
796
+ }
797
+ return context;
798
+ }
799
+ /**
800
+ * Get interaction information for an element (compact format)
801
+ */
802
+ static getInteractionInfo(element) {
803
+ const htmlElement = element;
804
+ const interaction = {};
805
+ const hasClickHandler = !!(
806
+ htmlElement.onclick ||
807
+ element.getAttribute('onclick') ||
808
+ element.matches('button, a[href], [role="button"], [tabindex]:not([tabindex="-1"])')
809
+ );
810
+ if (hasClickHandler) interaction.click = true;
811
+ const hasChangeHandler = !!(
812
+ htmlElement.onchange ||
813
+ element.getAttribute('onchange') ||
814
+ element.matches('input, select, textarea')
815
+ );
816
+ if (hasChangeHandler) interaction.change = true;
817
+ const hasSubmitHandler = !!(
818
+ htmlElement.onsubmit ||
819
+ element.getAttribute('onsubmit') ||
820
+ element.matches('form')
821
+ );
822
+ if (hasSubmitHandler) interaction.submit = true;
823
+ const triggersNavigation = element.matches('a[href], button[type="submit"]');
824
+ if (triggersNavigation) interaction.nav = true;
825
+ const isDisabled =
826
+ htmlElement.hasAttribute('disabled') ||
827
+ htmlElement.getAttribute('aria-disabled') === 'true';
828
+ if (isDisabled) interaction.disabled = true;
829
+ const isHidden = !this.isVisible(element);
830
+ if (isHidden) interaction.hidden = true;
831
+ const ariaRole = element.getAttribute('role');
832
+ if (ariaRole) interaction.role = ariaRole;
833
+ if (element.matches('input, textarea, select, button')) {
834
+ const form = element.form || element.closest('form');
835
+ if (form) {
836
+ interaction.form = SelectorGenerator.generateSelectors(form).css;
837
+ }
838
+ }
839
+ return interaction;
840
+ }
841
+ /**
842
+ * Get text content of an element (limited length)
843
+ */
844
+ static getElementText(element, options) {
845
+ if (element.matches('input, textarea')) {
846
+ const input = element;
847
+ return input.value || input.placeholder || '';
848
+ }
849
+ if (element.matches('img')) {
850
+ return element.alt || '';
851
+ }
852
+ const text = element.textContent?.trim() || '';
853
+ const maxLength = options?.textTruncateLength;
854
+ if (maxLength && text.length > maxLength) {
855
+ return `${text.substring(0, maxLength)}...`;
856
+ }
857
+ return text;
858
+ }
859
+ /**
860
+ * Check if element is a semantic container
861
+ */
862
+ static isSemanticContainer(element) {
863
+ return element.matches(
864
+ 'article, section, nav, aside, main, header, footer, form, table, ul, ol, dl, figure, details, dialog, [role="region"], [role="navigation"], [role="main"], [role="complementary"]'
865
+ );
866
+ }
867
+ /**
868
+ * Get interactive elements
869
+ */
870
+ static getInteractiveElements(container, options) {
871
+ const elements = [];
872
+ const selector = this.INTERACTIVE_SELECTORS.join(', ');
873
+ const found = container.querySelectorAll(selector);
874
+ for (const element of Array.from(found)) {
875
+ const extracted = this.extractElement(element, options);
876
+ if (extracted) {
877
+ elements.push(extracted);
878
+ }
879
+ }
880
+ if (options.customSelectors) {
881
+ for (const customSelector of options.customSelectors) {
882
+ try {
883
+ const customFound = container.querySelectorAll(customSelector);
884
+ for (const element of Array.from(customFound)) {
885
+ const extracted = this.extractElement(element, options);
886
+ if (extracted) {
887
+ elements.push(extracted);
888
+ }
889
+ }
890
+ } catch (_e) {
891
+ console.warn(`Invalid custom selector: ${customSelector}`);
892
+ }
893
+ }
894
+ }
895
+ return elements;
896
+ }
897
+ /**
898
+ * Get semantic elements (for full mode)
899
+ */
900
+ static getSemanticElements(container, options) {
901
+ const elements = [];
902
+ const selector = this.SEMANTIC_SELECTORS.join(', ');
903
+ const found = container.querySelectorAll(selector);
904
+ for (const element of Array.from(found)) {
905
+ const extracted = this.extractElement(element, options);
906
+ if (extracted) {
907
+ elements.push(extracted);
908
+ }
909
+ }
910
+ return elements;
911
+ }
912
+ };
913
+ },
914
+ });
915
+
916
+ // src/markdown-formatter.ts
917
+ function truncate(text, len) {
918
+ const t = (text ?? '').trim();
919
+ if (!len || t.length <= len) return t;
920
+ const keywords = [
921
+ 'login',
922
+ 'log in',
923
+ 'sign in',
924
+ 'sign up',
925
+ 'submit',
926
+ 'search',
927
+ 'filter',
928
+ 'add to cart',
929
+ 'next',
930
+ 'continue',
931
+ ];
932
+ const lower = t.toLowerCase();
933
+ const hit = keywords.map((k) => ({ k, i: lower.indexOf(k) })).find((x) => x.i > -1);
934
+ const head = Math.max(0, Math.floor(len * 0.66));
935
+ if (hit && hit.i > head) {
936
+ const tailWindow = Math.max(12, len - head - 5);
937
+ const start = Math.max(0, hit.i - Math.floor(tailWindow / 2));
938
+ const end = Math.min(t.length, start + tailWindow);
939
+ return `${t.slice(0, head).trimEnd()} \u2026 ${t.slice(start, end).trim()}\u2026`;
940
+ }
941
+ const slice = t.slice(0, len);
942
+ const lastSpace = slice.lastIndexOf(' ');
943
+ return `${lastSpace > 32 ? slice.slice(0, lastSpace) : slice}\u2026`;
944
+ }
945
+ function bestSelector(el) {
946
+ return el.selector?.css || '';
947
+ }
948
+ function hashId(input) {
949
+ let h = 5381;
950
+ for (let i = 0; i < input.length; i++) h = (h * 33) ^ input.charCodeAt(i);
951
+ return `sec-${(h >>> 0).toString(36)}`;
952
+ }
953
+ function iconForRegion(key) {
954
+ switch (key) {
955
+ case 'header':
956
+ return '\u{1F9ED}';
957
+ case 'navigation':
958
+ return '\u{1F4D1}';
959
+ case 'main':
960
+ return '\u{1F4C4}';
961
+ case 'sections':
962
+ return '\u{1F5C2}\uFE0F';
963
+ case 'sidebar':
964
+ return '\u{1F4DA}';
965
+ case 'footer':
966
+ return '\u{1F53B}';
967
+ case 'modals':
968
+ return '\u{1F4AC}';
969
+ default:
970
+ return '\u{1F539}';
971
+ }
972
+ }
973
+ function elementLine(el, opts) {
974
+ const txt = truncate(el.text || el.attributes?.ariaLabel, opts?.maxTextLength ?? 80);
975
+ const sel = bestSelector(el);
976
+ const tag = el.tag.toLowerCase();
977
+ const action = el.interaction?.submit
978
+ ? 'submit'
979
+ : el.interaction?.click
980
+ ? 'click'
981
+ : el.interaction?.change
982
+ ? 'change'
983
+ : void 0;
984
+ const actionText = action ? ` (${action})` : '';
985
+ return `- ${tag.toUpperCase()}: ${txt || '(no text)'} \u2192 \`${sel}\`${actionText}`;
986
+ }
987
+ function selectorQualitySummary(inter) {
988
+ const all = [];
989
+ all.push(...inter.buttons.map((e) => e.selector?.css || ''));
990
+ all.push(...inter.links.map((e) => e.selector?.css || ''));
991
+ all.push(...inter.inputs.map((e) => e.selector?.css || ''));
992
+ all.push(...inter.clickable.map((e) => e.selector?.css || ''));
993
+ const total = all.length || 1;
994
+ const idCount = all.filter((s) => s.startsWith('#')).length;
995
+ const testIdCount = all.filter((s) => /\[data-testid=/.test(s)).length;
996
+ const nthCount = all.filter((s) => /:nth-child\(/.test(s)).length;
997
+ const stable = idCount + testIdCount;
998
+ const stablePct = Math.round((stable / total) * 100);
999
+ const nthPct = Math.round((nthCount / total) * 100);
1000
+ return `Selector quality: ${stablePct}% stable (ID/data-testid), ${nthPct}% structural (:nth-child)`;
1001
+ }
1002
+ function renderInteractive(inter, opts) {
1003
+ const parts = [];
1004
+ const limit = (arr) =>
1005
+ typeof opts?.maxElements === 'number' ? arr.slice(0, opts.maxElements) : arr;
1006
+ if (inter.buttons.length) {
1007
+ parts.push('Buttons:');
1008
+ for (const el of limit(inter.buttons)) parts.push(elementLine(el, opts));
1009
+ }
1010
+ if (inter.links.length) {
1011
+ parts.push('Links:');
1012
+ for (const el of limit(inter.links)) parts.push(elementLine(el, opts));
1013
+ }
1014
+ if (inter.inputs.length) {
1015
+ parts.push('Inputs:');
1016
+ for (const el of limit(inter.inputs)) parts.push(elementLine(el, opts));
1017
+ }
1018
+ if (inter.clickable.length) {
1019
+ parts.push('Other Clickable:');
1020
+ for (const el of limit(inter.clickable)) parts.push(elementLine(el, opts));
1021
+ }
1022
+ if (inter.forms.length) {
1023
+ parts.push('Forms:');
1024
+ for (const f of limit(inter.forms)) {
1025
+ parts.push(
1026
+ `- FORM: action=${f.action ?? '-'} method=${f.method ?? '-'} \u2192 \`${f.selector}\``
1027
+ );
1028
+ }
1029
+ }
1030
+ return parts.join('\n');
1031
+ }
1032
+ function renderRegionInfo(region) {
1033
+ const icon = iconForRegion('region');
1034
+ const id = hashId(`${region.selector}|${region.label ?? ''}|${region.role ?? ''}`);
1035
+ const label = region.label ? ` ${region.label}` : '';
1036
+ const stats = [];
1037
+ if (region.buttonCount) stats.push(`${region.buttonCount} buttons`);
1038
+ if (region.linkCount) stats.push(`${region.linkCount} links`);
1039
+ if (region.inputCount) stats.push(`${region.inputCount} inputs`);
1040
+ if (region.textPreview) stats.push(`\u201C${truncate(region.textPreview, 80)}\u201D`);
1041
+ const statsLine = stats.length ? ` \u2014 ${stats.join(', ')}` : '';
1042
+ return `${icon} ${label} \u2192 \`${region.selector}\` [${id}]${statsLine}`;
1043
+ }
1044
+ function wrapXml(body, meta, type = 'section') {
1045
+ const attrs = [
1046
+ meta?.title ? `title="${escapeXml(meta.title)}"` : null,
1047
+ meta?.url ? `url="${escapeXml(meta.url)}"` : null,
1048
+ ]
1049
+ .filter(Boolean)
1050
+ .join(' ');
1051
+ return `<page ${attrs}>
1052
+ <${type}><![CDATA[
1053
+ ${body}
1054
+ ]]></${type}>
1055
+ </page>`;
1056
+ }
1057
+ function escapeXml(s) {
1058
+ return s
1059
+ .replace(/&/g, '&amp;')
1060
+ .replace(/</g, '&lt;')
1061
+ .replace(/>/g, '&gt;')
1062
+ .replace(/"/g, '&quot;');
1063
+ }
1064
+ function capitalize(s) {
1065
+ return s.charAt(0).toUpperCase() + s.slice(1);
1066
+ }
1067
+ var MarkdownFormatter;
1068
+ var init_markdown_formatter = __esm({
1069
+ 'src/markdown-formatter.ts'() {
1070
+ MarkdownFormatter = class {
1071
+ static structure(overview, _opts, meta) {
1072
+ const lines = [];
1073
+ lines.push('# Page Outline');
1074
+ if (meta?.title || meta?.url) {
1075
+ lines.push(`Title: ${meta?.title ?? ''}`.trim());
1076
+ lines.push(`URL: ${meta?.url ?? ''}`.trim());
1077
+ }
1078
+ lines.push('');
1079
+ const regions = overview.regions;
1080
+ const entries = [
1081
+ ['header', regions.header],
1082
+ ['navigation', regions.navigation],
1083
+ ['main', regions.main],
1084
+ ['sections', regions.sections],
1085
+ ['sidebar', regions.sidebar],
1086
+ ['footer', regions.footer],
1087
+ ['modals', regions.modals],
1088
+ ];
1089
+ for (const [key, value] of entries) {
1090
+ if (!value) continue;
1091
+ const icon = iconForRegion(key);
1092
+ if (Array.isArray(value)) {
1093
+ if (!value.length) continue;
1094
+ lines.push(`## ${icon} ${capitalize(key)}`);
1095
+ for (const region of value) lines.push(renderRegionInfo(region));
1096
+ } else {
1097
+ lines.push(`## ${icon} ${capitalize(key)}`);
1098
+ lines.push(renderRegionInfo(value));
1099
+ }
1100
+ lines.push('');
1101
+ }
1102
+ if (overview.suggestions?.length) {
1103
+ lines.push('## Suggestions');
1104
+ for (const s of overview.suggestions) lines.push(`- ${s}`);
1105
+ lines.push('');
1106
+ }
1107
+ lines.push(
1108
+ 'Next: choose a region (by selector or [sectionId]) and call dom_extract_region for actionable details.'
1109
+ );
1110
+ const body = lines.join('\n');
1111
+ return wrapXml(body, meta, 'outline');
1112
+ }
1113
+ static region(result, opts, meta) {
1114
+ const lines = [];
1115
+ lines.push('# Region Details');
1116
+ if (meta?.title || meta?.url) {
1117
+ lines.push(`Title: ${meta?.title ?? ''}`.trim());
1118
+ lines.push(`URL: ${meta?.url ?? ''}`.trim());
1119
+ }
1120
+ lines.push('');
1121
+ const inter = result.interactive;
1122
+ if (result.page) {
1123
+ const ps = [
1124
+ result.page.hasErrors ? 'errors: yes' : 'errors: no',
1125
+ result.page.isLoading ? 'loading: yes' : 'loading: no',
1126
+ result.page.hasModals ? 'modals: yes' : 'modals: no',
1127
+ ];
1128
+ lines.push(`Page state: ${ps.join(', ')}`);
1129
+ }
1130
+ const summary = [];
1131
+ const count = (arr) => (arr ? arr.length : 0);
1132
+ summary.push(`${count(inter.buttons)} buttons`);
1133
+ summary.push(`${count(inter.links)} links`);
1134
+ summary.push(`${count(inter.inputs)} inputs`);
1135
+ if (inter.forms?.length) summary.push(`${count(inter.forms)} forms`);
1136
+ lines.push(`Summary: ${summary.join(', ')}`);
1137
+ lines.push(selectorQualitySummary(inter));
1138
+ lines.push('');
1139
+ lines.push(renderInteractive(inter, opts));
1140
+ lines.push('');
1141
+ lines.push(
1142
+ 'Next: write a script using the most stable selectors above. If selectors look unstable, rerun dom_extract_region with higher detail or call dom_extract_content for text context.'
1143
+ );
1144
+ const body = lines.join('\n');
1145
+ return wrapXml(body, meta, 'section');
1146
+ }
1147
+ static content(content, opts, meta) {
1148
+ const lines = [];
1149
+ lines.push('# Content');
1150
+ lines.push(`Selector: \`${content.selector}\``);
1151
+ lines.push('');
1152
+ if (content.text.headings?.length) {
1153
+ lines.push('Headings:');
1154
+ for (const h of content.text.headings)
1155
+ lines.push(`- H${h.level}: ${truncate(h.text, opts.maxTextLength ?? 120)}`);
1156
+ lines.push('');
1157
+ }
1158
+ if (content.text.paragraphs?.length) {
1159
+ const limit =
1160
+ typeof opts.maxElements === 'number'
1161
+ ? opts.maxElements
1162
+ : content.text.paragraphs.length;
1163
+ lines.push('Paragraphs:');
1164
+ for (const p of content.text.paragraphs.slice(0, limit))
1165
+ lines.push(`- ${truncate(p, opts.maxTextLength ?? 200)}`);
1166
+ lines.push('');
1167
+ }
1168
+ if (content.text.lists?.length) {
1169
+ lines.push('Lists:');
1170
+ for (const list of content.text.lists) {
1171
+ lines.push(`- ${list.type.toUpperCase()}:`);
1172
+ const limit =
1173
+ typeof opts.maxElements === 'number' ? opts.maxElements : list.items.length;
1174
+ for (const item of list.items.slice(0, limit))
1175
+ lines.push(` - ${truncate(item, opts.maxTextLength ?? 120)}`);
1176
+ }
1177
+ lines.push('');
1178
+ }
1179
+ if (content.tables?.length) {
1180
+ lines.push('Tables:');
1181
+ for (const t of content.tables) {
1182
+ lines.push(`- Headers: ${t.headers.join(' | ')}`);
1183
+ const limit = typeof opts.maxElements === 'number' ? opts.maxElements : t.rows.length;
1184
+ for (const row of t.rows.slice(0, limit)) lines.push(` - ${row.join(' | ')}`);
1185
+ }
1186
+ lines.push('');
1187
+ }
1188
+ if (content.media?.length) {
1189
+ lines.push('Media:');
1190
+ const limit =
1191
+ typeof opts.maxElements === 'number' ? opts.maxElements : content.media.length;
1192
+ for (const m of content.media.slice(0, limit)) {
1193
+ lines.push(
1194
+ `- ${m.type.toUpperCase()}: ${m.alt ?? ''} ${m.src ? `\u2192 ${m.src}` : ''}`.trim()
1195
+ );
1196
+ }
1197
+ lines.push('');
1198
+ }
1199
+ lines.push(
1200
+ 'Next: if text is insufficient for targeting, call dom_extract_region for interactive selectors.'
1201
+ );
1202
+ const body = lines.join('\n');
1203
+ return wrapXml(body, meta, 'content');
1204
+ }
1205
+ };
1206
+ },
1207
+ });
1208
+
1209
+ // src/progressive.ts
1210
+ function resolveSmartDomReader() {
1211
+ if (typeof window !== 'undefined') {
1212
+ const globalWindow = window;
1213
+ const direct = globalWindow.SmartDOMReader;
1214
+ if (typeof direct === 'function') {
1215
+ return direct;
1216
+ }
1217
+ const namespace = globalWindow.SmartDOMReaderNamespace;
1218
+ if (namespace && typeof namespace.SmartDOMReader === 'function') {
1219
+ return namespace.SmartDOMReader;
1220
+ }
1221
+ }
1222
+ try {
1223
+ if (typeof __require === 'function') {
1224
+ const moduleExports = (init_index(), __toCommonJS(index_exports));
1225
+ if (moduleExports && typeof moduleExports.SmartDOMReader === 'function') {
1226
+ return moduleExports.SmartDOMReader;
1227
+ }
1228
+ if (moduleExports && typeof moduleExports.default === 'function') {
1229
+ return moduleExports.default;
1230
+ }
1231
+ }
1232
+ } catch {}
1233
+ return void 0;
1234
+ }
1235
+ var ProgressiveExtractor;
1236
+ var init_progressive = __esm({
1237
+ 'src/progressive.ts'() {
1238
+ init_content_detection();
1239
+ init_selectors();
1240
+ init_traversal();
1241
+ ProgressiveExtractor = class {
1242
+ /**
1243
+ * Step 1: Extract high-level structural overview
1244
+ * This provides a "map" of the page for the AI to understand structure
1245
+ */
1246
+ static extractStructure(root) {
1247
+ const regions = {};
1248
+ const header = root.querySelector('header, [role="banner"], .header, #header');
1249
+ if (header) {
1250
+ regions.header = this.analyzeRegion(header);
1251
+ }
1252
+ const navs = root.querySelectorAll('nav, [role="navigation"], .nav, .navigation');
1253
+ if (navs.length > 0) {
1254
+ regions.navigation = Array.from(navs).map((nav) => this.analyzeRegion(nav));
1255
+ }
1256
+ if (root instanceof Document) {
1257
+ const main = ContentDetection.findMainContent(root);
1258
+ if (main) {
1259
+ regions.main = this.analyzeRegion(main);
1260
+ const sections = main.querySelectorAll('section, article, [role="region"]');
1261
+ if (sections.length > 0) {
1262
+ regions.sections = Array.from(sections)
1263
+ .filter((section) => !section.closest('nav, header, footer'))
1264
+ .map((section) => this.analyzeRegion(section));
1265
+ }
1266
+ }
1267
+ } else {
1268
+ regions.main = this.analyzeRegion(root);
1269
+ const sections = root.querySelectorAll('section, article, [role="region"]');
1270
+ if (sections.length > 0) {
1271
+ regions.sections = Array.from(sections)
1272
+ .filter((section) => !section.closest('nav, header, footer'))
1273
+ .map((section) => this.analyzeRegion(section));
1274
+ }
1275
+ }
1276
+ const sidebars = root.querySelectorAll('aside, [role="complementary"], .sidebar, #sidebar');
1277
+ if (sidebars.length > 0) {
1278
+ regions.sidebar = Array.from(sidebars).map((sidebar) => this.analyzeRegion(sidebar));
1279
+ }
1280
+ const footer = root.querySelector('footer, [role="contentinfo"], .footer, #footer');
1281
+ if (footer) {
1282
+ regions.footer = this.analyzeRegion(footer);
1283
+ }
1284
+ const modals = root.querySelectorAll('[role="dialog"], .modal, .popup, .overlay');
1285
+ const visibleModals = Array.from(modals).filter((modal) => DOMTraversal.isVisible(modal));
1286
+ if (visibleModals.length > 0) {
1287
+ regions.modals = visibleModals.map((modal) => this.analyzeRegion(modal));
1288
+ }
1289
+ const forms = this.extractFormOverview(root);
1290
+ const summary = this.calculateSummary(root, regions, forms);
1291
+ const suggestions = this.generateSuggestions(regions, summary);
1292
+ return { regions, forms, summary, suggestions };
1293
+ }
1294
+ /**
1295
+ * Step 2: Extract detailed information from a specific region
1296
+ */
1297
+ static extractRegion(selector, doc, options, smartDomReaderCtor) {
1298
+ const element = doc.querySelector(selector);
1299
+ if (!element) return null;
1300
+ const SmartDOMReaderCtor = smartDomReaderCtor ?? resolveSmartDomReader();
1301
+ if (!SmartDOMReaderCtor) {
1302
+ throw new Error(
1303
+ 'SmartDOMReader is unavailable. Ensure the Smart DOM Reader module is loaded before calling extractRegion.'
1304
+ );
1305
+ }
1306
+ const reader = new SmartDOMReaderCtor(options);
1307
+ return reader.extract(element, options);
1308
+ }
1309
+ /**
1310
+ * Step 3: Extract readable content from a region
1311
+ */
1312
+ static extractContent(selector, doc, options = {}) {
1313
+ const element = doc.querySelector(selector);
1314
+ if (!element) return null;
1315
+ const result = {
1316
+ selector,
1317
+ text: {},
1318
+ metadata: {
1319
+ wordCount: 0,
1320
+ hasInteractive: false,
1321
+ },
1322
+ };
1323
+ if (options.includeHeadings !== false) {
1324
+ const headings = element.querySelectorAll('h1, h2, h3, h4, h5, h6');
1325
+ result.text.headings = Array.from(headings).map((h) => ({
1326
+ level: Number.parseInt(h.tagName[1], 10),
1327
+ text: this.getTextContent(h, options.maxTextLength),
1328
+ }));
1329
+ }
1330
+ const paragraphs = element.querySelectorAll('p');
1331
+ if (paragraphs.length > 0) {
1332
+ result.text.paragraphs = Array.from(paragraphs)
1333
+ .map((p) => this.getTextContent(p, options.maxTextLength))
1334
+ .filter((text) => text.length > 0);
1335
+ }
1336
+ if (options.includeLists !== false) {
1337
+ const lists = element.querySelectorAll('ul, ol');
1338
+ result.text.lists = Array.from(lists).map((list) => ({
1339
+ type: list.tagName.toLowerCase(),
1340
+ items: Array.from(list.querySelectorAll('li')).map((li) =>
1341
+ this.getTextContent(li, options.maxTextLength)
1342
+ ),
1343
+ }));
1344
+ }
1345
+ if (options.includeTables !== false) {
1346
+ const tables = element.querySelectorAll('table');
1347
+ result.tables = Array.from(tables).map((table) => {
1348
+ const headers = Array.from(table.querySelectorAll('th')).map((th) =>
1349
+ this.getTextContent(th)
1350
+ );
1351
+ const rows = Array.from(table.querySelectorAll('tr'))
1352
+ .filter((tr) => tr.querySelector('td'))
1353
+ .map((tr) =>
1354
+ Array.from(tr.querySelectorAll('td')).map((td) => this.getTextContent(td))
1355
+ );
1356
+ return { headers, rows };
1357
+ });
1358
+ }
1359
+ if (options.includeMedia !== false) {
1360
+ const images = element.querySelectorAll('img');
1361
+ const videos = element.querySelectorAll('video');
1362
+ const audios = element.querySelectorAll('audio');
1363
+ result.media = [
1364
+ ...Array.from(images).map((img) => ({
1365
+ type: 'img',
1366
+ alt: img.getAttribute('alt') || void 0,
1367
+ src: img.getAttribute('src') || void 0,
1368
+ })),
1369
+ ...Array.from(videos).map((video) => ({
1370
+ type: 'video',
1371
+ src: video.getAttribute('src') || void 0,
1372
+ })),
1373
+ ...Array.from(audios).map((audio) => ({
1374
+ type: 'audio',
1375
+ src: audio.getAttribute('src') || void 0,
1376
+ })),
1377
+ ];
1378
+ }
1379
+ const allText = element.textContent || '';
1380
+ result.metadata.wordCount = allText.trim().split(/\s+/).length;
1381
+ result.metadata.hasInteractive =
1382
+ element.querySelectorAll('button, a, input, textarea, select').length > 0;
1383
+ return result;
1384
+ }
1385
+ /**
1386
+ * Analyze a region and extract summary information
1387
+ */
1388
+ static analyzeRegion(element) {
1389
+ const selector = SelectorGenerator.generateSelectors(element).css;
1390
+ const buttons = element.querySelectorAll('button, [role="button"]');
1391
+ const links = element.querySelectorAll('a[href]');
1392
+ const inputs = element.querySelectorAll('input, textarea, select');
1393
+ const forms = element.querySelectorAll('form');
1394
+ const lists = element.querySelectorAll('ul, ol');
1395
+ const tables = element.querySelectorAll('table');
1396
+ const media = element.querySelectorAll('img, video, audio');
1397
+ const interactiveCount = buttons.length + links.length + inputs.length;
1398
+ let label;
1399
+ const ariaLabel = element.getAttribute('aria-label');
1400
+ if (ariaLabel) {
1401
+ label = ariaLabel;
1402
+ } else if (element.getAttribute('aria-labelledby')) {
1403
+ const labelId = element.getAttribute('aria-labelledby');
1404
+ if (labelId) {
1405
+ const labelElement = element.ownerDocument?.getElementById(labelId);
1406
+ if (labelElement) {
1407
+ label = labelElement.textContent?.trim();
1408
+ }
1409
+ }
1410
+ } else {
1411
+ const heading = element.querySelector('h1, h2, h3');
1412
+ if (heading) {
1413
+ label = heading.textContent?.trim();
1414
+ }
1415
+ }
1416
+ const textContent = element.textContent?.trim() || '';
1417
+ const textPreview =
1418
+ textContent.length > 50 ? `${textContent.substring(0, 50)}...` : textContent;
1419
+ return {
1420
+ selector,
1421
+ label,
1422
+ role: element.getAttribute('role') || void 0,
1423
+ interactiveCount,
1424
+ hasForm: forms.length > 0,
1425
+ hasList: lists.length > 0,
1426
+ hasTable: tables.length > 0,
1427
+ hasMedia: media.length > 0,
1428
+ buttonCount: buttons.length > 0 ? buttons.length : void 0,
1429
+ linkCount: links.length > 0 ? links.length : void 0,
1430
+ inputCount: inputs.length > 0 ? inputs.length : void 0,
1431
+ textPreview: textPreview.length > 0 ? textPreview : void 0,
1432
+ };
1433
+ }
1434
+ /**
1435
+ * Extract overview of forms on the page
1436
+ */
1437
+ static extractFormOverview(root) {
1438
+ const forms = root.querySelectorAll('form');
1439
+ return Array.from(forms).map((form) => {
1440
+ const inputs = form.querySelectorAll('input, textarea, select');
1441
+ const selector = SelectorGenerator.generateSelectors(form).css;
1442
+ let location = 'unknown';
1443
+ if (form.closest('header, [role="banner"]')) {
1444
+ location = 'header';
1445
+ } else if (form.closest('nav, [role="navigation"]')) {
1446
+ location = 'navigation';
1447
+ } else if (form.closest('main, [role="main"]')) {
1448
+ location = 'main';
1449
+ } else if (form.closest('aside, [role="complementary"]')) {
1450
+ location = 'sidebar';
1451
+ } else if (form.closest('footer, [role="contentinfo"]')) {
1452
+ location = 'footer';
1453
+ }
1454
+ let purpose;
1455
+ const formId = form.getAttribute('id')?.toLowerCase();
1456
+ const formClass = form.getAttribute('class')?.toLowerCase();
1457
+ const formAction = form.getAttribute('action')?.toLowerCase();
1458
+ const hasEmail = form.querySelector('input[type="email"]');
1459
+ const hasPassword = form.querySelector('input[type="password"]');
1460
+ const hasSearch = form.querySelector('input[type="search"]');
1461
+ if (hasSearch || formId?.includes('search') || formClass?.includes('search')) {
1462
+ purpose = 'search';
1463
+ } else if (hasPassword && hasEmail) {
1464
+ purpose = 'login';
1465
+ } else if (hasPassword) {
1466
+ purpose = 'authentication';
1467
+ } else if (formId?.includes('contact') || formClass?.includes('contact')) {
1468
+ purpose = 'contact';
1469
+ } else if (formId?.includes('subscribe') || formClass?.includes('subscribe')) {
1470
+ purpose = 'subscription';
1471
+ } else if (formAction?.includes('checkout') || formClass?.includes('checkout')) {
1472
+ purpose = 'checkout';
1473
+ }
1474
+ return {
1475
+ selector,
1476
+ location,
1477
+ inputCount: inputs.length,
1478
+ purpose,
1479
+ };
1480
+ });
1481
+ }
1482
+ /**
1483
+ * Calculate summary statistics
1484
+ */
1485
+ static calculateSummary(root, regions, forms) {
1486
+ const allInteractive = root.querySelectorAll('button, a[href], input, textarea, select');
1487
+ const allSections = root.querySelectorAll('section, article, [role="region"]');
1488
+ const hasModals = (regions.modals?.length || 0) > 0;
1489
+ const errorSelectors = ['.error', '.alert-danger', '[role="alert"]'];
1490
+ const hasErrors = errorSelectors.some((sel) => {
1491
+ const element = root.querySelector(sel);
1492
+ return element ? DOMTraversal.isVisible(element) : false;
1493
+ });
1494
+ const loadingSelectors = ['.loading', '.spinner', '[aria-busy="true"]'];
1495
+ const isLoading = loadingSelectors.some((sel) => {
1496
+ const element = root.querySelector(sel);
1497
+ return element ? DOMTraversal.isVisible(element) : false;
1498
+ });
1499
+ const mainContentSelector = regions.main?.selector;
1500
+ return {
1501
+ totalInteractive: allInteractive.length,
1502
+ totalForms: forms.length,
1503
+ totalSections: allSections.length,
1504
+ hasModals,
1505
+ hasErrors,
1506
+ isLoading,
1507
+ mainContentSelector,
1508
+ };
1509
+ }
1510
+ /**
1511
+ * Generate AI-friendly suggestions
1512
+ */
1513
+ static generateSuggestions(regions, summary) {
1514
+ const suggestions = [];
1515
+ if (summary.hasErrors) {
1516
+ suggestions.push('Page has error indicators - check error messages before interacting');
1517
+ }
1518
+ if (summary.isLoading) {
1519
+ suggestions.push('Page appears to be loading - wait or check loading state');
1520
+ }
1521
+ if (summary.hasModals) {
1522
+ suggestions.push('Modal/dialog is open - may need to interact with or close it first');
1523
+ }
1524
+ if (regions.main && regions.main.interactiveCount > 10) {
1525
+ suggestions.push(
1526
+ `Main content has ${regions.main.interactiveCount} interactive elements - consider filtering`
1527
+ );
1528
+ }
1529
+ if (summary.totalForms > 0) {
1530
+ suggestions.push(`Found ${summary.totalForms} form(s) on the page`);
1531
+ }
1532
+ if (!regions.main) {
1533
+ suggestions.push('No clear main content area detected - may need to explore regions');
1534
+ }
1535
+ return suggestions;
1536
+ }
1537
+ /**
1538
+ * Get text content with optional truncation
1539
+ */
1540
+ static getTextContent(element, maxLength) {
1541
+ const text = element.textContent?.trim() || '';
1542
+ if (maxLength && text.length > maxLength) {
1543
+ return `${text.substring(0, maxLength)}...`;
1544
+ }
1545
+ return text;
1546
+ }
1547
+ };
1548
+ },
1549
+ });
1550
+
1551
+ // src/types.ts
1552
+ var init_types = __esm({
1553
+ 'src/types.ts'() {},
1554
+ });
1555
+
1556
+ // src/index.ts
1557
+ var index_exports = {};
1558
+ __export(index_exports, {
1559
+ ContentDetection: () => ContentDetection,
1560
+ MarkdownFormatter: () => MarkdownFormatter,
1561
+ ProgressiveExtractor: () => ProgressiveExtractor,
1562
+ SelectorGenerator: () => SelectorGenerator,
1563
+ SmartDOMReader: () => SmartDOMReader,
1564
+ default: () => index_default,
1565
+ });
1566
+ var SmartDOMReader;
1567
+ var index_default;
1568
+ var init_index = __esm({
1569
+ 'src/index.ts'() {
1570
+ init_content_detection();
1571
+ init_selectors();
1572
+ init_traversal();
1573
+ init_content_detection();
1574
+ init_markdown_formatter();
1575
+ init_progressive();
1576
+ init_selectors();
1577
+ init_types();
1578
+ SmartDOMReader = class _SmartDOMReader {
1579
+ options;
1580
+ constructor(options = {}) {
1581
+ this.options = {
1582
+ mode: options.mode || 'interactive',
1583
+ maxDepth: options.maxDepth || 5,
1584
+ includeHidden: options.includeHidden || false,
1585
+ includeShadowDOM: options.includeShadowDOM || true,
1586
+ includeIframes: options.includeIframes || false,
1587
+ viewportOnly: options.viewportOnly || false,
1588
+ mainContentOnly: options.mainContentOnly || false,
1589
+ customSelectors: options.customSelectors || [],
1590
+ attributeTruncateLength: options.attributeTruncateLength,
1591
+ dataAttributeTruncateLength: options.dataAttributeTruncateLength,
1592
+ textTruncateLength: options.textTruncateLength,
1593
+ filter: options.filter,
1594
+ };
1595
+ }
1596
+ /**
1597
+ * Main extraction method - extracts all data in one pass
1598
+ * @param rootElement The document or element to extract from
1599
+ * @param runtimeOptions Options to override constructor options
1600
+ */
1601
+ extract(rootElement, runtimeOptions) {
1602
+ const startTime = Date.now();
1603
+ const doc = rootElement instanceof Document ? rootElement : rootElement.ownerDocument;
1604
+ const options = { ...this.options, ...runtimeOptions };
1605
+ let container = rootElement instanceof Document ? doc : rootElement;
1606
+ if (options.mainContentOnly && rootElement instanceof Document) {
1607
+ container = ContentDetection.findMainContent(doc);
1608
+ }
1609
+ const pageState = this.extractPageState(doc);
1610
+ const landmarks = this.extractLandmarks(doc);
1611
+ const interactive = this.extractInteractiveElements(container, options);
1612
+ const result = {
1613
+ mode: options.mode,
1614
+ timestamp: startTime,
1615
+ page: pageState,
1616
+ landmarks,
1617
+ interactive,
1618
+ };
1619
+ if (options.mode === 'full') {
1620
+ result.semantic = this.extractSemanticElements(container, options);
1621
+ result.metadata = this.extractMetadata(doc, container, options);
1622
+ }
1623
+ return result;
1624
+ }
1625
+ /**
1626
+ * Extract page state information
1627
+ */
1628
+ extractPageState(doc) {
1629
+ return {
1630
+ url: doc.location?.href || '',
1631
+ title: doc.title || '',
1632
+ hasErrors: this.detectErrors(doc),
1633
+ isLoading: this.detectLoading(doc),
1634
+ hasModals: this.detectModals(doc),
1635
+ hasFocus: this.getFocusedElement(doc),
1636
+ };
1637
+ }
1638
+ /**
1639
+ * Extract page landmarks
1640
+ */
1641
+ extractLandmarks(doc) {
1642
+ const detected = ContentDetection.detectLandmarks(doc);
1643
+ return {
1644
+ navigation: this.elementsToSelectors(detected.navigation || []),
1645
+ main: this.elementsToSelectors(detected.main || []),
1646
+ forms: this.elementsToSelectors(detected.form || []),
1647
+ headers: this.elementsToSelectors(detected.banner || []),
1648
+ footers: this.elementsToSelectors(detected.contentinfo || []),
1649
+ articles: this.elementsToSelectors(detected.region || []),
1650
+ sections: this.elementsToSelectors(detected.region || []),
1651
+ };
1652
+ }
1653
+ /**
1654
+ * Convert elements to selector strings
1655
+ */
1656
+ elementsToSelectors(elements) {
1657
+ return elements.map((el) => SelectorGenerator.generateSelectors(el).css);
1658
+ }
1659
+ /**
1660
+ * Extract interactive elements
1661
+ */
1662
+ extractInteractiveElements(container, options) {
1663
+ const buttons = [];
1664
+ const links = [];
1665
+ const inputs = [];
1666
+ const clickable = [];
1667
+ const buttonElements = container.querySelectorAll(
1668
+ 'button, [role="button"], input[type="button"], input[type="submit"]'
1669
+ );
1670
+ buttonElements.forEach((el) => {
1671
+ if (this.shouldIncludeElement(el, options)) {
1672
+ const extracted = DOMTraversal.extractElement(el, options);
1673
+ if (extracted) buttons.push(extracted);
1674
+ }
1675
+ });
1676
+ const linkElements = container.querySelectorAll('a[href]');
1677
+ linkElements.forEach((el) => {
1678
+ if (this.shouldIncludeElement(el, options)) {
1679
+ const extracted = DOMTraversal.extractElement(el, options);
1680
+ if (extracted) links.push(extracted);
1681
+ }
1682
+ });
1683
+ const inputElements = container.querySelectorAll(
1684
+ 'input:not([type="button"]):not([type="submit"]), textarea, select'
1685
+ );
1686
+ inputElements.forEach((el) => {
1687
+ if (this.shouldIncludeElement(el, options)) {
1688
+ const extracted = DOMTraversal.extractElement(el, options);
1689
+ if (extracted) inputs.push(extracted);
1690
+ }
1691
+ });
1692
+ if (options.customSelectors) {
1693
+ options.customSelectors.forEach((selector) => {
1694
+ const elements = container.querySelectorAll(selector);
1695
+ elements.forEach((el) => {
1696
+ if (this.shouldIncludeElement(el, options)) {
1697
+ const extracted = DOMTraversal.extractElement(el, options);
1698
+ if (extracted) clickable.push(extracted);
1699
+ }
1700
+ });
1701
+ });
1702
+ }
1703
+ const forms = this.extractForms(container, options);
1704
+ return {
1705
+ buttons,
1706
+ links,
1707
+ inputs,
1708
+ forms,
1709
+ clickable,
1710
+ };
1711
+ }
1712
+ /**
1713
+ * Extract form information
1714
+ */
1715
+ extractForms(container, options) {
1716
+ const forms = [];
1717
+ const formElements = container.querySelectorAll('form');
1718
+ formElements.forEach((form) => {
1719
+ if (!this.shouldIncludeElement(form, options)) return;
1720
+ const formInputs = [];
1721
+ const formButtons = [];
1722
+ const inputs = form.querySelectorAll(
1723
+ 'input:not([type="button"]):not([type="submit"]), textarea, select'
1724
+ );
1725
+ inputs.forEach((input) => {
1726
+ const extracted = DOMTraversal.extractElement(input, options);
1727
+ if (extracted) formInputs.push(extracted);
1728
+ });
1729
+ const buttons = form.querySelectorAll(
1730
+ 'button, input[type="button"], input[type="submit"]'
1731
+ );
1732
+ buttons.forEach((button) => {
1733
+ const extracted = DOMTraversal.extractElement(button, options);
1734
+ if (extracted) formButtons.push(extracted);
1735
+ });
1736
+ forms.push({
1737
+ selector: SelectorGenerator.generateSelectors(form).css,
1738
+ action: form.getAttribute('action') || void 0,
1739
+ method: form.getAttribute('method') || void 0,
1740
+ inputs: formInputs,
1741
+ buttons: formButtons,
1742
+ });
1743
+ });
1744
+ return forms;
1745
+ }
1746
+ /**
1747
+ * Extract semantic elements (full mode only)
1748
+ */
1749
+ extractSemanticElements(container, options) {
1750
+ const headings = [];
1751
+ const images = [];
1752
+ const tables = [];
1753
+ const lists = [];
1754
+ const articles = [];
1755
+ container.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((el) => {
1756
+ if (this.shouldIncludeElement(el, options)) {
1757
+ const extracted = DOMTraversal.extractElement(el, options);
1758
+ if (extracted) headings.push(extracted);
1759
+ }
1760
+ });
1761
+ container.querySelectorAll('img').forEach((el) => {
1762
+ if (this.shouldIncludeElement(el, options)) {
1763
+ const extracted = DOMTraversal.extractElement(el, options);
1764
+ if (extracted) images.push(extracted);
1765
+ }
1766
+ });
1767
+ container.querySelectorAll('table').forEach((el) => {
1768
+ if (this.shouldIncludeElement(el, options)) {
1769
+ const extracted = DOMTraversal.extractElement(el, options);
1770
+ if (extracted) tables.push(extracted);
1771
+ }
1772
+ });
1773
+ container.querySelectorAll('ul, ol').forEach((el) => {
1774
+ if (this.shouldIncludeElement(el, options)) {
1775
+ const extracted = DOMTraversal.extractElement(el, options);
1776
+ if (extracted) lists.push(extracted);
1777
+ }
1778
+ });
1779
+ container.querySelectorAll('article, [role="article"]').forEach((el) => {
1780
+ if (this.shouldIncludeElement(el, options)) {
1781
+ const extracted = DOMTraversal.extractElement(el, options);
1782
+ if (extracted) articles.push(extracted);
1783
+ }
1784
+ });
1785
+ return {
1786
+ headings,
1787
+ images,
1788
+ tables,
1789
+ lists,
1790
+ articles,
1791
+ };
1792
+ }
1793
+ /**
1794
+ * Extract metadata
1795
+ */
1796
+ extractMetadata(doc, container, options) {
1797
+ const allElements = container.querySelectorAll('*');
1798
+ const extractedElements = container.querySelectorAll(
1799
+ 'button, a, input, textarea, select, h1, h2, h3, h4, h5, h6, img, table, ul, ol, article'
1800
+ ).length;
1801
+ return {
1802
+ totalElements: allElements.length,
1803
+ extractedElements,
1804
+ mainContent:
1805
+ options.mainContentOnly && container instanceof Element
1806
+ ? SelectorGenerator.generateSelectors(container).css
1807
+ : void 0,
1808
+ language: doc.documentElement.getAttribute('lang') || void 0,
1809
+ };
1810
+ }
1811
+ /**
1812
+ * Check if element should be included based on options
1813
+ */
1814
+ shouldIncludeElement(element, options) {
1815
+ if (!options.includeHidden && !DOMTraversal.isVisible(element)) {
1816
+ return false;
1817
+ }
1818
+ if (options.viewportOnly && !DOMTraversal.isInViewport(element)) {
1819
+ return false;
1820
+ }
1821
+ if (options.filter && !DOMTraversal.passesFilter(element, options.filter)) {
1822
+ return false;
1823
+ }
1824
+ return true;
1825
+ }
1826
+ /**
1827
+ * Detect errors on the page
1828
+ */
1829
+ detectErrors(doc) {
1830
+ const errorSelectors = ['.error', '.alert-danger', '[role="alert"]', '.error-message'];
1831
+ return errorSelectors.some((sel) => {
1832
+ const element = doc.querySelector(sel);
1833
+ return element ? DOMTraversal.isVisible(element) : false;
1834
+ });
1835
+ }
1836
+ /**
1837
+ * Detect if page is loading
1838
+ */
1839
+ detectLoading(doc) {
1840
+ const loadingSelectors = ['.loading', '.spinner', '[aria-busy="true"]', '.loader'];
1841
+ return loadingSelectors.some((sel) => {
1842
+ const element = doc.querySelector(sel);
1843
+ return element ? DOMTraversal.isVisible(element) : false;
1844
+ });
1845
+ }
1846
+ /**
1847
+ * Detect modal dialogs
1848
+ */
1849
+ detectModals(doc) {
1850
+ const modalSelectors = ['[role="dialog"]', '.modal', '.popup', '.overlay'];
1851
+ return modalSelectors.some((sel) => {
1852
+ const element = doc.querySelector(sel);
1853
+ return element ? DOMTraversal.isVisible(element) : false;
1854
+ });
1855
+ }
1856
+ /**
1857
+ * Get currently focused element
1858
+ */
1859
+ getFocusedElement(doc) {
1860
+ const focused = doc.activeElement;
1861
+ if (focused && focused !== doc.body) {
1862
+ return SelectorGenerator.generateSelectors(focused).css;
1863
+ }
1864
+ return void 0;
1865
+ }
1866
+ // ===== Static convenience methods =====
1867
+ /**
1868
+ * Quick extraction for interactive elements only
1869
+ * @param doc The document to extract from
1870
+ * @param options Extraction options
1871
+ */
1872
+ static extractInteractive(doc, options = {}) {
1873
+ const reader = new _SmartDOMReader({
1874
+ ...options,
1875
+ mode: 'interactive',
1876
+ });
1877
+ return reader.extract(doc);
1878
+ }
1879
+ /**
1880
+ * Quick extraction for full content
1881
+ * @param doc The document to extract from
1882
+ * @param options Extraction options
1883
+ */
1884
+ static extractFull(doc, options = {}) {
1885
+ const reader = new _SmartDOMReader({
1886
+ ...options,
1887
+ mode: 'full',
1888
+ });
1889
+ return reader.extract(doc);
1890
+ }
1891
+ /**
1892
+ * Extract from a specific element
1893
+ * @param element The element to extract from
1894
+ * @param mode The extraction mode
1895
+ * @param options Additional options
1896
+ */
1897
+ static extractFromElement(element, mode = 'interactive', options = {}) {
1898
+ const reader = new _SmartDOMReader({
1899
+ ...options,
1900
+ mode,
1901
+ });
1902
+ return reader.extract(element);
1903
+ }
1904
+ };
1905
+ index_default = SmartDOMReader;
1906
+ },
1907
+ });
1908
+ init_index();
1909
+ export {
1910
+ ContentDetection,
1911
+ MarkdownFormatter,
1912
+ ProgressiveExtractor,
1913
+ SelectorGenerator,
1914
+ SmartDOMReader,
1915
+ index_default as default,
1916
+ };
1917
+ //# sourceMappingURL=index.js.map