// web-test forms/click-form v1.1 — click handler for form-element targets: button, tab, submenu, link, field-focus. // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills // // Called by core/click.mjs dispatcher after target is found. // Owns the CDP network-monitor lifecycle for button clicks (server roundtrip waits), // post-click submenu detection (split buttons like "Создать на основании"), // and confirmation hint propagation in the final state. import { page, ACTION_WAIT } from '../core/state.mjs'; import { detectFormScript, readSubmenuScript, } from '../../dom.mjs'; import { checkForErrors } from '../core/errors.mjs'; import { waitForStable, startNetworkMonitor } from '../core/wait.mjs'; import { safeClick, returnFormState, isInputFocused } from '../core/helpers.mjs'; /** * Click a form target (button, tab, submenu, link) using its resolved {kind, id, x, y, name}. * Handles three special concerns: * 1. **netMonitor** for `kind: 'button'` — captures CDP requests started by the click * so we can wait for them (when the form doesn't change) before stabilising. * 2. **Submenu detection** — both pre-click (`kind: 'submenu'` already known) and * post-click (split buttons like "Создать на основании" which open a popup). * Returns `submenu[]` items as a hint for the caller. * 3. **Confirmation propagation** — if a confirmation dialog opens as a result of the * click, surface `confirmation` and `hint` fields on the returned state so the * caller can react with Да/Нет/Отмена on the next call. */ export async function clickFormTarget(target, ctx) { const { formNum, timeout } = ctx; let netMonitor = null; try { // CDP network monitor BEFORE the click for buttons — captures all server requests // triggered by the click so we can wait for them after. if (target.kind === 'button') { try { netMonitor = await startNetworkMonitor(); } catch {} } // Tabs without ID — use coordinate click to avoid global [data-content] ambiguity if (target.kind === 'tab' && !target.id && target.x && target.y) { await page.mouse.click(target.x, target.y); } else { const selector = `[id="${target.id}"]`; // Use Playwright click for proper mousedown/mouseup events await safeClick(selector, { timeout: 5000 }); } // Pre-known submenu button — read popup items and return them as hints if (target.kind === 'submenu') { await page.waitForTimeout(ACTION_WAIT); const submenuItems = await page.evaluate(readSubmenuScript()); const extras = { clicked: { kind: 'submenu', name: target.name } }; if (Array.isArray(submenuItems)) { extras.submenu = submenuItems.map(i => i.name); extras.hint = 'Call web_click again with a submenu item name to select it'; } return returnFormState(extras); } await waitForStable(formNum); // Check if the click opened a popup/submenu (split buttons like "Создать на основании") const openedPopup = await page.evaluate(readSubmenuScript()); if (Array.isArray(openedPopup) && openedPopup.length > 0) { return returnFormState({ clicked: { kind: 'submenu', name: target.name }, submenu: openedPopup.map(i => i.name), hint: 'Call web_click again with a submenu item name to select it', }); } // For buttons that trigger server-side operations (post, write, etc.), // the DOM may stabilise BEFORE the server response arrives. // The CDP monitor (started before click) lets us wait for all in-flight requests // to complete (300ms debounce) or for a modal/balloon/confirm to appear. // Skip for grid edit mode (e.g. "Добавить" row) — no server round-trip expected. if (target.kind === 'button') { const postForm = await page.evaluate(detectFormScript()); if (postForm === formNum) { const inGridEdit = await page.evaluate(`(() => { const f = document.activeElement; if (!f || (f.tagName !== 'INPUT' && f.tagName !== 'TEXTAREA')) return false; let n = f; while (n) { if (n.classList?.contains('grid')) return true; n = n.parentElement; } return false; })()`); if (!inGridEdit && netMonitor) { await netMonitor.waitDone(timeout); await waitForStable(); } } } // Build final state with confirmation propagation // (the one custom branch deliberately skipped by Phase 2 — surfaces confirmation // + hint when a save/delete dialog opened as a result of the click). const extras = { clicked: { kind: target.kind, name: target.name } }; const err = await checkForErrors(); if (err?.confirmation) { extras.confirmation = err.confirmation; extras.hint = 'Call web_click with a button name (e.g. "Да", "Нет", "Отмена") to respond'; } return returnFormState(extras); } finally { if (netMonitor) try { await netMonitor.cleanup(); } catch {} } } /** * Focus a form input field (last-resort target kind: 'field') by clicking the input itself — * does NOT change its value. Lets the caller then drive focus-dependent shortcuts * (F4 selection form, Shift+F4 clear, etc.) via getPage().keyboard. * Returns flat form state with `focused: { field, id, ok }`; `ok` reflects whether the * input actually received focus (false for disabled/readonly fields). Never throws on ok=false. */ export async function focusFormField(target, ctx) { const selector = `[id="${target.id}"]`; await safeClick(selector, { timeout: 5000 }); await waitForStable(ctx.formNum); const ok = await isInputFocused({ allowTextarea: true }); return returnFormState({ focused: { field: target.name, id: target.id, ok } }); }