auto-webmcp 0.2.3 → 0.2.5
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/dist/analyzer.d.ts +5 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/auto-webmcp.cjs.js +261 -11
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +261 -11
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +2 -2
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +5 -0
- package/dist/interceptor.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/analyzer.d.ts
CHANGED
|
@@ -14,4 +14,9 @@ export interface ToolMetadata {
|
|
|
14
14
|
export declare function resetFormIndex(): void;
|
|
15
15
|
/** Derive ToolMetadata from a <form> element */
|
|
16
16
|
export declare function analyzeForm(form: HTMLFormElement, override?: FormOverride): ToolMetadata;
|
|
17
|
+
/**
|
|
18
|
+
* Derive ToolMetadata from a group of form controls that are NOT inside a <form>.
|
|
19
|
+
* Used by discovery.ts's orphan-input scanner for pages like newsletter landing pages.
|
|
20
|
+
*/
|
|
21
|
+
export declare function analyzeOrphanInputGroup(container: Element, inputs: Array<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>, submitBtn: HTMLButtonElement | HTMLInputElement | null): ToolMetadata;
|
|
17
22
|
//# sourceMappingURL=analyzer.d.ts.map
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAA8H,MAAM,aAAa,CAAC;AACrK,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAMxF"}
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAA8H,MAAM,aAAa,CAAC;AACrK,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAMxF;AA8YD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,KAAK,CAAC,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC,EACzE,SAAS,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GACrD,YAAY,CAKd"}
|
package/dist/auto-webmcp.cjs.js
CHANGED
|
@@ -588,6 +588,88 @@ function labelTextWithoutNested(label) {
|
|
|
588
588
|
function humanizeName(raw) {
|
|
589
589
|
return raw.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
590
590
|
}
|
|
591
|
+
function analyzeOrphanInputGroup(container, inputs, submitBtn) {
|
|
592
|
+
const name = inferOrphanToolName(container, submitBtn);
|
|
593
|
+
const description = inferOrphanToolDescription(container);
|
|
594
|
+
const { schema: inputSchema, fieldElements } = buildSchemaFromInputs(inputs);
|
|
595
|
+
return { name, description, inputSchema, fieldElements };
|
|
596
|
+
}
|
|
597
|
+
function inferOrphanToolName(container, submitBtn) {
|
|
598
|
+
if (submitBtn) {
|
|
599
|
+
const text = submitBtn instanceof HTMLInputElement ? submitBtn.value.trim() : submitBtn.textContent?.trim() ?? "";
|
|
600
|
+
if (text && text.length > 0 && text.length < 80)
|
|
601
|
+
return sanitizeName(text);
|
|
602
|
+
}
|
|
603
|
+
const heading = getNearestHeadingTextFrom(container);
|
|
604
|
+
if (heading)
|
|
605
|
+
return sanitizeName(heading);
|
|
606
|
+
const title = document.title?.trim();
|
|
607
|
+
if (title)
|
|
608
|
+
return sanitizeName(title);
|
|
609
|
+
return `form_${++formIndex}`;
|
|
610
|
+
}
|
|
611
|
+
function inferOrphanToolDescription(container) {
|
|
612
|
+
const heading = getNearestHeadingTextFrom(container);
|
|
613
|
+
const pageTitle = document.title?.trim();
|
|
614
|
+
if (heading && pageTitle && heading !== pageTitle)
|
|
615
|
+
return `${heading} on ${pageTitle}`;
|
|
616
|
+
if (heading)
|
|
617
|
+
return heading;
|
|
618
|
+
if (pageTitle)
|
|
619
|
+
return pageTitle;
|
|
620
|
+
return "Submit form";
|
|
621
|
+
}
|
|
622
|
+
function getNearestHeadingTextFrom(el) {
|
|
623
|
+
const inner = el.querySelector("h1, h2, h3");
|
|
624
|
+
if (inner?.textContent?.trim())
|
|
625
|
+
return inner.textContent.trim();
|
|
626
|
+
let node = el;
|
|
627
|
+
while (node) {
|
|
628
|
+
let sibling = node.previousElementSibling;
|
|
629
|
+
while (sibling) {
|
|
630
|
+
if (/^H[1-3]$/i.test(sibling.tagName)) {
|
|
631
|
+
const text = sibling.textContent?.trim() ?? "";
|
|
632
|
+
if (text)
|
|
633
|
+
return text;
|
|
634
|
+
}
|
|
635
|
+
sibling = sibling.previousElementSibling;
|
|
636
|
+
}
|
|
637
|
+
node = node.parentElement;
|
|
638
|
+
if (!node || node === document.body)
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
return "";
|
|
642
|
+
}
|
|
643
|
+
function buildSchemaFromInputs(inputs) {
|
|
644
|
+
const properties = {};
|
|
645
|
+
const required = [];
|
|
646
|
+
const fieldElements = /* @__PURE__ */ new Map();
|
|
647
|
+
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
648
|
+
for (const control of inputs) {
|
|
649
|
+
const name = control.name;
|
|
650
|
+
const fieldKey = name || resolveNativeControlFallbackKey(control);
|
|
651
|
+
if (!fieldKey)
|
|
652
|
+
continue;
|
|
653
|
+
if (control instanceof HTMLInputElement && control.type === "radio") {
|
|
654
|
+
if (processedRadioGroups.has(fieldKey))
|
|
655
|
+
continue;
|
|
656
|
+
processedRadioGroups.add(fieldKey);
|
|
657
|
+
}
|
|
658
|
+
const schemaProp = inputTypeToSchema(control);
|
|
659
|
+
if (!schemaProp)
|
|
660
|
+
continue;
|
|
661
|
+
schemaProp.title = inferFieldTitle(control);
|
|
662
|
+
const desc = inferFieldDescription(control);
|
|
663
|
+
if (desc)
|
|
664
|
+
schemaProp.description = desc;
|
|
665
|
+
properties[fieldKey] = schemaProp;
|
|
666
|
+
if (!name)
|
|
667
|
+
fieldElements.set(fieldKey, control);
|
|
668
|
+
if (control.required)
|
|
669
|
+
required.push(fieldKey);
|
|
670
|
+
}
|
|
671
|
+
return { schema: { type: "object", properties, required }, fieldElements };
|
|
672
|
+
}
|
|
591
673
|
|
|
592
674
|
// src/discovery.ts
|
|
593
675
|
init_registry();
|
|
@@ -610,7 +692,22 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
610
692
|
return new Promise((resolve, reject) => {
|
|
611
693
|
pendingExecutions.set(form, { resolve, reject });
|
|
612
694
|
if (config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0) {
|
|
613
|
-
|
|
695
|
+
setTimeout(() => {
|
|
696
|
+
fillFormFields(form, params);
|
|
697
|
+
let submitForm = form;
|
|
698
|
+
if (!form.isConnected) {
|
|
699
|
+
const liveBtn = document.querySelector(
|
|
700
|
+
'button[type="submit"]:not([disabled]), input[type="submit"]:not([disabled])'
|
|
701
|
+
);
|
|
702
|
+
const found = liveBtn?.closest("form");
|
|
703
|
+
if (found) {
|
|
704
|
+
submitForm = found;
|
|
705
|
+
pendingExecutions.set(submitForm, { resolve, reject });
|
|
706
|
+
attachSubmitInterceptor(submitForm, toolName);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
submitForm.requestSubmit();
|
|
710
|
+
}, 300);
|
|
614
711
|
}
|
|
615
712
|
});
|
|
616
713
|
};
|
|
@@ -641,7 +738,7 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
641
738
|
function setReactValue(el, v) {
|
|
642
739
|
el.focus();
|
|
643
740
|
el.select?.();
|
|
644
|
-
if (document.execCommand("insertText", false, v)) {
|
|
741
|
+
if (document.execCommand("insertText", false, v) && el.value === v) {
|
|
645
742
|
return;
|
|
646
743
|
}
|
|
647
744
|
const setter = el instanceof HTMLTextAreaElement ? _textareaValueSetter : _inputValueSetter;
|
|
@@ -661,11 +758,28 @@ function setReactChecked(el, checked) {
|
|
|
661
758
|
}
|
|
662
759
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
663
760
|
}
|
|
761
|
+
function findInShadowRoots(root, selector) {
|
|
762
|
+
for (const host of Array.from(root.querySelectorAll("*"))) {
|
|
763
|
+
const sr = host.shadowRoot;
|
|
764
|
+
if (!sr)
|
|
765
|
+
continue;
|
|
766
|
+
const found = sr.querySelector(selector);
|
|
767
|
+
if (found)
|
|
768
|
+
return found;
|
|
769
|
+
const deeper = findInShadowRoots(sr, selector);
|
|
770
|
+
if (deeper)
|
|
771
|
+
return deeper;
|
|
772
|
+
}
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
664
775
|
function findNativeField(form, key) {
|
|
665
776
|
const esc = CSS.escape(key);
|
|
666
|
-
|
|
777
|
+
const light = form.querySelector(`[name="${esc}"]`) ?? form.querySelector(
|
|
667
778
|
`input#${esc}, textarea#${esc}, select#${esc}`
|
|
668
779
|
);
|
|
780
|
+
if (light)
|
|
781
|
+
return light;
|
|
782
|
+
return findInShadowRoots(document, `[name="${esc}"]`) ?? findInShadowRoots(document, `input#${esc}, textarea#${esc}, select#${esc}`);
|
|
669
783
|
}
|
|
670
784
|
function fillFormFields(form, params) {
|
|
671
785
|
lastParams.set(form, params);
|
|
@@ -685,15 +799,24 @@ function fillFormFields(form, params) {
|
|
|
685
799
|
}
|
|
686
800
|
const ariaEl = fieldEls?.get(key);
|
|
687
801
|
if (ariaEl) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
802
|
+
let effectiveEl = ariaEl;
|
|
803
|
+
if (!ariaEl.isConnected) {
|
|
804
|
+
const elId = ariaEl.id;
|
|
805
|
+
if (elId) {
|
|
806
|
+
const fresh = document.getElementById(elId) ?? findInShadowRoots(document, `#${CSS.escape(elId)}`);
|
|
807
|
+
if (fresh)
|
|
808
|
+
effectiveEl = fresh;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (effectiveEl instanceof HTMLInputElement) {
|
|
812
|
+
fillInput(effectiveEl, form, key, value);
|
|
813
|
+
} else if (effectiveEl instanceof HTMLTextAreaElement) {
|
|
814
|
+
setReactValue(effectiveEl, String(value ?? ""));
|
|
815
|
+
} else if (effectiveEl instanceof HTMLSelectElement) {
|
|
816
|
+
effectiveEl.value = String(value ?? "");
|
|
817
|
+
effectiveEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
695
818
|
} else {
|
|
696
|
-
fillAriaField(
|
|
819
|
+
fillAriaField(effectiveEl, value);
|
|
697
820
|
}
|
|
698
821
|
}
|
|
699
822
|
}
|
|
@@ -781,6 +904,31 @@ function serializeFormData(form, params, fieldEls) {
|
|
|
781
904
|
}
|
|
782
905
|
return result;
|
|
783
906
|
}
|
|
907
|
+
function fillElement(el, value) {
|
|
908
|
+
if (el instanceof HTMLInputElement) {
|
|
909
|
+
const type = el.type.toLowerCase();
|
|
910
|
+
if (type === "checkbox") {
|
|
911
|
+
setReactChecked(el, Boolean(value));
|
|
912
|
+
} else if (type === "radio") {
|
|
913
|
+
if (el.value === String(value)) {
|
|
914
|
+
if (_checkedSetter)
|
|
915
|
+
_checkedSetter.call(el, true);
|
|
916
|
+
else
|
|
917
|
+
el.checked = true;
|
|
918
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
setReactValue(el, String(value ?? ""));
|
|
922
|
+
}
|
|
923
|
+
} else if (el instanceof HTMLTextAreaElement) {
|
|
924
|
+
setReactValue(el, String(value ?? ""));
|
|
925
|
+
} else if (el instanceof HTMLSelectElement) {
|
|
926
|
+
el.value = String(value ?? "");
|
|
927
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
928
|
+
} else {
|
|
929
|
+
fillAriaField(el, value);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
784
932
|
|
|
785
933
|
// src/enhancer.ts
|
|
786
934
|
async function enrichMetadata(metadata, enhancer) {
|
|
@@ -893,6 +1041,7 @@ async function registerForm(form, config) {
|
|
|
893
1041
|
const execute = buildExecuteHandler(form, config, metadata.name, metadata);
|
|
894
1042
|
await registerFormTool(form, metadata, execute);
|
|
895
1043
|
registeredForms.add(form);
|
|
1044
|
+
registeredFormCount++;
|
|
896
1045
|
if (config.debug) {
|
|
897
1046
|
console.debug(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
|
|
898
1047
|
}
|
|
@@ -912,6 +1061,7 @@ async function unregisterForm(form, config) {
|
|
|
912
1061
|
}
|
|
913
1062
|
var observer = null;
|
|
914
1063
|
var registeredForms = /* @__PURE__ */ new WeakSet();
|
|
1064
|
+
var registeredFormCount = 0;
|
|
915
1065
|
var reAnalysisTimers = /* @__PURE__ */ new Map();
|
|
916
1066
|
var RE_ANALYSIS_DEBOUNCE_MS = 300;
|
|
917
1067
|
function isInterestingNode(node) {
|
|
@@ -993,6 +1143,102 @@ async function scanForms(config) {
|
|
|
993
1143
|
const forms = Array.from(document.querySelectorAll("form"));
|
|
994
1144
|
await Promise.allSettled(forms.map((form) => registerForm(form, config)));
|
|
995
1145
|
}
|
|
1146
|
+
var ORPHAN_EXCLUDED_TYPES = /* @__PURE__ */ new Set([
|
|
1147
|
+
"password",
|
|
1148
|
+
"hidden",
|
|
1149
|
+
"file",
|
|
1150
|
+
"submit",
|
|
1151
|
+
"reset",
|
|
1152
|
+
"button",
|
|
1153
|
+
"image"
|
|
1154
|
+
]);
|
|
1155
|
+
async function scanOrphanInputs(config) {
|
|
1156
|
+
if (!isWebMCPSupported())
|
|
1157
|
+
return;
|
|
1158
|
+
const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button:not([type]):not([disabled])';
|
|
1159
|
+
const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search/i;
|
|
1160
|
+
const orphanInputs = Array.from(
|
|
1161
|
+
document.querySelectorAll(
|
|
1162
|
+
"input:not(form input), textarea:not(form textarea), select:not(form select)"
|
|
1163
|
+
)
|
|
1164
|
+
).filter((el) => {
|
|
1165
|
+
if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase())) {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
const rect = el.getBoundingClientRect();
|
|
1169
|
+
return rect.width > 0 && rect.height > 0;
|
|
1170
|
+
});
|
|
1171
|
+
if (orphanInputs.length === 0)
|
|
1172
|
+
return;
|
|
1173
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1174
|
+
for (const input of orphanInputs) {
|
|
1175
|
+
let container = input.parentElement;
|
|
1176
|
+
let foundContainer = input.parentElement ?? document.body;
|
|
1177
|
+
while (container && container !== document.body) {
|
|
1178
|
+
const hasSubmitBtn = container.querySelector(SUBMIT_BTN_SELECTOR) !== null || Array.from(container.querySelectorAll("button")).some(
|
|
1179
|
+
(b) => SUBMIT_TEXT_RE.test(b.textContent ?? "")
|
|
1180
|
+
);
|
|
1181
|
+
if (hasSubmitBtn) {
|
|
1182
|
+
foundContainer = container;
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
container = container.parentElement;
|
|
1186
|
+
}
|
|
1187
|
+
if (!groups.has(foundContainer))
|
|
1188
|
+
groups.set(foundContainer, []);
|
|
1189
|
+
groups.get(foundContainer).push(input);
|
|
1190
|
+
}
|
|
1191
|
+
for (const [container, inputs] of groups) {
|
|
1192
|
+
const allCandidates = Array.from(
|
|
1193
|
+
container.querySelectorAll(SUBMIT_BTN_SELECTOR)
|
|
1194
|
+
).filter((b) => {
|
|
1195
|
+
const r = b.getBoundingClientRect();
|
|
1196
|
+
return r.width > 0 && r.height > 0;
|
|
1197
|
+
});
|
|
1198
|
+
let submitBtn = allCandidates[allCandidates.length - 1] ?? null;
|
|
1199
|
+
if (!submitBtn) {
|
|
1200
|
+
const pageBtns = Array.from(document.querySelectorAll("button")).filter(
|
|
1201
|
+
(b) => {
|
|
1202
|
+
const r = b.getBoundingClientRect();
|
|
1203
|
+
return r.width > 0 && r.height > 0 && SUBMIT_TEXT_RE.test(b.textContent ?? "");
|
|
1204
|
+
}
|
|
1205
|
+
);
|
|
1206
|
+
submitBtn = pageBtns[pageBtns.length - 1] ?? null;
|
|
1207
|
+
}
|
|
1208
|
+
const metadata = analyzeOrphanInputGroup(container, inputs, submitBtn);
|
|
1209
|
+
const inputPairs = [];
|
|
1210
|
+
const schemaProps = metadata.inputSchema.properties;
|
|
1211
|
+
for (const el of inputs) {
|
|
1212
|
+
const key = el.name || el.dataset["webmcpName"] || el.id || el.getAttribute("aria-label") || null;
|
|
1213
|
+
const safeKey = key ? key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) : null;
|
|
1214
|
+
if (safeKey && schemaProps[safeKey]) {
|
|
1215
|
+
inputPairs.push({ key: safeKey, el });
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
const toolName = metadata.name;
|
|
1219
|
+
const execute = async (params) => {
|
|
1220
|
+
for (const { key, el } of inputPairs) {
|
|
1221
|
+
if (params[key] !== void 0) {
|
|
1222
|
+
fillElement(el, params[key]);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
1226
|
+
return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
|
|
1227
|
+
};
|
|
1228
|
+
try {
|
|
1229
|
+
await navigator.modelContext.registerTool({
|
|
1230
|
+
name: metadata.name,
|
|
1231
|
+
description: metadata.description,
|
|
1232
|
+
inputSchema: metadata.inputSchema,
|
|
1233
|
+
execute
|
|
1234
|
+
});
|
|
1235
|
+
if (config.debug) {
|
|
1236
|
+
console.debug(`[auto-webmcp] Orphan tool registered: ${metadata.name}`, metadata);
|
|
1237
|
+
}
|
|
1238
|
+
} catch {
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
996
1242
|
function warnToolQuality(name, description) {
|
|
997
1243
|
if (/^form_\d+$|^submit$|^form$/.test(name)) {
|
|
998
1244
|
console.warn(`[auto-webmcp] Tool "${name}" has a generic name. Consider adding a toolname or data-webmcp-name attribute.`);
|
|
@@ -1010,9 +1256,13 @@ async function startDiscovery(config) {
|
|
|
1010
1256
|
(resolve) => document.addEventListener("DOMContentLoaded", () => resolve(), { once: true })
|
|
1011
1257
|
);
|
|
1012
1258
|
}
|
|
1259
|
+
registeredFormCount = 0;
|
|
1013
1260
|
startObserver(config);
|
|
1014
1261
|
listenForRouteChanges(config);
|
|
1015
1262
|
await scanForms(config);
|
|
1263
|
+
if (registeredFormCount === 0) {
|
|
1264
|
+
await scanOrphanInputs(config);
|
|
1265
|
+
}
|
|
1016
1266
|
}
|
|
1017
1267
|
function stopDiscovery() {
|
|
1018
1268
|
observer?.disconnect();
|