mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
60151c801f
core/click.mjs (307 LOC) распилен на тонкий dispatcher (~105 LOC) +
3 доменных handler-файла. Закрывает §10 родительского плана (отклонение
на этапе C.10 — «split — наследие плана»).
Структура:
- core/click.mjs (~105 LOC) — dispatcher: ensureConnected, spreadsheet-cell
spec, highlight, confirmation/popup interception, findTarget, dispatch
по target.kind
- core/helpers.mjs +modifierClick(x, y, modifier, {dbl?}) — общий
mouse-click helper с поддержкой Ctrl/Shift модификаторов
- forms/click-popup.mjs (~90 LOC) — clickConfirmationButton +
tryClickPopupItem (popup/confirmation внутри формы — форменный контекст,
не навигация)
- forms/click-form.mjs (~107 LOC) — clickFormTarget: button/tab/submenu +
netMonitor lifecycle + post-click submenu detection + confirmation hint
propagation
- table/click-row.mjs (~95 LOC) — clickGridGroupTarget,
clickGridTreeNodeTarget, clickGridRowTarget с переиспользованием
modifierClick и существующих getGridToggleIcon/shouldClickToggle
Контракт dispatcher → handler: (target, ctx) где
ctx = {formNum, modifier, dblclick, toggle, expand, timeout, table, gridSelector}.
Handler возвращает returnFormState({clicked, ...}).
Граф зависимостей остаётся деревом:
- core/click.mjs → table/click-row, forms/click-popup, forms/click-form, spreadsheet
- table/{filter,grid,row-fill}.mjs → core/click.mjs (другие action-функции)
- handler-модули → helpers, wait, grid-toggle (НЕ click.mjs)
Поведение clickElement 1:1, публичный API без изменений.
netMonitor переехал внутрь clickFormTarget со своим try/finally.
Confirmation hint propagation (тот сайт что Phase 2 НЕ конвертировал)
переехал в clickFormTarget — естественное место.
Точечный регресс 7/7 (02-crud, 05-table, 08-hierarchy, 13-misc, 16-tree-form,
11-report, 01-navigation) + полный 19/19 зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
4.3 KiB
JavaScript
91 lines
4.3 KiB
JavaScript
// web-test forms/click-popup v1.0 — click handlers for in-form popups: confirmation dialogs and open submenus.
|
||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||
//
|
||
// Both handlers run BEFORE clickElement's regular target-finding flow:
|
||
// - clickConfirmationButton intercepts when a pending confirmation dialog is open
|
||
// - tryClickPopupItem intercepts when a submenu/popup is open from a previous click
|
||
|
||
import { page, ACTION_WAIT, normYo } from '../core/state.mjs';
|
||
import { readSubmenuScript } from '../../dom.mjs';
|
||
import { waitForStable } from '../core/wait.mjs';
|
||
import { returnFormState } from '../core/helpers.mjs';
|
||
|
||
/**
|
||
* Click a button in the currently-open confirmation dialog (Да/Нет/Отмена, etc).
|
||
* Caller is responsible for verifying that a confirmation is actually pending
|
||
* (via checkForErrors().confirmation) before invoking this handler.
|
||
*
|
||
* Throws if no button matching `text` is found in the dialog.
|
||
*/
|
||
export async function clickConfirmationButton(text) {
|
||
const btnResult = await page.evaluate(`(() => {
|
||
const norm = s => s?.trim().replace(/\\u00a0/g, ' ') || '';
|
||
const ny = s => s.replace(/ё/gi, 'е').replace(/\\u00a0/g, ' ');
|
||
const target = ny(${JSON.stringify(text.toLowerCase())});
|
||
const btns = [...document.querySelectorAll('a.press.pressButton')].filter(el => el.offsetWidth > 0);
|
||
let best = btns.find(el => ny(norm(el.innerText).toLowerCase()) === target);
|
||
if (!best) best = btns.find(el => ny(norm(el.innerText).toLowerCase()).includes(target));
|
||
if (best) {
|
||
const r = best.getBoundingClientRect();
|
||
return { name: norm(best.innerText), x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2) };
|
||
}
|
||
return { error: 'not_found', available: btns.map(el => norm(el.innerText)).filter(Boolean) };
|
||
})()`);
|
||
if (btnResult?.error) {
|
||
throw new Error(`clickElement: "${text}" not found among confirmation buttons. Available: ${btnResult.available?.join(', ') || 'none'}`);
|
||
}
|
||
await page.mouse.click(btnResult.x, btnResult.y);
|
||
await waitForStable();
|
||
return returnFormState({ clicked: { kind: 'confirmation', name: btnResult.name } });
|
||
}
|
||
|
||
/**
|
||
* Try to click an item inside an already-open submenu/popup.
|
||
*
|
||
* Returns a form-state result on match (kind: 'popupItem' or 'submenuArrow'),
|
||
* or `null` if the requested text doesn't match any visible popup item — in
|
||
* which case the caller should fall through to regular form-element finding.
|
||
*
|
||
* @param {string} text — fuzzy-matched against item labels (NBSP/ё-normalised)
|
||
* @param {Array} popupItems — items already read via readSubmenuScript()
|
||
*/
|
||
export async function tryClickPopupItem(text, popupItems) {
|
||
const target = normYo(text.toLowerCase());
|
||
let found = popupItems.find(i => normYo(i.name.toLowerCase()) === target);
|
||
if (!found) found = popupItems.find(i => normYo(i.name.toLowerCase()).includes(target));
|
||
if (!found) return null;
|
||
|
||
// submenuArrow items (group headers like "Создать", "Печать") — hover to expand nested submenu
|
||
if (found.kind === 'submenuArrow') {
|
||
// page.hover(selector) is more reliable than page.mouse.move(x,y) —
|
||
// some submenu groups don't expand with plain mouse.move
|
||
if (found.id) {
|
||
await page.hover(`[id="${found.id}"]`);
|
||
} else {
|
||
await page.mouse.move(found.x, found.y);
|
||
}
|
||
await page.waitForTimeout(ACTION_WAIT);
|
||
const nestedItems = await page.evaluate(readSubmenuScript());
|
||
const extras = { clicked: { kind: 'submenuArrow', name: found.name } };
|
||
if (Array.isArray(nestedItems)) {
|
||
extras.submenu = nestedItems.map(i => i.name);
|
||
extras.hint = 'Call web_click again with a submenu item name to select it';
|
||
}
|
||
return returnFormState(extras);
|
||
}
|
||
|
||
// Regular submenu/dropdown items — trusted events required.
|
||
// Use mouse.click(x,y) when in viewport; use :visible selector for clipped items
|
||
// (same ID can exist hidden in parent cloud AND visible in nested cloud).
|
||
const vpHeight = await page.evaluate('window.innerHeight');
|
||
if (found.x && found.y && found.y > 0 && found.y < vpHeight) {
|
||
await page.mouse.click(found.x, found.y);
|
||
} else if (found.id) {
|
||
await page.click(`[id="${found.id}"]:visible`);
|
||
} else if (found.x && found.y) {
|
||
await page.mouse.click(found.x, found.y);
|
||
}
|
||
await waitForStable();
|
||
return returnFormState({ clicked: { kind: 'popupItem', name: found.name } });
|
||
}
|