browser-opt 1.0.24 → 1.0.25
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/browser-core/agent.d.ts +4 -0
- package/dist/browser-core/agent.d.ts.map +1 -1
- package/dist/browser-core/agent.js +13 -0
- package/dist/browser-core/agent.js.map +1 -1
- package/dist/browser-opt/index.d.ts +1 -1
- package/dist/browser-opt/index.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/index.d.ts +1 -1
- package/dist/browser-opt/runner/actions/index.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/index.js +135 -3
- package/dist/browser-opt/runner/actions/index.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/click-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/click-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/click-action.js +10 -4
- package/dist/browser-opt/runner/actions/utils/click-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/date-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/date-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/date-action.js +5 -1
- package/dist/browser-opt/runner/actions/utils/date-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/dom-action.d.ts +14 -3
- package/dist/browser-opt/runner/actions/utils/dom-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/dom-action.js +301 -20
- package/dist/browser-opt/runner/actions/utils/dom-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/fill-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/fill-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/fill-action.js +27 -7
- package/dist/browser-opt/runner/actions/utils/fill-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/select-option-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/select-option-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/select-option-action.js +187 -65
- package/dist/browser-opt/runner/actions/utils/select-option-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.d.ts +2 -2
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.js +79 -19
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/upload-action.d.ts +8 -1
- package/dist/browser-opt/runner/actions/utils/upload-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/upload-action.js +517 -64
- package/dist/browser-opt/runner/actions/utils/upload-action.js.map +1 -1
- package/dist/browser-opt/runner/index.d.ts.map +1 -1
- package/dist/browser-opt/runner/index.js +7 -0
- package/dist/browser-opt/runner/index.js.map +1 -1
- package/dist/browser-opt/runner/step-executor.d.ts.map +1 -1
- package/dist/browser-opt/runner/step-executor.js +39 -8
- package/dist/browser-opt/runner/step-executor.js.map +1 -1
- package/dist/browser-opt/type.d.ts +28 -0
- package/dist/browser-opt/type.d.ts.map +1 -1
- package/dist/browser-opt/utils.d.ts.map +1 -1
- package/dist/browser-opt/utils.js +164 -12
- package/dist/browser-opt/utils.js.map +1 -1
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +11 -0
- package/dist/cli/utils/output.js.map +1 -1
- package/package.json +1 -1
- package/skills/browser-opt/SKILL.md +10 -2
|
@@ -5,30 +5,54 @@ import * as fs from 'node:fs';
|
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { findUploadRef } from '../../../utils.js';
|
|
7
7
|
import { captureTransientSnapshot } from '../../evidence.js';
|
|
8
|
-
/**
|
|
8
|
+
/** 执行上传动作,先展示可见上传区域,再使用快照 ref、隐藏 input 或长表单搜索结果上传。 */
|
|
9
9
|
export async function executeUploadAction(agent, action, snapshot, outputDir, options) {
|
|
10
|
-
|
|
10
|
+
const sources = action.sources ?? [action.source];
|
|
11
|
+
if (sources.length > 1) {
|
|
12
|
+
return executeBatchUploadAction(agent, action.field, sources, outputDir, action.rowNumber);
|
|
13
|
+
}
|
|
11
14
|
const searchOutput = [];
|
|
15
|
+
let ref = action.rowNumber ? null : findUploadRef(snapshot, action.field);
|
|
16
|
+
let scrollTarget = ref;
|
|
12
17
|
if (!ref) {
|
|
13
|
-
const domTarget = findHiddenUploadInputSelector(agent, action.field);
|
|
18
|
+
const domTarget = findHiddenUploadInputSelector(agent, action.field, action.rowNumber);
|
|
14
19
|
if (domTarget) {
|
|
15
20
|
ref = domTarget.selector;
|
|
21
|
+
scrollTarget = domTarget.scrollSelector ?? null;
|
|
16
22
|
searchOutput.push(`upload dom selector ${domTarget.selector}`);
|
|
17
23
|
}
|
|
18
24
|
}
|
|
19
|
-
if (!ref &&
|
|
25
|
+
if (!ref && revealTableUploadInput(agent, action.field, action.rowNumber)) {
|
|
26
|
+
const rowLabel = action.rowNumber ? `第 ${action.rowNumber} 行 ` : '';
|
|
27
|
+
searchOutput.push(`upload reveal table input ${rowLabel}${action.field}`);
|
|
28
|
+
for (let attempt = 1; attempt <= 10 && !ref; attempt += 1) {
|
|
29
|
+
agent.waitMs(100);
|
|
30
|
+
const domTarget = findHiddenUploadInputSelector(agent, action.field, action.rowNumber);
|
|
31
|
+
if (domTarget) {
|
|
32
|
+
ref = domTarget.selector;
|
|
33
|
+
scrollTarget = domTarget.scrollSelector ?? null;
|
|
34
|
+
searchOutput.push(`upload dynamic input ${attempt} ${domTarget.selector}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!ref && !action.rowNumber && options.allowViewportSearch) {
|
|
20
39
|
const scrolled = searchUploadInLongForm(agent, action.field);
|
|
21
40
|
if (scrolled) {
|
|
22
41
|
ref = scrolled.ref;
|
|
42
|
+
scrollTarget = scrolled.ref;
|
|
23
43
|
searchOutput.push(...scrolled.logs);
|
|
24
44
|
}
|
|
25
45
|
}
|
|
26
46
|
if (!ref) {
|
|
27
|
-
|
|
47
|
+
const rowLabel = action.rowNumber ? `第 ${action.rowNumber} 行 ` : '';
|
|
48
|
+
throw new Error(`无法找到上传控件:${rowLabel}${action.field};DOM诊断:${diagnoseUploadTarget(agent, action.field, action.rowNumber)}`);
|
|
28
49
|
}
|
|
29
|
-
|
|
50
|
+
if (scrollTarget) {
|
|
51
|
+
agent.scrollIntoView(scrollTarget);
|
|
52
|
+
}
|
|
53
|
+
const filePath = await prepareUploadFile(sources[0], outputDir);
|
|
30
54
|
const output = agent.upload(ref, [filePath]);
|
|
31
|
-
const completionOutput = waitForUploadCompletion(agent, action.field);
|
|
55
|
+
const completionOutput = waitForUploadCompletion(agent, action.field, action.rowNumber, ref);
|
|
32
56
|
return [
|
|
33
57
|
...searchOutput,
|
|
34
58
|
`upload @${ref} ${filePath}`,
|
|
@@ -36,43 +60,164 @@ export async function executeUploadAction(agent, action, snapshot, outputDir, op
|
|
|
36
60
|
completionOutput,
|
|
37
61
|
].filter(Boolean).join('\n').trim();
|
|
38
62
|
}
|
|
39
|
-
/**
|
|
40
|
-
function
|
|
63
|
+
/** 上传动作必须在目标字段内形成文件、预览或组件成功态,不能只依赖 upload 命令返回。 */
|
|
64
|
+
export function verifyUploadActionEffect(agent, action, afterSnapshot) {
|
|
65
|
+
const expectedCount = action.sources?.length ?? 1;
|
|
66
|
+
const state = getUploadState(agent, action.field, action.rowNumber, expectedCount);
|
|
67
|
+
const rowLabel = action.rowNumber ? `第 ${action.rowNumber} 行` : '';
|
|
68
|
+
const target = `${rowLabel}${action.field}`;
|
|
69
|
+
if (state.failed) {
|
|
70
|
+
return { passed: false, message: `目标字段显示上传失败:${target}` };
|
|
71
|
+
}
|
|
72
|
+
if (state.completed && (state.completedCount ?? expectedCount) >= expectedCount) {
|
|
73
|
+
return { passed: true, message: `已确认上传成功:${target}` };
|
|
74
|
+
}
|
|
75
|
+
const normalizedSnapshot = afterSnapshot.text.replace(/\s+/g, '');
|
|
76
|
+
const escapedField = action.field.replace(/\s+/g, '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
77
|
+
const hasExplicitSuccess = new RegExp(`(?:${escapedField}.{0,80}(?:上传成功|上传完成|预览)|(?:上传成功|上传完成|预览).{0,80}${escapedField})`).test(normalizedSnapshot);
|
|
78
|
+
if (hasExplicitSuccess) {
|
|
79
|
+
return { passed: true, message: `已通过页面状态确认上传成功:${target}` };
|
|
80
|
+
}
|
|
81
|
+
return { passed: false, message: `未在目标字段检测到上传成功状态:${target}` };
|
|
82
|
+
}
|
|
83
|
+
/** 批量上传前滚动字段容器一次,再按 input 的 DOM 顺序逐槽位绑定图片。 */
|
|
84
|
+
async function executeBatchUploadAction(agent, field, sources, outputDir, rowNumber) {
|
|
85
|
+
const { selectors, scrollSelector } = findBatchUploadInputSelectors(agent, field, sources.length, rowNumber);
|
|
86
|
+
if (selectors.length < sources.length) {
|
|
87
|
+
throw new Error(`上传槽位不足:${field}需要 ${sources.length} 个,找到 ${selectors.length} 个`);
|
|
88
|
+
}
|
|
89
|
+
if (scrollSelector) {
|
|
90
|
+
agent.scrollIntoView(scrollSelector);
|
|
91
|
+
}
|
|
92
|
+
const filePaths = await Promise.all(sources.map((source) => prepareUploadFile(source, outputDir)));
|
|
93
|
+
const output = [`upload batch slots ${selectors.join(', ')}`];
|
|
94
|
+
for (const [index, filePath] of filePaths.entries()) {
|
|
95
|
+
const selector = selectors[index];
|
|
96
|
+
output.push(`upload slot ${index + 1} @${selector} ${filePath}`, agent.upload(selector, [filePath]), waitForUploadCompletion(agent, field, rowNumber, selector));
|
|
97
|
+
}
|
|
98
|
+
return output.filter(Boolean).join('\n').trim();
|
|
99
|
+
}
|
|
100
|
+
/** 上传命令返回后等待目标字段出现明确成功态,并兼容只写入文件但未触发组件事件的页面。 */
|
|
101
|
+
function waitForUploadCompletion(agent, field, rowNumber, ref) {
|
|
41
102
|
const waitLogs = [];
|
|
103
|
+
let inputChangeAttempted = false;
|
|
42
104
|
agent.waitMs(300);
|
|
43
105
|
for (let attempt = 1; attempt <= 20; attempt += 1) {
|
|
44
|
-
const
|
|
45
|
-
const uploadHelper = ${uploadDomHelperSource()};
|
|
46
|
-
return JSON.stringify(uploadHelper.getUploadStateByField(${JSON.stringify(field)}));
|
|
47
|
-
})()`;
|
|
48
|
-
const state = parseEvalJson(agent.evaluate(script));
|
|
106
|
+
const state = getUploadState(agent, field, rowNumber, 1);
|
|
49
107
|
if (state.failed) {
|
|
50
108
|
throw new Error(`上传失败:${field}`);
|
|
51
109
|
}
|
|
52
|
-
if (state.
|
|
110
|
+
if (state.completed === true) {
|
|
53
111
|
return [...waitLogs, `upload settled ${field}`].join('\n');
|
|
54
112
|
}
|
|
113
|
+
if (!inputChangeAttempted && state.pending !== true) {
|
|
114
|
+
inputChangeAttempted = true;
|
|
115
|
+
const notification = notifyUploadInputChange(agent, ref);
|
|
116
|
+
if (notification.dispatched === true) {
|
|
117
|
+
waitLogs.push(`upload notify input change ${field} files=${notification.files ?? 0}`);
|
|
118
|
+
agent.waitMs(300);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
55
122
|
waitLogs.push(`upload wait ${attempt} ${field}`);
|
|
56
123
|
agent.waitMs(500);
|
|
57
124
|
}
|
|
58
125
|
throw new Error(`等待上传完成超时:${field}`);
|
|
59
126
|
}
|
|
60
|
-
/**
|
|
61
|
-
function
|
|
127
|
+
/** 仅对已注入文件的隐藏 input 补发原生事件,解决部分 Ant Upload 未响应 CDP 文件写入的问题。 */
|
|
128
|
+
function notifyUploadInputChange(agent, ref) {
|
|
129
|
+
if (!ref.startsWith('[') && !ref.startsWith('#') && !ref.startsWith('.')) {
|
|
130
|
+
return { dispatched: false };
|
|
131
|
+
}
|
|
132
|
+
const script = `(() => {
|
|
133
|
+
const input = document.querySelector(${JSON.stringify(ref)});
|
|
134
|
+
if (!(input instanceof HTMLInputElement) || input.type !== 'file' || !input.files?.length) {
|
|
135
|
+
return JSON.stringify({ dispatched: false, files: input instanceof HTMLInputElement ? input.files?.length || 0 : 0 });
|
|
136
|
+
}
|
|
137
|
+
input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
138
|
+
input.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
139
|
+
return JSON.stringify({ dispatched: true, files: input.files.length });
|
|
140
|
+
})()`;
|
|
141
|
+
try {
|
|
142
|
+
return parseEvalJson(agent.evaluate(script));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return { dispatched: false };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** 统一读取字段级上传状态,供等待逻辑和最终效果校验复用。 */
|
|
149
|
+
function getUploadState(agent, field, rowNumber, expectedCount) {
|
|
150
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : ', null';
|
|
62
151
|
const script = `(() => {
|
|
63
152
|
const uploadHelper = ${uploadDomHelperSource()};
|
|
64
|
-
|
|
153
|
+
return JSON.stringify(uploadHelper.getUploadStateByField(${JSON.stringify(field)}${rowArgument}, ${expectedCount}));
|
|
154
|
+
})()`;
|
|
155
|
+
return parseEvalJson(agent.evaluate(script));
|
|
156
|
+
}
|
|
157
|
+
/** Ant Upload 等组件会隐藏真实 file input,同时返回用于滚动的可见字段容器。 */
|
|
158
|
+
function findHiddenUploadInputSelector(agent, field, rowNumber) {
|
|
159
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
160
|
+
const script = `(() => {
|
|
161
|
+
const uploadHelper = ${uploadDomHelperSource()};
|
|
162
|
+
const result = uploadHelper.findUploadInputByField(${JSON.stringify(field)}${rowArgument});
|
|
65
163
|
return JSON.stringify(result);
|
|
66
164
|
})()
|
|
67
165
|
`;
|
|
68
166
|
try {
|
|
69
167
|
const parsed = parseEvalJson(agent.evaluate(script));
|
|
70
|
-
return parsed.found && parsed.selector
|
|
168
|
+
return parsed.found && parsed.selector
|
|
169
|
+
? { selector: parsed.selector, scrollSelector: parsed.scrollSelector }
|
|
170
|
+
: null;
|
|
71
171
|
}
|
|
72
172
|
catch {
|
|
73
173
|
return null;
|
|
74
174
|
}
|
|
75
175
|
}
|
|
176
|
+
/** 表格上传组件可能在点击单元格入口后才创建 file input,按列头定位首行入口并触发一次。 */
|
|
177
|
+
function revealTableUploadInput(agent, field, rowNumber) {
|
|
178
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
179
|
+
const script = `(() => {
|
|
180
|
+
const uploadHelper = ${uploadDomHelperSource()};
|
|
181
|
+
return JSON.stringify(uploadHelper.revealTableUploadInputByField(${JSON.stringify(field)}${rowArgument}));
|
|
182
|
+
})()`;
|
|
183
|
+
try {
|
|
184
|
+
return parseEvalJson(agent.evaluate(script)).revealed === true;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** 上传定位最终失败时记录表头、数据行和 file input 的真实结构,避免仅凭快照反复猜测。 */
|
|
191
|
+
function diagnoseUploadTarget(agent, field, rowNumber) {
|
|
192
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
193
|
+
const script = `(() => {
|
|
194
|
+
const uploadHelper = ${uploadDomHelperSource()};
|
|
195
|
+
return JSON.stringify(uploadHelper.diagnoseUploadByField(${JSON.stringify(field)}${rowArgument}));
|
|
196
|
+
})()`;
|
|
197
|
+
try {
|
|
198
|
+
return JSON.stringify(parseEvalJson(agent.evaluate(script)).diagnostic ?? {});
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return JSON.stringify({ error: error instanceof Error ? error.message : String(error) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** 批量上传前一次性标记字段内的全部目标 input,确保素材与槽位按页面顺序一一对应。 */
|
|
205
|
+
function findBatchUploadInputSelectors(agent, field, count, rowNumber) {
|
|
206
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
207
|
+
const script = `(() => {
|
|
208
|
+
const uploadHelper = ${uploadDomHelperSource()};
|
|
209
|
+
const result = uploadHelper.findUploadInputsByField(${JSON.stringify(field)}, ${count}${rowArgument});
|
|
210
|
+
return JSON.stringify(result);
|
|
211
|
+
})()
|
|
212
|
+
`;
|
|
213
|
+
try {
|
|
214
|
+
const parsed = parseEvalJson(agent.evaluate(script));
|
|
215
|
+
return { selectors: parsed.selectors ?? [], scrollSelector: parsed.scrollSelector };
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return { selectors: [] };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
76
221
|
/** 返回在页面上下文执行的上传控件定位工具源码,兼容隐藏 input[type=file]。 */
|
|
77
222
|
function uploadDomHelperSource() {
|
|
78
223
|
return `
|
|
@@ -83,6 +228,19 @@ const browserOptUploadVisible = (element) => {
|
|
|
83
228
|
const rect = element.getBoundingClientRect();
|
|
84
229
|
return style.visibility !== 'hidden' && style.display !== 'none' && rect.width > 0 && rect.height > 0;
|
|
85
230
|
};
|
|
231
|
+
const browserOptUploadStableSelector = (element, attribute, prefix) => {
|
|
232
|
+
const existingId = element.getAttribute(attribute);
|
|
233
|
+
const existingSelector = existingId ? '[' + attribute + '="' + existingId + '"]' : '';
|
|
234
|
+
if (existingSelector && document.querySelectorAll(existingSelector).length === 1) {
|
|
235
|
+
return existingSelector;
|
|
236
|
+
}
|
|
237
|
+
const root = document.documentElement;
|
|
238
|
+
const sequence = Number(root.getAttribute('data-browser-opt-upload-sequence') || '0') + 1;
|
|
239
|
+
root.setAttribute('data-browser-opt-upload-sequence', String(sequence));
|
|
240
|
+
const id = prefix + sequence;
|
|
241
|
+
element.setAttribute(attribute, id);
|
|
242
|
+
return '[' + attribute + '="' + id + '"]';
|
|
243
|
+
};
|
|
86
244
|
const browserOptUploadAncestorChain = (element) => {
|
|
87
245
|
const chain = [];
|
|
88
246
|
let current = element;
|
|
@@ -92,8 +250,146 @@ const browserOptUploadAncestorChain = (element) => {
|
|
|
92
250
|
}
|
|
93
251
|
return chain;
|
|
94
252
|
};
|
|
253
|
+
const browserOptUploadTableRoot = (element) => element.closest('.ant-table, .el-table, [role="table"], [role="grid"]')
|
|
254
|
+
|| element.closest('table')?.parentElement;
|
|
255
|
+
const browserOptUploadTableRows = (root) => root
|
|
256
|
+
? [...root.querySelectorAll('tbody tr, [role="row"], .ant-table-tbody-virtual-holder-inner .ant-table-row')]
|
|
257
|
+
.filter((row) => !row.closest('thead') && !row.querySelector(':scope > [role="columnheader"]'))
|
|
258
|
+
: [];
|
|
259
|
+
const browserOptUploadRowCells = (row) => [...row.querySelectorAll(':scope > td, :scope > th, :scope > [role="cell"], :scope > [role="gridcell"], :scope > .ant-table-cell')];
|
|
260
|
+
const browserOptUploadVisibleDescendants = (root, selector) => [...root.querySelectorAll(selector)].filter(browserOptUploadVisible);
|
|
261
|
+
const browserOptUploadRowGroups = (rows) => {
|
|
262
|
+
const groups = [];
|
|
263
|
+
for (const row of [...rows].sort((left, right) => left.getBoundingClientRect().top - right.getBoundingClientRect().top)) {
|
|
264
|
+
const top = row.getBoundingClientRect().top;
|
|
265
|
+
const group = groups.find((item) => Math.abs(item.top - top) <= 2);
|
|
266
|
+
if (group) group.rows.push(row);
|
|
267
|
+
else groups.push({ top, rows: [row] });
|
|
268
|
+
}
|
|
269
|
+
return groups.map((group) => group.rows);
|
|
270
|
+
};
|
|
271
|
+
const browserOptUploadMatchesRow = (element, rowNumber) => {
|
|
272
|
+
if (!rowNumber) return true;
|
|
273
|
+
const row = element.closest('tbody tr, [role="row"], .ant-table-row');
|
|
274
|
+
const root = row ? browserOptUploadTableRoot(row) : null;
|
|
275
|
+
const targetRows = root ? browserOptUploadRowGroups(browserOptUploadTableRows(root))[rowNumber - 1] || [] : [];
|
|
276
|
+
return Boolean(row && targetRows.includes(row));
|
|
277
|
+
};
|
|
278
|
+
const browserOptUploadHorizontalOverlap = (left, right) => Math.max(0, Math.min(left.right, right.right) - Math.max(left.left, right.left));
|
|
279
|
+
const browserOptUploadSyncVirtualHeader = (element) => {
|
|
280
|
+
const virtualTable = element.closest('.ant-table-virtual');
|
|
281
|
+
const virtualBody = element.closest('.ant-table-tbody-virtual-holder');
|
|
282
|
+
const virtualHeader = virtualTable?.querySelector('.ant-table-header');
|
|
283
|
+
if (virtualBody && virtualHeader) {
|
|
284
|
+
virtualHeader.scrollLeft = virtualBody.scrollLeft;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const browserOptUploadCellIdentities = (cell) => {
|
|
288
|
+
const identities = [];
|
|
289
|
+
for (const name of ['data-column-key', 'data-field', 'data-prop', 'data-index', 'aria-colindex']) {
|
|
290
|
+
const value = cell.getAttribute(name);
|
|
291
|
+
if (value) identities.push(name + '=' + value);
|
|
292
|
+
}
|
|
293
|
+
for (const token of cell.classList || []) {
|
|
294
|
+
if (/column|(^|[-_])col([-_]|\\d)|cell[-_]\\d/i.test(token) && !/^(ant-table-cell|el-table__cell)$/.test(token)) {
|
|
295
|
+
identities.push('class=' + token);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const table = cell.closest('table');
|
|
299
|
+
const index = cell.cellIndex;
|
|
300
|
+
const col = table && index >= 0 ? table.querySelectorAll('colgroup col')[index] : null;
|
|
301
|
+
if (col) {
|
|
302
|
+
for (const name of ['name', 'class', 'data-column-key', 'data-index']) {
|
|
303
|
+
const value = col.getAttribute(name);
|
|
304
|
+
if (value) identities.push('col-' + name + '=' + value);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return [...new Set(identities)];
|
|
308
|
+
};
|
|
309
|
+
const browserOptUploadInputsInside = (cell) => {
|
|
310
|
+
if (!cell) return [];
|
|
311
|
+
const inputs = [
|
|
312
|
+
...(cell.matches('input[type="file"]') ? [cell] : []),
|
|
313
|
+
...cell.querySelectorAll('input[type="file"]'),
|
|
314
|
+
];
|
|
315
|
+
return [...new Set(inputs)];
|
|
316
|
+
};
|
|
317
|
+
const browserOptUploadTableColumnCells = (header, rowNumber) => {
|
|
318
|
+
const component = browserOptUploadTableRoot(header);
|
|
319
|
+
if (!component) return [];
|
|
320
|
+
browserOptUploadSyncVirtualHeader(component);
|
|
321
|
+
const headerRect = header.getBoundingClientRect();
|
|
322
|
+
const rows = browserOptUploadTableRows(component);
|
|
323
|
+
const targetRows = rowNumber ? browserOptUploadRowGroups(rows)[rowNumber - 1] || [] : rows;
|
|
324
|
+
const headerCells = [...(header.parentElement?.children || [])];
|
|
325
|
+
const columnIndex = header.cellIndex >= 0 ? header.cellIndex : headerCells.indexOf(header);
|
|
326
|
+
|
|
327
|
+
if (component.matches('.ant-table-virtual') && columnIndex >= 0) {
|
|
328
|
+
const exactCells = targetRows.map((row) => browserOptUploadRowCells(row)[columnIndex]).filter(Boolean);
|
|
329
|
+
if (exactCells.length > 0) return exactCells;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const headerIdentities = browserOptUploadCellIdentities(header);
|
|
333
|
+
const identityCells = headerIdentities.length === 0 ? [] : targetRows
|
|
334
|
+
.flatMap((row) => browserOptUploadRowCells(row)
|
|
335
|
+
.filter((cell) => browserOptUploadCellIdentities(cell).some((identity) => headerIdentities.includes(identity))));
|
|
336
|
+
if (identityCells.length > 0) return identityCells;
|
|
337
|
+
|
|
338
|
+
const sameTextHeaders = [...component.querySelectorAll('thead th, thead td, [role="columnheader"]')]
|
|
339
|
+
.filter((item) => normalizeBrowserOptUploadText(item.textContent || '') === normalizeBrowserOptUploadText(header.textContent || ''));
|
|
340
|
+
const geometricCells = sameTextHeaders.length > 1 ? [] : targetRows
|
|
341
|
+
.flatMap((row) => browserOptUploadRowCells(row)
|
|
342
|
+
.map((cell) => ({ cell, rowTop: row.getBoundingClientRect().top, overlap: browserOptUploadHorizontalOverlap(headerRect, cell.getBoundingClientRect()) })))
|
|
343
|
+
.filter((item) => item.overlap > 0)
|
|
344
|
+
.sort((a, b) => b.overlap - a.overlap || a.rowTop - b.rowTop)
|
|
345
|
+
.map((item) => item.cell);
|
|
346
|
+
if (geometricCells.length > 0) return geometricCells;
|
|
347
|
+
|
|
348
|
+
if (columnIndex < 0) return [];
|
|
349
|
+
const ownTable = header.closest('table');
|
|
350
|
+
const ownRows = ownTable?.querySelector('tbody tr') ? [...ownTable.querySelectorAll('tbody tr')] : [];
|
|
351
|
+
const indexedRows = rowNumber
|
|
352
|
+
? (ownRows.length > 0 ? [ownRows[rowNumber - 1]].filter(Boolean) : targetRows)
|
|
353
|
+
: (ownRows.length > 0 ? ownRows : rows);
|
|
354
|
+
return indexedRows.map((row) => browserOptUploadRowCells(row)[columnIndex]).filter(Boolean);
|
|
355
|
+
};
|
|
356
|
+
const browserOptUploadFieldHeaders = (field) => [...document.querySelectorAll('thead th, thead td, [role="columnheader"]')]
|
|
357
|
+
.filter((element) => browserOptUploadVisible(element))
|
|
358
|
+
.filter((element) => normalizeBrowserOptUploadText(element.textContent || '').includes(normalizeBrowserOptUploadText(field)));
|
|
359
|
+
const browserOptUploadHeadersForCell = (cell) => {
|
|
360
|
+
const root = browserOptUploadTableRoot(cell);
|
|
361
|
+
if (!root) return [];
|
|
362
|
+
const cellRect = cell.getBoundingClientRect();
|
|
363
|
+
const identities = browserOptUploadCellIdentities(cell);
|
|
364
|
+
const headers = [...root.querySelectorAll('thead th, thead td, [role="columnheader"]')]
|
|
365
|
+
.filter((header) => browserOptUploadVisible(header));
|
|
366
|
+
if (root.matches('.ant-table-virtual')) {
|
|
367
|
+
const row = cell.closest('.ant-table-row');
|
|
368
|
+
const columnIndex = row ? browserOptUploadRowCells(row).indexOf(cell) : -1;
|
|
369
|
+
const header = columnIndex >= 0 ? headers[columnIndex] : null;
|
|
370
|
+
if (header) return [header];
|
|
371
|
+
}
|
|
372
|
+
const identityHeaders = identities.length === 0 ? [] : headers
|
|
373
|
+
.filter((header) => browserOptUploadCellIdentities(header).some((identity) => identities.includes(identity)));
|
|
374
|
+
if (identityHeaders.length > 0) return identityHeaders;
|
|
375
|
+
return headers
|
|
376
|
+
.map((header) => ({ header, overlap: browserOptUploadHorizontalOverlap(header.getBoundingClientRect(), cellRect) }))
|
|
377
|
+
.filter((item) => item.overlap > 0)
|
|
378
|
+
.sort((a, b) => b.overlap - a.overlap)
|
|
379
|
+
.map((item) => item.header);
|
|
380
|
+
};
|
|
95
381
|
const browserOptUploadContext = (input, fieldText) => {
|
|
96
382
|
const chain = browserOptUploadAncestorChain(input);
|
|
383
|
+
const cell = input.closest('td, th, [role="cell"], [role="gridcell"], .ant-table-cell');
|
|
384
|
+
const table = cell?.closest('table');
|
|
385
|
+
if (cell) {
|
|
386
|
+
const geometricHeader = browserOptUploadHeadersForCell(cell)
|
|
387
|
+
.find((header) => normalizeBrowserOptUploadText(header.textContent || '').includes(fieldText));
|
|
388
|
+
if (geometricHeader) {
|
|
389
|
+
return { text: normalizeBrowserOptUploadText(geometricHeader.textContent || ''), depth: 0.125, source: 'table-geometry' };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
97
393
|
const directMatch = chain
|
|
98
394
|
.map((element, depth) => ({ element, depth, text: normalizeBrowserOptUploadText(element.textContent || '') }))
|
|
99
395
|
.find((item) => item.text.includes(fieldText));
|
|
@@ -101,6 +397,18 @@ const browserOptUploadContext = (input, fieldText) => {
|
|
|
101
397
|
return { text: directMatch.text, depth: directMatch.depth, source: 'ancestor' };
|
|
102
398
|
}
|
|
103
399
|
|
|
400
|
+
if (cell && table) {
|
|
401
|
+
const headerRows = [...table.querySelectorAll('thead tr')];
|
|
402
|
+
const headerCells = headerRows.length > 0
|
|
403
|
+
? [...headerRows[headerRows.length - 1].querySelectorAll('th, td')]
|
|
404
|
+
: [];
|
|
405
|
+
const header = headerCells[cell.cellIndex];
|
|
406
|
+
const headerText = normalizeBrowserOptUploadText(header?.textContent || '');
|
|
407
|
+
if (headerText.includes(fieldText)) {
|
|
408
|
+
return { text: headerText, depth: 0.25, source: 'table-header' };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
104
412
|
for (const item of chain.map((element, depth) => ({ element, depth }))) {
|
|
105
413
|
const siblings = [item.element.previousElementSibling, item.element.nextElementSibling]
|
|
106
414
|
.filter(Boolean)
|
|
@@ -120,9 +428,44 @@ const browserOptUploadContext = (input, fieldText) => {
|
|
|
120
428
|
|
|
121
429
|
return { text: normalizeBrowserOptUploadText(chain.map((element) => element.textContent || '').join(' ')), depth: 99, source: 'none' };
|
|
122
430
|
};
|
|
123
|
-
function
|
|
431
|
+
function findUploadInputsByField(field, limit, rowNumber = null) {
|
|
124
432
|
const fieldText = normalizeBrowserOptUploadText(field);
|
|
125
|
-
const
|
|
433
|
+
const tableCandidates = [];
|
|
434
|
+
for (const header of browserOptUploadFieldHeaders(field)) {
|
|
435
|
+
for (const cell of browserOptUploadTableColumnCells(header, rowNumber)) {
|
|
436
|
+
for (const input of browserOptUploadInputsInside(cell).filter((candidate) => browserOptUploadMatchesRow(candidate, rowNumber))) {
|
|
437
|
+
tableCandidates.push({ input, cell });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const revealedCells = [...document.querySelectorAll('[data-browser-opt-upload-target-field]')]
|
|
442
|
+
.filter((cell) => cell.getAttribute('data-browser-opt-upload-target-field') === fieldText)
|
|
443
|
+
.filter((cell) => !rowNumber || cell.getAttribute('data-browser-opt-upload-target-row') === String(rowNumber));
|
|
444
|
+
const revealedInputs = [...document.querySelectorAll('input[type="file"]')]
|
|
445
|
+
.filter((input) => !input.hasAttribute('data-browser-opt-upload-before-reveal'));
|
|
446
|
+
if (revealedCells.length === 1 && revealedInputs.length === 1) {
|
|
447
|
+
tableCandidates.push({ input: revealedInputs[0], cell: revealedCells[0] });
|
|
448
|
+
}
|
|
449
|
+
if (tableCandidates.length > 0) {
|
|
450
|
+
const uniqueTableCandidates = [...new Map(tableCandidates.map((item) => [item.input, item])).values()];
|
|
451
|
+
const selectors = uniqueTableCandidates.slice(0, limit).map((candidate) => browserOptUploadStableSelector(
|
|
452
|
+
candidate.input,
|
|
453
|
+
'data-browser-opt-upload-id',
|
|
454
|
+
'browser-opt-upload-',
|
|
455
|
+
));
|
|
456
|
+
const scrollCell = uniqueTableCandidates[0]?.cell;
|
|
457
|
+
let scrollSelector;
|
|
458
|
+
if (scrollCell) {
|
|
459
|
+
scrollSelector = browserOptUploadStableSelector(
|
|
460
|
+
scrollCell,
|
|
461
|
+
'data-browser-opt-upload-scroll-target',
|
|
462
|
+
'browser-opt-upload-scroll-',
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return { found: selectors.length > 0, selectors, count: uniqueTableCandidates.length, scrollSelector };
|
|
466
|
+
}
|
|
467
|
+
const inputs = [...document.querySelectorAll('input[type="file"]')]
|
|
468
|
+
.filter((input) => browserOptUploadMatchesRow(input, rowNumber));
|
|
126
469
|
const candidates = inputs.map((input, index) => {
|
|
127
470
|
const chain = browserOptUploadAncestorChain(input);
|
|
128
471
|
const context = browserOptUploadContext(input, fieldText);
|
|
@@ -131,56 +474,166 @@ function findUploadInputByField(field) {
|
|
|
131
474
|
const score = context.text.includes(fieldText) ? 3 : fieldText.includes(context.text) && context.text ? 2 : 0;
|
|
132
475
|
return { input, index, text: context.text, depth: context.depth, score, visible: Boolean(rect && rect.width > 0 && rect.height > 0) };
|
|
133
476
|
}).sort((a, b) => b.score - a.score || a.depth - b.depth || Number(b.visible) - Number(a.visible) || a.index - b.index);
|
|
134
|
-
|
|
135
|
-
if (
|
|
477
|
+
let selected = candidates.filter((candidate) => candidate.score > 0);
|
|
478
|
+
if (selected.length === 0) {
|
|
136
479
|
if (inputs.length === 1) {
|
|
137
|
-
|
|
138
|
-
|
|
480
|
+
selected = candidates.slice(0, 1);
|
|
481
|
+
} else {
|
|
482
|
+
return { found: false, selectors: [], count: inputs.length };
|
|
139
483
|
}
|
|
140
|
-
return { found: false, count: inputs.length };
|
|
141
484
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
485
|
+
const selectors = selected.slice(0, limit).map((candidate) => browserOptUploadStableSelector(
|
|
486
|
+
candidate.input,
|
|
487
|
+
'data-browser-opt-upload-id',
|
|
488
|
+
'browser-opt-upload-',
|
|
489
|
+
));
|
|
490
|
+
const firstSelected = selected[0];
|
|
491
|
+
const scrollElement = firstSelected
|
|
492
|
+
? browserOptUploadAncestorChain(firstSelected.input)
|
|
493
|
+
.find((element) => browserOptUploadVisible(element) && normalizeBrowserOptUploadText(element.textContent || '').includes(fieldText))
|
|
494
|
+
|| browserOptUploadAncestorChain(firstSelected.input).find((element) => browserOptUploadVisible(element))
|
|
495
|
+
: null;
|
|
496
|
+
let scrollSelector;
|
|
497
|
+
if (scrollElement) {
|
|
498
|
+
scrollSelector = browserOptUploadStableSelector(
|
|
499
|
+
scrollElement,
|
|
500
|
+
'data-browser-opt-upload-scroll-target',
|
|
501
|
+
'browser-opt-upload-scroll-',
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return { found: selectors.length > 0, selectors, count: selected.length, scrollSelector };
|
|
505
|
+
}
|
|
506
|
+
function findUploadInputByField(field, rowNumber = null) {
|
|
507
|
+
const result = findUploadInputsByField(field, 1, rowNumber);
|
|
508
|
+
return result.found
|
|
509
|
+
? { found: true, selector: result.selectors[0], count: result.count, scrollSelector: result.scrollSelector }
|
|
510
|
+
: result;
|
|
145
511
|
}
|
|
146
|
-
function getUploadStateByField(field) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
512
|
+
function getUploadStateByField(field, rowNumber = null, expectedCount = 1) {
|
|
513
|
+
const targets = findUploadInputsByField(field, expectedCount, rowNumber);
|
|
514
|
+
const tableScopes = browserOptUploadFieldHeaders(field)
|
|
515
|
+
.flatMap((header) => browserOptUploadTableColumnCells(header, rowNumber));
|
|
516
|
+
const uniqueTableScopes = [...new Set(tableScopes)].slice(0, expectedCount);
|
|
517
|
+
if ((!targets.found || targets.selectors.length === 0) && uniqueTableScopes.length === 0) {
|
|
518
|
+
return { found: false, pending: false, failed: false, inputFilesCount: 0 };
|
|
150
519
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
520
|
+
const fieldText = normalizeBrowserOptUploadText(field);
|
|
521
|
+
const inputs = (targets.selectors || []).map((selector) => document.querySelector(selector)).filter(Boolean);
|
|
522
|
+
const scopes = uniqueTableScopes.length > 0 ? uniqueTableScopes : inputs.map((input) => {
|
|
523
|
+
const chain = browserOptUploadAncestorChain(input);
|
|
524
|
+
return input.closest('td, th, [role="cell"], [role="gridcell"], .ant-table-cell')
|
|
525
|
+
|| chain.find((element) => /ant-form-item|form-item|field/i.test(String(element.className || ''))
|
|
526
|
+
&& normalizeBrowserOptUploadText(element.textContent || '').includes(fieldText))
|
|
527
|
+
|| chain.find((element) => normalizeBrowserOptUploadText(element.textContent || '').includes(fieldText))
|
|
528
|
+
|| input.parentElement;
|
|
529
|
+
}).filter(Boolean);
|
|
530
|
+
const states = scopes.map((scope, index) => {
|
|
531
|
+
const input = inputs[index] || inputs.find((candidate) => scope.contains(candidate));
|
|
532
|
+
const visibleMatches = (selectors) => [...scope.querySelectorAll(selectors)].filter(browserOptUploadVisible);
|
|
533
|
+
const failed = visibleMatches([
|
|
534
|
+
'.ant-upload-list-item-error',
|
|
535
|
+
'.ant-progress-status-exception',
|
|
536
|
+
'.el-upload-list__item.is-fail',
|
|
537
|
+
'[class*="upload-error"]',
|
|
538
|
+
'[class*="upload-fail"]'
|
|
539
|
+
].join(',')).length > 0 || /上传失败|上传错误/.test(scope.textContent || '');
|
|
540
|
+
const pending = visibleMatches([
|
|
541
|
+
'.ant-upload-list-item-uploading',
|
|
542
|
+
'.ant-upload-list-item-progress',
|
|
543
|
+
'.ant-progress-status-active',
|
|
544
|
+
'.ant-spin-spinning',
|
|
545
|
+
'.anticon-loading',
|
|
546
|
+
'.el-loading-mask',
|
|
547
|
+
'.el-icon-loading',
|
|
548
|
+
'[aria-busy="true"]',
|
|
549
|
+
'[class*="uploading"]'
|
|
550
|
+
].join(',')).length > 0;
|
|
551
|
+
const success = visibleMatches([
|
|
552
|
+
'.ant-upload-list-item-done',
|
|
553
|
+
'.ant-upload-list-item-success',
|
|
554
|
+
'.ant-upload-list-picture-card',
|
|
555
|
+
'.el-upload-list__item.is-success',
|
|
556
|
+
'[class*="upload-success"]',
|
|
557
|
+
'[class*="upload-done"]',
|
|
558
|
+
'img[src]'
|
|
559
|
+
].join(',')).length > 0;
|
|
560
|
+
// file input 持有文件只表示命令写入成功;必须等上传组件渲染出预览或成功态,才能确认业务上传完成。
|
|
561
|
+
const completed = !pending && !failed && success;
|
|
562
|
+
return { pending, failed, completed, inputFilesCount: input?.files?.length || 0 };
|
|
563
|
+
});
|
|
564
|
+
const completedCount = states.filter((state) => state.completed).length;
|
|
565
|
+
return {
|
|
566
|
+
found: true,
|
|
567
|
+
pending: states.some((state) => state.pending),
|
|
568
|
+
failed: states.some((state) => state.failed),
|
|
569
|
+
completed: completedCount >= expectedCount,
|
|
570
|
+
completedCount,
|
|
571
|
+
inputFilesCount: states.reduce((sum, state) => sum + (state.inputFilesCount || 0), 0),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function revealTableUploadInputByField(field, rowNumber = null) {
|
|
575
|
+
for (const header of browserOptUploadFieldHeaders(field)) {
|
|
576
|
+
for (const cell of browserOptUploadTableColumnCells(header, rowNumber)) {
|
|
577
|
+
const candidates = browserOptUploadVisibleDescendants(cell, 'button, label, [role="button"], [class*="upload"], [onclick]')
|
|
578
|
+
.sort((a, b) => {
|
|
579
|
+
const aRect = a.getBoundingClientRect();
|
|
580
|
+
const bRect = b.getBoundingClientRect();
|
|
581
|
+
return aRect.width * aRect.height - bRect.width * bRect.height;
|
|
582
|
+
});
|
|
583
|
+
const trigger = candidates[0] || (browserOptUploadVisible(cell) ? cell : null);
|
|
584
|
+
if (!trigger) continue;
|
|
585
|
+
document.querySelectorAll('[data-browser-opt-upload-before-reveal]').forEach((input) => {
|
|
586
|
+
input.removeAttribute('data-browser-opt-upload-before-reveal');
|
|
587
|
+
});
|
|
588
|
+
document.querySelectorAll('[data-browser-opt-upload-target-field]').forEach((target) => {
|
|
589
|
+
target.removeAttribute('data-browser-opt-upload-target-field');
|
|
590
|
+
target.removeAttribute('data-browser-opt-upload-target-row');
|
|
591
|
+
});
|
|
592
|
+
document.querySelectorAll('input[type="file"]').forEach((input) => {
|
|
593
|
+
input.setAttribute('data-browser-opt-upload-before-reveal', 'true');
|
|
594
|
+
});
|
|
595
|
+
cell.setAttribute('data-browser-opt-upload-target-field', normalizeBrowserOptUploadText(field));
|
|
596
|
+
cell.setAttribute('data-browser-opt-upload-target-row', rowNumber ? String(rowNumber) : '');
|
|
597
|
+
trigger.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
598
|
+
trigger.click();
|
|
599
|
+
return { found: true, revealed: true };
|
|
600
|
+
}
|
|
154
601
|
}
|
|
602
|
+
return { found: false, revealed: false };
|
|
603
|
+
}
|
|
604
|
+
function diagnoseUploadByField(field, rowNumber = null) {
|
|
155
605
|
const fieldText = normalizeBrowserOptUploadText(field);
|
|
156
|
-
const
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
'.ant-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
606
|
+
const headers = [...document.querySelectorAll('thead th, thead td, [role="columnheader"]')]
|
|
607
|
+
.filter((header) => normalizeBrowserOptUploadText(header.textContent || '').includes(fieldText))
|
|
608
|
+
.map((header) => {
|
|
609
|
+
const component = browserOptUploadTableRoot(header);
|
|
610
|
+
const rect = header.getBoundingClientRect();
|
|
611
|
+
return {
|
|
612
|
+
tag: header.tagName,
|
|
613
|
+
className: String(header.className || ''),
|
|
614
|
+
identities: browserOptUploadCellIdentities(header),
|
|
615
|
+
rect: { left: rect.left, right: rect.right, top: rect.top, width: rect.width },
|
|
616
|
+
component: component ? { tag: component.tagName, className: String(component.className || '') } : null,
|
|
617
|
+
rows: browserOptUploadTableRows(component).length,
|
|
618
|
+
ownRows: header.closest('table')?.querySelectorAll('tbody tr').length || 0,
|
|
619
|
+
};
|
|
620
|
+
});
|
|
621
|
+
const inputs = [...document.querySelectorAll('input[type="file"]')]
|
|
622
|
+
.filter((input) => browserOptUploadMatchesRow(input, rowNumber))
|
|
623
|
+
.map((input) => {
|
|
624
|
+
const cell = input.closest('td, th, [role="cell"], [role="gridcell"], .ant-table-cell');
|
|
625
|
+
const context = browserOptUploadContext(input, fieldText);
|
|
626
|
+
return {
|
|
627
|
+
className: String(input.className || ''),
|
|
628
|
+
cellTag: cell?.tagName || null,
|
|
629
|
+
cellClassName: String(cell?.className || ''),
|
|
630
|
+
identities: cell ? browserOptUploadCellIdentities(cell) : [],
|
|
631
|
+
context: { source: context.source, depth: context.depth, matches: context.text.includes(fieldText) },
|
|
632
|
+
};
|
|
633
|
+
});
|
|
634
|
+
return { diagnostic: { headers, inputs, inputCount: inputs.length } };
|
|
635
|
+
}
|
|
636
|
+
return { findUploadInputByField, findUploadInputsByField, getUploadStateByField, revealTableUploadInputByField, diagnoseUploadByField };
|
|
184
637
|
})()
|
|
185
638
|
`;
|
|
186
639
|
}
|