@qontinui/ui-bridge 0.2.0 → 0.3.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.
- package/dist/ai/index.d.mts +4 -4
- package/dist/ai/index.d.ts +4 -4
- package/dist/babel-plugin/index.js +515 -0
- package/dist/babel-plugin/index.js.map +1 -0
- package/dist/babel-plugin/index.mjs +499 -0
- package/dist/babel-plugin/index.mjs.map +1 -0
- package/dist/control/index.d.mts +5 -5
- package/dist/control/index.d.ts +5 -5
- package/dist/core/index.d.mts +115 -44
- package/dist/core/index.d.ts +115 -44
- package/dist/core/index.js +0 -1560
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +1 -1549
- package/dist/core/index.mjs.map +1 -1
- package/dist/debug/index.d.mts +3 -3
- package/dist/debug/index.d.ts +3 -3
- package/dist/index.d.mts +7 -8
- package/dist/index.d.ts +7 -8
- package/dist/index.js +859 -873
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +860 -862
- package/dist/index.mjs.map +1 -1
- package/dist/{metrics-C9XRi_mL.d.ts → metrics-BfiT_rhZ.d.ts} +2 -2
- package/dist/{metrics-NC3csD0R.d.mts → metrics-DTA2bwG7.d.mts} +2 -2
- package/dist/native/control/index.js +453 -0
- package/dist/native/control/index.js.map +1 -0
- package/dist/native/control/index.mjs +450 -0
- package/dist/native/control/index.mjs.map +1 -0
- package/dist/native/core/index.js +486 -0
- package/dist/native/core/index.js.map +1 -0
- package/dist/native/core/index.mjs +475 -0
- package/dist/native/core/index.mjs.map +1 -0
- package/dist/native/debug/index.js +451 -0
- package/dist/native/debug/index.js.map +1 -0
- package/dist/native/debug/index.mjs +449 -0
- package/dist/native/debug/index.mjs.map +1 -0
- package/dist/native/index.js +2274 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/index.mjs +2246 -0
- package/dist/native/index.mjs.map +1 -0
- package/dist/native/react/index.js +1401 -0
- package/dist/native/react/index.js.map +1 -0
- package/dist/native/react/index.mjs +1389 -0
- package/dist/native/react/index.mjs.map +1 -0
- package/dist/native/server/index.js +415 -0
- package/dist/native/server/index.js.map +1 -0
- package/dist/native/server/index.mjs +410 -0
- package/dist/native/server/index.mjs.map +1 -0
- package/dist/react/index.d.mts +20 -7
- package/dist/react/index.d.ts +20 -7
- package/dist/react/index.js +42 -4
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +42 -4
- package/dist/react/index.mjs.map +1 -1
- package/dist/{registry-CIEDjbQ9.d.ts → registry-BKLEm-yk.d.ts} +9 -15
- package/dist/{registry-SsSDq46X.d.mts → registry-BmZgyCz8.d.mts} +9 -15
- package/dist/render-log/index.d.mts +1 -1
- package/dist/render-log/index.d.ts +1 -1
- package/dist/server/express.d.mts +36 -0
- package/dist/server/express.d.ts +36 -0
- package/dist/server/express.js +196 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/express.mjs +192 -0
- package/dist/server/express.mjs.map +1 -0
- package/dist/server/handlers.d.mts +93 -0
- package/dist/server/handlers.d.ts +93 -0
- package/dist/server/handlers.js +4278 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/handlers.mjs +4275 -0
- package/dist/server/handlers.mjs.map +1 -0
- package/dist/server/index.d.mts +10 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.js +5352 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +5337 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/server/nextjs.d.mts +126 -0
- package/dist/server/nextjs.d.ts +126 -0
- package/dist/server/nextjs.js +287 -0
- package/dist/server/nextjs.js.map +1 -0
- package/dist/server/nextjs.mjs +282 -0
- package/dist/server/nextjs.mjs.map +1 -0
- package/dist/server/standalone.d.mts +6 -0
- package/dist/server/standalone.d.ts +6 -0
- package/dist/server/standalone.js +719 -0
- package/dist/server/standalone.js.map +1 -0
- package/dist/server/standalone.mjs +715 -0
- package/dist/server/standalone.mjs.map +1 -0
- package/dist/standalone-BURj8J3G.d.ts +212 -0
- package/dist/standalone-Dwmel29d.d.mts +212 -0
- package/dist/swc-plugin/index.d.mts +79 -0
- package/dist/swc-plugin/index.d.ts +79 -0
- package/dist/swc-plugin/index.js +15 -0
- package/dist/swc-plugin/index.js.map +1 -0
- package/dist/swc-plugin/index.mjs +9 -0
- package/dist/swc-plugin/index.mjs.map +1 -0
- package/dist/{types-CFT3Dnx4.d.mts → types-B5Q0GVo0.d.mts} +115 -3
- package/dist/{types-Dr6tH-bm.d.mts → types-B7J7noLK.d.mts} +1 -1
- package/dist/{types-oCTrRxSw.d.ts → types-BkNRILUa.d.ts} +1 -1
- package/dist/types-CEQLnFMv.d.mts +156 -0
- package/dist/types-CHnlwiTK.d.ts +156 -0
- package/dist/{types-BvCfFuEV.d.ts → types-DfPqwU-i.d.ts} +115 -3
- package/dist/{types-CPMbN_Iw.d.mts → types-jKVgTI6_.d.mts} +356 -160
- package/dist/{types-CPMbN_Iw.d.ts → types-jKVgTI6_.d.ts} +356 -160
- package/package.json +106 -3
- package/swc-plugin-wasm/ui_bridge_swc_plugin.wasm +0 -0
- package/dist/websocket-client-CX4QJesI.d.ts +0 -124
- package/dist/websocket-client-C_Na0OSp.d.mts +0 -124
package/dist/core/index.js
CHANGED
|
@@ -1,1553 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// src/core/element-identifier.ts
|
|
4
|
-
var ID_ATTRIBUTES = ["data-ui-id", "data-testid", "data-awas-element", "id"];
|
|
5
|
-
function generateXPath(element) {
|
|
6
|
-
if (element.id) {
|
|
7
|
-
return `//*[@id="${element.id}"]`;
|
|
8
|
-
}
|
|
9
|
-
const parts = [];
|
|
10
|
-
let current = element;
|
|
11
|
-
while (current && current.nodeType === Node.ELEMENT_NODE) {
|
|
12
|
-
let selector = current.nodeName.toLowerCase();
|
|
13
|
-
const uiId = current.getAttribute("data-ui-id");
|
|
14
|
-
if (uiId) {
|
|
15
|
-
selector += `[@data-ui-id="${uiId}"]`;
|
|
16
|
-
parts.unshift(selector);
|
|
17
|
-
break;
|
|
18
|
-
}
|
|
19
|
-
const testId = current.getAttribute("data-testid");
|
|
20
|
-
if (testId) {
|
|
21
|
-
selector += `[@data-testid="${testId}"]`;
|
|
22
|
-
parts.unshift(selector);
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
const id = current.id;
|
|
26
|
-
if (id) {
|
|
27
|
-
selector += `[@id="${id}"]`;
|
|
28
|
-
parts.unshift(selector);
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
const parentEl = current.parentElement;
|
|
32
|
-
if (parentEl) {
|
|
33
|
-
const currentEl = current;
|
|
34
|
-
const siblings = Array.from(parentEl.children).filter(
|
|
35
|
-
(child) => child.nodeName === currentEl.nodeName
|
|
36
|
-
);
|
|
37
|
-
if (siblings.length > 1) {
|
|
38
|
-
const index = siblings.indexOf(currentEl) + 1;
|
|
39
|
-
selector += `[${index}]`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
parts.unshift(selector);
|
|
43
|
-
current = parentEl;
|
|
44
|
-
}
|
|
45
|
-
return "/" + parts.join("/");
|
|
46
|
-
}
|
|
47
|
-
function generateCSSSelector(element) {
|
|
48
|
-
const uiId = element.getAttribute("data-ui-id");
|
|
49
|
-
if (uiId) {
|
|
50
|
-
return `[data-ui-id="${uiId}"]`;
|
|
51
|
-
}
|
|
52
|
-
const testId = element.getAttribute("data-testid");
|
|
53
|
-
if (testId) {
|
|
54
|
-
return `[data-testid="${testId}"]`;
|
|
55
|
-
}
|
|
56
|
-
const awasId = element.getAttribute("data-awas-element");
|
|
57
|
-
if (awasId) {
|
|
58
|
-
return `[data-awas-element="${awasId}"]`;
|
|
59
|
-
}
|
|
60
|
-
if (element.id) {
|
|
61
|
-
return `#${CSS.escape(element.id)}`;
|
|
62
|
-
}
|
|
63
|
-
const path = [];
|
|
64
|
-
let current = element;
|
|
65
|
-
while (current && current.nodeType === Node.ELEMENT_NODE) {
|
|
66
|
-
let selector = current.nodeName.toLowerCase();
|
|
67
|
-
const parentUiId = current.getAttribute("data-ui-id");
|
|
68
|
-
if (parentUiId && current !== element) {
|
|
69
|
-
path.unshift(`[data-ui-id="${parentUiId}"]`);
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
const parentTestId = current.getAttribute("data-testid");
|
|
73
|
-
if (parentTestId && current !== element) {
|
|
74
|
-
path.unshift(`[data-testid="${parentTestId}"]`);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
if (current.id) {
|
|
78
|
-
path.unshift(`#${CSS.escape(current.id)}`);
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
const parentEl = current.parentElement;
|
|
82
|
-
if (parentEl) {
|
|
83
|
-
const currentEl = current;
|
|
84
|
-
const siblings = Array.from(parentEl.children);
|
|
85
|
-
const sameTagSiblings = siblings.filter(
|
|
86
|
-
(s) => s.nodeName === currentEl.nodeName
|
|
87
|
-
);
|
|
88
|
-
if (sameTagSiblings.length > 1) {
|
|
89
|
-
const index = siblings.indexOf(currentEl) + 1;
|
|
90
|
-
selector += `:nth-child(${index})`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
path.unshift(selector);
|
|
94
|
-
current = current.parentElement;
|
|
95
|
-
}
|
|
96
|
-
return path.join(" > ");
|
|
97
|
-
}
|
|
98
|
-
function getBestIdentifier(element) {
|
|
99
|
-
const uiId = element.getAttribute("data-ui-id");
|
|
100
|
-
if (uiId) return uiId;
|
|
101
|
-
const testId = element.getAttribute("data-testid");
|
|
102
|
-
if (testId) return testId;
|
|
103
|
-
const awasId = element.getAttribute("data-awas-element");
|
|
104
|
-
if (awasId) return awasId;
|
|
105
|
-
if (element.id) return element.id;
|
|
106
|
-
return generateCSSSelector(element);
|
|
107
|
-
}
|
|
108
|
-
function createElementIdentifier(element) {
|
|
109
|
-
return {
|
|
110
|
-
uiId: element.getAttribute("data-ui-id") || void 0,
|
|
111
|
-
testId: element.getAttribute("data-testid") || void 0,
|
|
112
|
-
awasId: element.getAttribute("data-awas-element") || void 0,
|
|
113
|
-
htmlId: element.id || void 0,
|
|
114
|
-
xpath: generateXPath(element),
|
|
115
|
-
selector: generateCSSSelector(element)
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function findElementByIdentifier(identifier, root = document) {
|
|
119
|
-
if (typeof identifier === "string") {
|
|
120
|
-
const byUiId = root.querySelector(`[data-ui-id="${identifier}"]`);
|
|
121
|
-
if (byUiId) return byUiId;
|
|
122
|
-
const byTestId = root.querySelector(`[data-testid="${identifier}"]`);
|
|
123
|
-
if (byTestId) return byTestId;
|
|
124
|
-
const byAwasId = root.querySelector(`[data-awas-element="${identifier}"]`);
|
|
125
|
-
if (byAwasId) return byAwasId;
|
|
126
|
-
const byId = root.querySelector(`#${CSS.escape(identifier)}`);
|
|
127
|
-
if (byId) return byId;
|
|
128
|
-
try {
|
|
129
|
-
const bySelector = root.querySelector(identifier);
|
|
130
|
-
if (bySelector) return bySelector;
|
|
131
|
-
} catch {
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const result = document.evaluate(
|
|
135
|
-
identifier,
|
|
136
|
-
root,
|
|
137
|
-
null,
|
|
138
|
-
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
139
|
-
null
|
|
140
|
-
);
|
|
141
|
-
if (result.singleNodeValue instanceof HTMLElement) {
|
|
142
|
-
return result.singleNodeValue;
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
}
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
if (identifier.uiId) {
|
|
149
|
-
const el = root.querySelector(`[data-ui-id="${identifier.uiId}"]`);
|
|
150
|
-
if (el) return el;
|
|
151
|
-
}
|
|
152
|
-
if (identifier.testId) {
|
|
153
|
-
const el = root.querySelector(`[data-testid="${identifier.testId}"]`);
|
|
154
|
-
if (el) return el;
|
|
155
|
-
}
|
|
156
|
-
if (identifier.awasId) {
|
|
157
|
-
const el = root.querySelector(`[data-awas-element="${identifier.awasId}"]`);
|
|
158
|
-
if (el) return el;
|
|
159
|
-
}
|
|
160
|
-
if (identifier.htmlId) {
|
|
161
|
-
const el = root.querySelector(`#${CSS.escape(identifier.htmlId)}`);
|
|
162
|
-
if (el) return el;
|
|
163
|
-
}
|
|
164
|
-
if (identifier.selector) {
|
|
165
|
-
try {
|
|
166
|
-
const el = root.querySelector(identifier.selector);
|
|
167
|
-
if (el) return el;
|
|
168
|
-
} catch {
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (identifier.xpath) {
|
|
172
|
-
try {
|
|
173
|
-
const result = document.evaluate(
|
|
174
|
-
identifier.xpath,
|
|
175
|
-
root,
|
|
176
|
-
null,
|
|
177
|
-
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
178
|
-
null
|
|
179
|
-
);
|
|
180
|
-
if (result.singleNodeValue instanceof HTMLElement) {
|
|
181
|
-
return result.singleNodeValue;
|
|
182
|
-
}
|
|
183
|
-
} catch {
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
function findAllElementsByIdentifier(pattern, root = document) {
|
|
189
|
-
const results = [];
|
|
190
|
-
try {
|
|
191
|
-
const elements = root.querySelectorAll(pattern);
|
|
192
|
-
results.push(...Array.from(elements));
|
|
193
|
-
if (results.length > 0) return results;
|
|
194
|
-
} catch {
|
|
195
|
-
}
|
|
196
|
-
const partials = [
|
|
197
|
-
`[data-ui-id*="${pattern}"]`,
|
|
198
|
-
`[data-testid*="${pattern}"]`,
|
|
199
|
-
`[data-awas-element*="${pattern}"]`,
|
|
200
|
-
`[id*="${pattern}"]`
|
|
201
|
-
];
|
|
202
|
-
for (const selector of partials) {
|
|
203
|
-
try {
|
|
204
|
-
const elements = root.querySelectorAll(selector);
|
|
205
|
-
for (const el of elements) {
|
|
206
|
-
if (!results.includes(el)) {
|
|
207
|
-
results.push(el);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
} catch {
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return results;
|
|
214
|
-
}
|
|
215
|
-
function elementMatchesIdentifier(element, identifier) {
|
|
216
|
-
if (typeof identifier === "string") {
|
|
217
|
-
return element.getAttribute("data-ui-id") === identifier || element.getAttribute("data-testid") === identifier || element.getAttribute("data-awas-element") === identifier || element.id === identifier || element.matches(identifier);
|
|
218
|
-
}
|
|
219
|
-
return identifier.uiId && element.getAttribute("data-ui-id") === identifier.uiId || identifier.testId && element.getAttribute("data-testid") === identifier.testId || identifier.awasId && element.getAttribute("data-awas-element") === identifier.awasId || identifier.htmlId && element.id === identifier.htmlId || false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/ai/fuzzy-matcher.ts
|
|
223
|
-
var DEFAULT_FUZZY_CONFIG = {
|
|
224
|
-
threshold: 0.7,
|
|
225
|
-
levenshteinWeight: 0.3,
|
|
226
|
-
jaroWinklerWeight: 0.4,
|
|
227
|
-
ngramWeight: 0.3,
|
|
228
|
-
ngramSize: 2,
|
|
229
|
-
caseSensitive: false,
|
|
230
|
-
ignoreWhitespace: true
|
|
231
|
-
};
|
|
232
|
-
function levenshteinDistance(s1, s2) {
|
|
233
|
-
const len1 = s1.length;
|
|
234
|
-
const len2 = s2.length;
|
|
235
|
-
const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));
|
|
236
|
-
for (let i = 0; i <= len1; i++) matrix[i][0] = i;
|
|
237
|
-
for (let j = 0; j <= len2; j++) matrix[0][j] = j;
|
|
238
|
-
for (let i = 1; i <= len1; i++) {
|
|
239
|
-
for (let j = 1; j <= len2; j++) {
|
|
240
|
-
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
241
|
-
matrix[i][j] = Math.min(
|
|
242
|
-
matrix[i - 1][j] + 1,
|
|
243
|
-
// deletion
|
|
244
|
-
matrix[i][j - 1] + 1,
|
|
245
|
-
// insertion
|
|
246
|
-
matrix[i - 1][j - 1] + cost
|
|
247
|
-
// substitution
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return matrix[len1][len2];
|
|
252
|
-
}
|
|
253
|
-
function levenshteinSimilarity(s1, s2) {
|
|
254
|
-
if (s1.length === 0 && s2.length === 0) return 1;
|
|
255
|
-
if (s1.length === 0 || s2.length === 0) return 0;
|
|
256
|
-
const distance = levenshteinDistance(s1, s2);
|
|
257
|
-
const maxLength = Math.max(s1.length, s2.length);
|
|
258
|
-
return 1 - distance / maxLength;
|
|
259
|
-
}
|
|
260
|
-
function jaroSimilarity(s1, s2) {
|
|
261
|
-
if (s1.length === 0 && s2.length === 0) return 1;
|
|
262
|
-
if (s1.length === 0 || s2.length === 0) return 0;
|
|
263
|
-
const matchDistance = Math.floor(Math.max(s1.length, s2.length) / 2) - 1;
|
|
264
|
-
const s1Matches = new Array(s1.length).fill(false);
|
|
265
|
-
const s2Matches = new Array(s2.length).fill(false);
|
|
266
|
-
let matches = 0;
|
|
267
|
-
let transpositions = 0;
|
|
268
|
-
for (let i = 0; i < s1.length; i++) {
|
|
269
|
-
const start = Math.max(0, i - matchDistance);
|
|
270
|
-
const end = Math.min(i + matchDistance + 1, s2.length);
|
|
271
|
-
for (let j = start; j < end; j++) {
|
|
272
|
-
if (s2Matches[j] || s1[i] !== s2[j]) continue;
|
|
273
|
-
s1Matches[i] = true;
|
|
274
|
-
s2Matches[j] = true;
|
|
275
|
-
matches++;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (matches === 0) return 0;
|
|
280
|
-
let k = 0;
|
|
281
|
-
for (let i = 0; i < s1.length; i++) {
|
|
282
|
-
if (!s1Matches[i]) continue;
|
|
283
|
-
while (!s2Matches[k]) k++;
|
|
284
|
-
if (s1[i] !== s2[k]) transpositions++;
|
|
285
|
-
k++;
|
|
286
|
-
}
|
|
287
|
-
return (matches / s1.length + matches / s2.length + (matches - transpositions / 2) / matches) / 3;
|
|
288
|
-
}
|
|
289
|
-
function jaroWinklerSimilarity(s1, s2, prefixScale = 0.1) {
|
|
290
|
-
const jaroSim = jaroSimilarity(s1, s2);
|
|
291
|
-
let prefixLength = 0;
|
|
292
|
-
const maxPrefix = Math.min(4, Math.min(s1.length, s2.length));
|
|
293
|
-
for (let i = 0; i < maxPrefix; i++) {
|
|
294
|
-
if (s1[i] === s2[i]) {
|
|
295
|
-
prefixLength++;
|
|
296
|
-
} else {
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return jaroSim + prefixLength * prefixScale * (1 - jaroSim);
|
|
301
|
-
}
|
|
302
|
-
function generateNgrams(s, n) {
|
|
303
|
-
const ngrams = /* @__PURE__ */ new Set();
|
|
304
|
-
if (s.length < n) {
|
|
305
|
-
ngrams.add(s);
|
|
306
|
-
return ngrams;
|
|
307
|
-
}
|
|
308
|
-
for (let i = 0; i <= s.length - n; i++) {
|
|
309
|
-
ngrams.add(s.substring(i, i + n));
|
|
310
|
-
}
|
|
311
|
-
return ngrams;
|
|
312
|
-
}
|
|
313
|
-
function ngramSimilarity(s1, s2, n = 2) {
|
|
314
|
-
if (s1.length === 0 && s2.length === 0) return 1;
|
|
315
|
-
if (s1.length === 0 || s2.length === 0) return 0;
|
|
316
|
-
const ngrams1 = generateNgrams(s1, n);
|
|
317
|
-
const ngrams2 = generateNgrams(s2, n);
|
|
318
|
-
let intersection = 0;
|
|
319
|
-
for (const ngram of ngrams1) {
|
|
320
|
-
if (ngrams2.has(ngram)) {
|
|
321
|
-
intersection++;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const union = ngrams1.size + ngrams2.size - intersection;
|
|
325
|
-
return union === 0 ? 0 : intersection / union;
|
|
326
|
-
}
|
|
327
|
-
function normalizeString(s, config = {}) {
|
|
328
|
-
let normalized = s;
|
|
329
|
-
if (!config.caseSensitive) {
|
|
330
|
-
normalized = normalized.toLowerCase();
|
|
331
|
-
}
|
|
332
|
-
if (config.ignoreWhitespace !== false) {
|
|
333
|
-
normalized = normalized.replace(/\s+/g, " ").trim();
|
|
334
|
-
}
|
|
335
|
-
return normalized;
|
|
336
|
-
}
|
|
337
|
-
function fuzzyMatch(source, target, config = {}) {
|
|
338
|
-
const finalConfig = { ...DEFAULT_FUZZY_CONFIG, ...config };
|
|
339
|
-
const normalizedSource = normalizeString(source, finalConfig);
|
|
340
|
-
const normalizedTarget = normalizeString(target, finalConfig);
|
|
341
|
-
const levenshteinScore = levenshteinSimilarity(normalizedSource, normalizedTarget);
|
|
342
|
-
const jaroWinklerScore = jaroWinklerSimilarity(normalizedSource, normalizedTarget);
|
|
343
|
-
const ngramScore = ngramSimilarity(normalizedSource, normalizedTarget, finalConfig.ngramSize);
|
|
344
|
-
const similarity = levenshteinScore * finalConfig.levenshteinWeight + jaroWinklerScore * finalConfig.jaroWinklerWeight + ngramScore * finalConfig.ngramWeight;
|
|
345
|
-
return {
|
|
346
|
-
similarity,
|
|
347
|
-
isMatch: similarity >= finalConfig.threshold,
|
|
348
|
-
scores: {
|
|
349
|
-
levenshtein: levenshteinScore,
|
|
350
|
-
jaroWinkler: jaroWinklerScore,
|
|
351
|
-
ngram: ngramScore
|
|
352
|
-
},
|
|
353
|
-
normalizedSource,
|
|
354
|
-
normalizedTarget
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
function tokenize(s) {
|
|
358
|
-
return s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase().split(" ").filter((token) => token.length > 0);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// src/ai/alias-generator.ts
|
|
362
|
-
var DEFAULT_ALIAS_CONFIG = {
|
|
363
|
-
includeText: true,
|
|
364
|
-
includeAriaLabel: true,
|
|
365
|
-
includePlaceholder: true,
|
|
366
|
-
includeTitle: true,
|
|
367
|
-
includeSynonyms: true,
|
|
368
|
-
maxAliases: 20,
|
|
369
|
-
minLength: 2,
|
|
370
|
-
maxLength: 50
|
|
371
|
-
};
|
|
372
|
-
var SYNONYMS = {
|
|
373
|
-
// Submit-related
|
|
374
|
-
submit: ["send", "go", "confirm", "ok", "apply", "save", "done", "finish"],
|
|
375
|
-
send: ["submit", "deliver", "post"],
|
|
376
|
-
save: ["submit", "store", "keep", "apply"],
|
|
377
|
-
cancel: ["close", "dismiss", "abort", "back", "exit", "quit", "nevermind"],
|
|
378
|
-
close: ["cancel", "dismiss", "exit", "x"],
|
|
379
|
-
delete: ["remove", "trash", "erase", "clear", "destroy"],
|
|
380
|
-
remove: ["delete", "clear", "discard"],
|
|
381
|
-
edit: ["modify", "change", "update", "alter"],
|
|
382
|
-
update: ["edit", "modify", "save", "refresh"],
|
|
383
|
-
add: ["create", "new", "plus", "insert"],
|
|
384
|
-
create: ["add", "new", "make"],
|
|
385
|
-
search: ["find", "lookup", "query", "filter"],
|
|
386
|
-
find: ["search", "locate", "lookup"],
|
|
387
|
-
login: ["signin", "sign in", "log in", "authenticate", "enter"],
|
|
388
|
-
logout: ["signout", "sign out", "log out", "exit"],
|
|
389
|
-
register: ["signup", "sign up", "join", "create account"],
|
|
390
|
-
next: ["continue", "forward", "proceed", "advance"],
|
|
391
|
-
previous: ["back", "backward", "return", "prior"],
|
|
392
|
-
back: ["previous", "return", "backward"],
|
|
393
|
-
start: ["begin", "launch", "initiate", "run", "execute"],
|
|
394
|
-
stop: ["end", "halt", "pause", "terminate"],
|
|
395
|
-
enable: ["activate", "turn on", "switch on"],
|
|
396
|
-
disable: ["deactivate", "turn off", "switch off"],
|
|
397
|
-
show: ["display", "reveal", "view", "open"],
|
|
398
|
-
hide: ["conceal", "collapse", "close"],
|
|
399
|
-
expand: ["open", "show", "unfold", "reveal"],
|
|
400
|
-
collapse: ["close", "hide", "fold", "minimize"],
|
|
401
|
-
yes: ["ok", "confirm", "agree", "accept"],
|
|
402
|
-
no: ["cancel", "decline", "reject", "deny"],
|
|
403
|
-
help: ["support", "assistance", "info", "information", "faq"],
|
|
404
|
-
settings: ["preferences", "options", "config", "configuration"],
|
|
405
|
-
profile: ["account", "user", "me"],
|
|
406
|
-
download: ["export", "save", "get"],
|
|
407
|
-
upload: ["import", "load", "attach"],
|
|
408
|
-
refresh: ["reload", "update", "sync"],
|
|
409
|
-
copy: ["duplicate", "clone"],
|
|
410
|
-
paste: ["insert"],
|
|
411
|
-
select: ["choose", "pick"],
|
|
412
|
-
toggle: ["switch", "flip"],
|
|
413
|
-
// Form fields
|
|
414
|
-
email: ["e-mail", "mail"],
|
|
415
|
-
password: ["pass", "pwd", "secret"],
|
|
416
|
-
username: ["user", "login", "account", "name"],
|
|
417
|
-
firstname: ["first name", "given name", "forename"],
|
|
418
|
-
lastname: ["last name", "surname", "family name"],
|
|
419
|
-
fullname: ["full name", "name", "complete name"],
|
|
420
|
-
phone: ["telephone", "tel", "mobile", "cell"],
|
|
421
|
-
address: ["location", "street"],
|
|
422
|
-
city: ["town"],
|
|
423
|
-
country: ["nation"],
|
|
424
|
-
zip: ["zipcode", "postal", "postal code", "postcode"],
|
|
425
|
-
// Navigation
|
|
426
|
-
home: ["main", "start", "dashboard"],
|
|
427
|
-
menu: ["navigation", "nav"],
|
|
428
|
-
sidebar: ["side bar", "side panel", "side menu"]
|
|
429
|
-
};
|
|
430
|
-
var ELEMENT_ACTION_WORDS = {
|
|
431
|
-
button: ["button", "btn", "click"],
|
|
432
|
-
input: ["input", "field", "textbox", "box"],
|
|
433
|
-
textarea: ["textarea", "text area", "text field", "multiline"],
|
|
434
|
-
select: ["select", "dropdown", "combo", "picker", "chooser"],
|
|
435
|
-
checkbox: ["checkbox", "check", "tick"],
|
|
436
|
-
radio: ["radio", "option", "choice"],
|
|
437
|
-
link: ["link", "anchor", "href"],
|
|
438
|
-
form: ["form"],
|
|
439
|
-
menu: ["menu"],
|
|
440
|
-
menuitem: ["menu item", "option"],
|
|
441
|
-
tab: ["tab"],
|
|
442
|
-
dialog: ["dialog", "modal", "popup"],
|
|
443
|
-
switch: ["switch", "toggle"],
|
|
444
|
-
slider: ["slider", "range"]
|
|
445
|
-
};
|
|
446
|
-
function normalizeAlias(text) {
|
|
447
|
-
return text.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
448
|
-
}
|
|
449
|
-
function extractWords(text) {
|
|
450
|
-
const tokens = tokenize(text);
|
|
451
|
-
return tokens.filter((t) => t.length >= 2);
|
|
452
|
-
}
|
|
453
|
-
function generateTextAliases(text, config) {
|
|
454
|
-
if (!text || !config.includeText) return [];
|
|
455
|
-
const aliases = [];
|
|
456
|
-
const normalized = normalizeAlias(text);
|
|
457
|
-
if (normalized.length >= config.minLength && normalized.length <= config.maxLength) {
|
|
458
|
-
aliases.push(normalized);
|
|
459
|
-
}
|
|
460
|
-
const words = extractWords(text);
|
|
461
|
-
for (const word of words) {
|
|
462
|
-
if (word.length >= config.minLength) {
|
|
463
|
-
aliases.push(word);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
if (words.length >= 2 && words.length <= 4) {
|
|
467
|
-
const twoWords = words.slice(0, 2).join(" ");
|
|
468
|
-
if (twoWords.length <= config.maxLength) {
|
|
469
|
-
aliases.push(twoWords);
|
|
470
|
-
}
|
|
471
|
-
if (words.length > 2) {
|
|
472
|
-
const lastTwo = words.slice(-2).join(" ");
|
|
473
|
-
if (lastTwo.length <= config.maxLength) {
|
|
474
|
-
aliases.push(lastTwo);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return aliases;
|
|
479
|
-
}
|
|
480
|
-
function generateSynonyms(aliases, config) {
|
|
481
|
-
if (!config.includeSynonyms) return [];
|
|
482
|
-
const synonyms = [];
|
|
483
|
-
for (const alias of aliases) {
|
|
484
|
-
const words = alias.toLowerCase().split(/\s+/);
|
|
485
|
-
for (const word of words) {
|
|
486
|
-
if (SYNONYMS[word]) {
|
|
487
|
-
for (const synonym of SYNONYMS[word]) {
|
|
488
|
-
const newAlias = alias.toLowerCase().replace(word, synonym);
|
|
489
|
-
if (newAlias !== alias.toLowerCase()) {
|
|
490
|
-
synonyms.push(newAlias);
|
|
491
|
-
}
|
|
492
|
-
if (synonym.length >= config.minLength) {
|
|
493
|
-
synonyms.push(synonym);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return synonyms;
|
|
500
|
-
}
|
|
501
|
-
function generateTypeAliases(elementType) {
|
|
502
|
-
const type = elementType.toLowerCase();
|
|
503
|
-
return ELEMENT_ACTION_WORDS[type] || [type];
|
|
504
|
-
}
|
|
505
|
-
function generateAliases(input, config = {}) {
|
|
506
|
-
const finalConfig = { ...DEFAULT_ALIAS_CONFIG, ...config };
|
|
507
|
-
const aliasSet = /* @__PURE__ */ new Set();
|
|
508
|
-
const addAlias = (alias) => {
|
|
509
|
-
const normalized = normalizeAlias(alias);
|
|
510
|
-
if (normalized.length >= finalConfig.minLength && normalized.length <= finalConfig.maxLength) {
|
|
511
|
-
aliasSet.add(normalized);
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
const addAliases = (aliases2) => {
|
|
515
|
-
for (const alias of aliases2) {
|
|
516
|
-
addAlias(alias);
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
if (finalConfig.includeText && input.textContent) {
|
|
520
|
-
addAliases(generateTextAliases(input.textContent, finalConfig));
|
|
521
|
-
}
|
|
522
|
-
if (finalConfig.includeAriaLabel && input.ariaLabel) {
|
|
523
|
-
addAliases(generateTextAliases(input.ariaLabel, finalConfig));
|
|
524
|
-
}
|
|
525
|
-
if (finalConfig.includeAriaLabel && input.ariaLabelledBy) {
|
|
526
|
-
addAliases(generateTextAliases(input.ariaLabelledBy, finalConfig));
|
|
527
|
-
}
|
|
528
|
-
if (finalConfig.includePlaceholder && input.placeholder) {
|
|
529
|
-
addAliases(generateTextAliases(input.placeholder, finalConfig));
|
|
530
|
-
}
|
|
531
|
-
if (finalConfig.includeTitle && input.title) {
|
|
532
|
-
addAliases(generateTextAliases(input.title, finalConfig));
|
|
533
|
-
}
|
|
534
|
-
if (input.labelText) {
|
|
535
|
-
addAliases(generateTextAliases(input.labelText, finalConfig));
|
|
536
|
-
}
|
|
537
|
-
if (input.id) {
|
|
538
|
-
addAliases(extractWords(input.id));
|
|
539
|
-
}
|
|
540
|
-
if (input.name) {
|
|
541
|
-
addAliases(extractWords(input.name));
|
|
542
|
-
}
|
|
543
|
-
if (input.value && (input.elementType === "button" || input.inputType === "submit" || input.inputType === "button")) {
|
|
544
|
-
addAliases(generateTextAliases(input.value, finalConfig));
|
|
545
|
-
}
|
|
546
|
-
if (input.elementType) {
|
|
547
|
-
addAliases(generateTypeAliases(input.elementType));
|
|
548
|
-
}
|
|
549
|
-
if (input.inputType) {
|
|
550
|
-
addAlias(input.inputType);
|
|
551
|
-
if (input.inputType === "email") {
|
|
552
|
-
addAliases(["email", "e-mail", "email address"]);
|
|
553
|
-
} else if (input.inputType === "password") {
|
|
554
|
-
addAliases(["password", "pass", "pwd"]);
|
|
555
|
-
} else if (input.inputType === "tel") {
|
|
556
|
-
addAliases(["phone", "telephone", "mobile"]);
|
|
557
|
-
} else if (input.inputType === "url") {
|
|
558
|
-
addAliases(["url", "website", "link", "address"]);
|
|
559
|
-
} else if (input.inputType === "search") {
|
|
560
|
-
addAliases(["search", "find", "query"]);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (finalConfig.includeSynonyms) {
|
|
564
|
-
const currentAliases = Array.from(aliasSet);
|
|
565
|
-
addAliases(generateSynonyms(currentAliases, finalConfig));
|
|
566
|
-
}
|
|
567
|
-
let aliases = Array.from(aliasSet);
|
|
568
|
-
aliases.sort((a, b) => a.length - b.length);
|
|
569
|
-
if (aliases.length > finalConfig.maxAliases) {
|
|
570
|
-
aliases = aliases.slice(0, finalConfig.maxAliases);
|
|
571
|
-
}
|
|
572
|
-
return aliases;
|
|
573
|
-
}
|
|
574
|
-
function generateDescription(input) {
|
|
575
|
-
const parts = [];
|
|
576
|
-
let name = input.ariaLabel || input.labelText || input.textContent || input.placeholder || input.title || input.id || input.name;
|
|
577
|
-
if (name) {
|
|
578
|
-
name = name.trim();
|
|
579
|
-
if (name.length > 30) {
|
|
580
|
-
name = name.substring(0, 27) + "...";
|
|
581
|
-
}
|
|
582
|
-
parts.push(`"${name}"`);
|
|
583
|
-
}
|
|
584
|
-
const typeWords = ELEMENT_ACTION_WORDS[input.elementType || ""] || [input.elementType || "element"];
|
|
585
|
-
parts.push(typeWords[0]);
|
|
586
|
-
if (input.inputType && input.inputType !== "text") {
|
|
587
|
-
parts.push(`(${input.inputType})`);
|
|
588
|
-
}
|
|
589
|
-
return parts.join(" ");
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// src/core/registry.ts
|
|
593
|
-
function getElementState(element) {
|
|
594
|
-
const rect = element.getBoundingClientRect();
|
|
595
|
-
const computedStyle = window.getComputedStyle(element);
|
|
596
|
-
const state = {
|
|
597
|
-
visible: isElementVisible(element, rect, computedStyle),
|
|
598
|
-
enabled: !isElementDisabled(element),
|
|
599
|
-
focused: document.activeElement === element,
|
|
600
|
-
rect: {
|
|
601
|
-
x: rect.x,
|
|
602
|
-
y: rect.y,
|
|
603
|
-
width: rect.width,
|
|
604
|
-
height: rect.height,
|
|
605
|
-
top: rect.top,
|
|
606
|
-
right: rect.right,
|
|
607
|
-
bottom: rect.bottom,
|
|
608
|
-
left: rect.left
|
|
609
|
-
},
|
|
610
|
-
textContent: element.textContent?.trim() || void 0,
|
|
611
|
-
computedStyles: {
|
|
612
|
-
display: computedStyle.display,
|
|
613
|
-
visibility: computedStyle.visibility,
|
|
614
|
-
opacity: computedStyle.opacity,
|
|
615
|
-
pointerEvents: computedStyle.pointerEvents
|
|
616
|
-
}
|
|
617
|
-
};
|
|
618
|
-
if (element instanceof HTMLInputElement) {
|
|
619
|
-
state.value = element.value;
|
|
620
|
-
if (element.type === "checkbox" || element.type === "radio") {
|
|
621
|
-
state.checked = element.checked;
|
|
622
|
-
}
|
|
623
|
-
} else if (element instanceof HTMLTextAreaElement) {
|
|
624
|
-
state.value = element.value;
|
|
625
|
-
} else if (element instanceof HTMLSelectElement) {
|
|
626
|
-
state.value = element.value;
|
|
627
|
-
state.selectedOptions = Array.from(element.selectedOptions).map((opt) => opt.value);
|
|
628
|
-
}
|
|
629
|
-
return state;
|
|
630
|
-
}
|
|
631
|
-
function isElementVisible(element, rect, style) {
|
|
632
|
-
if (rect.width === 0 || rect.height === 0) return false;
|
|
633
|
-
if (style.display === "none") return false;
|
|
634
|
-
if (style.visibility === "hidden") return false;
|
|
635
|
-
if (parseFloat(style.opacity) === 0) return false;
|
|
636
|
-
const inViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
637
|
-
return inViewport;
|
|
638
|
-
}
|
|
639
|
-
function isElementDisabled(element) {
|
|
640
|
-
if ("disabled" in element && element.disabled) {
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
if (element.getAttribute("aria-disabled") === "true") {
|
|
644
|
-
return true;
|
|
645
|
-
}
|
|
646
|
-
return false;
|
|
647
|
-
}
|
|
648
|
-
function inferActions(type) {
|
|
649
|
-
const baseActions = ["focus", "blur", "hover"];
|
|
650
|
-
switch (type) {
|
|
651
|
-
case "button":
|
|
652
|
-
return [...baseActions, "click", "doubleClick", "rightClick"];
|
|
653
|
-
case "input":
|
|
654
|
-
return [...baseActions, "click", "type", "clear"];
|
|
655
|
-
case "textarea":
|
|
656
|
-
return [...baseActions, "click", "type", "clear"];
|
|
657
|
-
case "select":
|
|
658
|
-
return [...baseActions, "click", "select"];
|
|
659
|
-
case "checkbox":
|
|
660
|
-
return [...baseActions, "click", "check", "uncheck", "toggle"];
|
|
661
|
-
case "radio":
|
|
662
|
-
return [...baseActions, "click", "check"];
|
|
663
|
-
case "link":
|
|
664
|
-
return [...baseActions, "click"];
|
|
665
|
-
case "form":
|
|
666
|
-
return ["focus", "blur"];
|
|
667
|
-
case "menu":
|
|
668
|
-
case "menuitem":
|
|
669
|
-
return [...baseActions, "click"];
|
|
670
|
-
case "tab":
|
|
671
|
-
return [...baseActions, "click"];
|
|
672
|
-
case "dialog":
|
|
673
|
-
return ["focus", "blur"];
|
|
674
|
-
case "custom":
|
|
675
|
-
default:
|
|
676
|
-
return [...baseActions, "click"];
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
function inferElementType(element) {
|
|
680
|
-
const tagName = element.tagName.toLowerCase();
|
|
681
|
-
const role = element.getAttribute("role");
|
|
682
|
-
if (role) {
|
|
683
|
-
switch (role) {
|
|
684
|
-
case "button":
|
|
685
|
-
return "button";
|
|
686
|
-
case "textbox":
|
|
687
|
-
return "input";
|
|
688
|
-
case "checkbox":
|
|
689
|
-
return "checkbox";
|
|
690
|
-
case "radio":
|
|
691
|
-
return "radio";
|
|
692
|
-
case "link":
|
|
693
|
-
return "link";
|
|
694
|
-
case "listbox":
|
|
695
|
-
case "combobox":
|
|
696
|
-
return "select";
|
|
697
|
-
case "menu":
|
|
698
|
-
return "menu";
|
|
699
|
-
case "menuitem":
|
|
700
|
-
return "menuitem";
|
|
701
|
-
case "tab":
|
|
702
|
-
return "tab";
|
|
703
|
-
case "dialog":
|
|
704
|
-
return "dialog";
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
switch (tagName) {
|
|
708
|
-
case "button":
|
|
709
|
-
return "button";
|
|
710
|
-
case "input": {
|
|
711
|
-
const inputType = element.type;
|
|
712
|
-
if (inputType === "checkbox") return "checkbox";
|
|
713
|
-
if (inputType === "radio") return "radio";
|
|
714
|
-
if (inputType === "submit" || inputType === "button") return "button";
|
|
715
|
-
return "input";
|
|
716
|
-
}
|
|
717
|
-
case "textarea":
|
|
718
|
-
return "textarea";
|
|
719
|
-
case "select":
|
|
720
|
-
return "select";
|
|
721
|
-
case "a":
|
|
722
|
-
return "link";
|
|
723
|
-
case "form":
|
|
724
|
-
return "form";
|
|
725
|
-
default:
|
|
726
|
-
return "custom";
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
var UIBridgeRegistry = class {
|
|
730
|
-
constructor(options = {}) {
|
|
731
|
-
this.elements = /* @__PURE__ */ new Map();
|
|
732
|
-
this.components = /* @__PURE__ */ new Map();
|
|
733
|
-
this.workflows = /* @__PURE__ */ new Map();
|
|
734
|
-
this.eventListeners = /* @__PURE__ */ new Map();
|
|
735
|
-
// State management
|
|
736
|
-
this.states = /* @__PURE__ */ new Map();
|
|
737
|
-
this.stateGroups = /* @__PURE__ */ new Map();
|
|
738
|
-
this.transitions = /* @__PURE__ */ new Map();
|
|
739
|
-
this.activeStates = /* @__PURE__ */ new Set();
|
|
740
|
-
this.options = options;
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Emit an event
|
|
744
|
-
*/
|
|
745
|
-
emit(type, data) {
|
|
746
|
-
const event = {
|
|
747
|
-
type,
|
|
748
|
-
timestamp: Date.now(),
|
|
749
|
-
data
|
|
750
|
-
};
|
|
751
|
-
this.options.onEvent?.(event);
|
|
752
|
-
const listeners = this.eventListeners.get(type);
|
|
753
|
-
if (listeners) {
|
|
754
|
-
for (const listener of listeners) {
|
|
755
|
-
try {
|
|
756
|
-
listener(event);
|
|
757
|
-
} catch (error) {
|
|
758
|
-
console.error(`Error in event listener for ${type}:`, error);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
if (this.options.verbose) {
|
|
763
|
-
console.log("[UIBridge]", type, data);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* Register an event listener
|
|
768
|
-
*/
|
|
769
|
-
on(type, listener) {
|
|
770
|
-
if (!this.eventListeners.has(type)) {
|
|
771
|
-
this.eventListeners.set(type, /* @__PURE__ */ new Set());
|
|
772
|
-
}
|
|
773
|
-
this.eventListeners.get(type).add(listener);
|
|
774
|
-
return () => {
|
|
775
|
-
this.eventListeners.get(type)?.delete(listener);
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* Remove an event listener
|
|
780
|
-
*/
|
|
781
|
-
off(type, listener) {
|
|
782
|
-
this.eventListeners.get(type)?.delete(listener);
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Register an element
|
|
786
|
-
*/
|
|
787
|
-
registerElement(id, element, options = {}) {
|
|
788
|
-
const type = options.type ?? inferElementType(element);
|
|
789
|
-
const actions = options.actions ?? inferActions(type);
|
|
790
|
-
const existingUiId = element.getAttribute("data-ui-id");
|
|
791
|
-
const actualId = existingUiId || id;
|
|
792
|
-
if (!existingUiId) {
|
|
793
|
-
element.setAttribute("data-ui-id", actualId);
|
|
794
|
-
}
|
|
795
|
-
const registered = {
|
|
796
|
-
id: actualId,
|
|
797
|
-
element,
|
|
798
|
-
type,
|
|
799
|
-
label: options.label,
|
|
800
|
-
actions,
|
|
801
|
-
customActions: options.customActions,
|
|
802
|
-
getState: () => getElementState(element),
|
|
803
|
-
getIdentifier: () => createElementIdentifier(element),
|
|
804
|
-
registeredAt: Date.now(),
|
|
805
|
-
mounted: true
|
|
806
|
-
};
|
|
807
|
-
this.elements.set(actualId, registered);
|
|
808
|
-
this.emit("element:registered", { id: actualId, type, label: options.label });
|
|
809
|
-
return registered;
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Unregister an element
|
|
813
|
-
*/
|
|
814
|
-
unregisterElement(id) {
|
|
815
|
-
const registered = this.elements.get(id);
|
|
816
|
-
if (registered) {
|
|
817
|
-
registered.mounted = false;
|
|
818
|
-
registered.element.removeAttribute("data-ui-id");
|
|
819
|
-
this.elements.delete(id);
|
|
820
|
-
this.emit("element:unregistered", { id });
|
|
821
|
-
return true;
|
|
822
|
-
}
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Get a registered element
|
|
827
|
-
*/
|
|
828
|
-
getElement(id) {
|
|
829
|
-
return this.elements.get(id);
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Get all registered elements
|
|
833
|
-
*/
|
|
834
|
-
getAllElements() {
|
|
835
|
-
return Array.from(this.elements.values());
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Find element by DOM element reference
|
|
839
|
-
*/
|
|
840
|
-
findByDOMElement(element) {
|
|
841
|
-
for (const registered of this.elements.values()) {
|
|
842
|
-
if (registered.element === element) {
|
|
843
|
-
return registered;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
return void 0;
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Search for elements using AI search criteria
|
|
850
|
-
*/
|
|
851
|
-
searchElements(criteria) {
|
|
852
|
-
const results = [];
|
|
853
|
-
const threshold = criteria.fuzzyThreshold ?? 0.7;
|
|
854
|
-
for (const element of this.elements.values()) {
|
|
855
|
-
if (!element.mounted) continue;
|
|
856
|
-
const state = element.getState();
|
|
857
|
-
if (!criteria.fuzzy && !state.visible) continue;
|
|
858
|
-
const aliases = element.aliases ?? this.generateElementAliases(element);
|
|
859
|
-
const textContent = state.textContent?.trim() || "";
|
|
860
|
-
const label = element.label || "";
|
|
861
|
-
let maxScore = 0;
|
|
862
|
-
const matchReasons = [];
|
|
863
|
-
const scores = {};
|
|
864
|
-
if (criteria.text) {
|
|
865
|
-
if (textContent.toLowerCase() === criteria.text.toLowerCase() || label.toLowerCase() === criteria.text.toLowerCase()) {
|
|
866
|
-
maxScore = 1;
|
|
867
|
-
matchReasons.push("exact text match");
|
|
868
|
-
scores.text = 1;
|
|
869
|
-
} else if (criteria.fuzzy !== false) {
|
|
870
|
-
const textResult = fuzzyMatch(criteria.text, textContent, { threshold });
|
|
871
|
-
const labelResult = fuzzyMatch(criteria.text, label, { threshold });
|
|
872
|
-
const bestResult = textResult.similarity > labelResult.similarity ? textResult : labelResult;
|
|
873
|
-
if (bestResult.isMatch) {
|
|
874
|
-
scores.text = bestResult.similarity;
|
|
875
|
-
if (bestResult.similarity > maxScore) {
|
|
876
|
-
maxScore = bestResult.similarity;
|
|
877
|
-
matchReasons.push(`text similarity: ${(bestResult.similarity * 100).toFixed(0)}%`);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
if (criteria.textContains) {
|
|
883
|
-
if (textContent.toLowerCase().includes(criteria.textContains.toLowerCase()) || label.toLowerCase().includes(criteria.textContains.toLowerCase())) {
|
|
884
|
-
const containsScore = 0.85;
|
|
885
|
-
scores.text = Math.max(scores.text ?? 0, containsScore);
|
|
886
|
-
if (containsScore > maxScore) {
|
|
887
|
-
maxScore = containsScore;
|
|
888
|
-
matchReasons.push("text contains");
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (criteria.accessibleName) {
|
|
893
|
-
const ariaLabel = element.element.getAttribute("aria-label") || "";
|
|
894
|
-
const accessibleName = ariaLabel || label || textContent;
|
|
895
|
-
if (accessibleName.toLowerCase() === criteria.accessibleName.toLowerCase()) {
|
|
896
|
-
scores.accessibility = 1;
|
|
897
|
-
if (1 > maxScore) {
|
|
898
|
-
maxScore = 1;
|
|
899
|
-
matchReasons.push("accessible name match");
|
|
900
|
-
}
|
|
901
|
-
} else if (criteria.fuzzy !== false) {
|
|
902
|
-
const result = fuzzyMatch(criteria.accessibleName, accessibleName, { threshold });
|
|
903
|
-
if (result.isMatch) {
|
|
904
|
-
scores.accessibility = result.similarity;
|
|
905
|
-
if (result.similarity > maxScore) {
|
|
906
|
-
maxScore = result.similarity;
|
|
907
|
-
matchReasons.push(`accessible name similarity: ${(result.similarity * 100).toFixed(0)}%`);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
if (criteria.role) {
|
|
913
|
-
const role = element.element.getAttribute("role") || this.inferRole(element.type);
|
|
914
|
-
if (role?.toLowerCase() === criteria.role.toLowerCase()) {
|
|
915
|
-
scores.role = 1;
|
|
916
|
-
if (1 > maxScore) {
|
|
917
|
-
maxScore = 1;
|
|
918
|
-
matchReasons.push(`role: ${criteria.role}`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
if (criteria.type) {
|
|
923
|
-
if (element.type === criteria.type) {
|
|
924
|
-
const typeScore = 0.9;
|
|
925
|
-
scores.role = Math.max(scores.role ?? 0, typeScore);
|
|
926
|
-
if (typeScore > maxScore) {
|
|
927
|
-
maxScore = typeScore;
|
|
928
|
-
matchReasons.push(`type: ${criteria.type}`);
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
for (const alias of aliases) {
|
|
933
|
-
const searchText = criteria.text || criteria.textContains || criteria.accessibleName;
|
|
934
|
-
if (searchText) {
|
|
935
|
-
if (alias.toLowerCase() === searchText.toLowerCase()) {
|
|
936
|
-
scores.fuzzy = 1;
|
|
937
|
-
if (1 > maxScore) {
|
|
938
|
-
maxScore = 1;
|
|
939
|
-
matchReasons.push(`alias: "${alias}"`);
|
|
940
|
-
}
|
|
941
|
-
} else if (criteria.fuzzy !== false) {
|
|
942
|
-
const result = fuzzyMatch(searchText, alias, { threshold });
|
|
943
|
-
if (result.isMatch && result.similarity > (scores.fuzzy ?? 0)) {
|
|
944
|
-
scores.fuzzy = result.similarity;
|
|
945
|
-
if (result.similarity > maxScore) {
|
|
946
|
-
maxScore = result.similarity;
|
|
947
|
-
matchReasons.push(`fuzzy alias: "${alias}"`);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
if (maxScore >= threshold) {
|
|
954
|
-
const aiElement = {
|
|
955
|
-
id: element.id,
|
|
956
|
-
type: element.type,
|
|
957
|
-
label: element.label,
|
|
958
|
-
tagName: element.element.tagName.toLowerCase(),
|
|
959
|
-
role: element.element.getAttribute("role") || void 0,
|
|
960
|
-
accessibleName: element.element.getAttribute("aria-label") || element.label,
|
|
961
|
-
actions: element.actions,
|
|
962
|
-
state,
|
|
963
|
-
registered: true,
|
|
964
|
-
description: element.description || generateDescription({
|
|
965
|
-
textContent,
|
|
966
|
-
ariaLabel: element.element.getAttribute("aria-label"),
|
|
967
|
-
elementType: element.type,
|
|
968
|
-
id: element.id,
|
|
969
|
-
labelText: element.label
|
|
970
|
-
}),
|
|
971
|
-
aliases,
|
|
972
|
-
purpose: element.purpose,
|
|
973
|
-
suggestedActions: [],
|
|
974
|
-
semanticType: element.semanticType
|
|
975
|
-
};
|
|
976
|
-
results.push({
|
|
977
|
-
element: aiElement,
|
|
978
|
-
confidence: maxScore,
|
|
979
|
-
matchReasons,
|
|
980
|
-
scores
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
results.sort((a, b) => b.confidence - a.confidence);
|
|
985
|
-
return results;
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* Find element by visible text
|
|
989
|
-
*/
|
|
990
|
-
findByText(text, fuzzy = true) {
|
|
991
|
-
const results = this.searchElements({ text, fuzzy, fuzzyThreshold: fuzzy ? 0.7 : 1 });
|
|
992
|
-
if (results.length > 0) {
|
|
993
|
-
return this.elements.get(results[0].element.id);
|
|
994
|
-
}
|
|
995
|
-
return void 0;
|
|
996
|
-
}
|
|
997
|
-
/**
|
|
998
|
-
* Find element by accessible name
|
|
999
|
-
*/
|
|
1000
|
-
findByAccessibleName(name) {
|
|
1001
|
-
const results = this.searchElements({ accessibleName: name, fuzzy: true });
|
|
1002
|
-
if (results.length > 0) {
|
|
1003
|
-
return this.elements.get(results[0].element.id);
|
|
1004
|
-
}
|
|
1005
|
-
return void 0;
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Generate aliases for an element
|
|
1009
|
-
*/
|
|
1010
|
-
generateElementAliases(element) {
|
|
1011
|
-
const state = element.getState();
|
|
1012
|
-
return generateAliases({
|
|
1013
|
-
textContent: state.textContent,
|
|
1014
|
-
ariaLabel: element.element.getAttribute("aria-label"),
|
|
1015
|
-
placeholder: element.element.getAttribute("placeholder"),
|
|
1016
|
-
title: element.element.getAttribute("title"),
|
|
1017
|
-
elementType: element.type,
|
|
1018
|
-
tagName: element.element.tagName.toLowerCase(),
|
|
1019
|
-
id: element.id,
|
|
1020
|
-
labelText: element.label
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Infer ARIA role from element type
|
|
1025
|
-
*/
|
|
1026
|
-
inferRole(type) {
|
|
1027
|
-
const roleMap = {
|
|
1028
|
-
button: "button",
|
|
1029
|
-
input: "textbox",
|
|
1030
|
-
select: "combobox",
|
|
1031
|
-
checkbox: "checkbox",
|
|
1032
|
-
radio: "radio",
|
|
1033
|
-
link: "link",
|
|
1034
|
-
form: void 0,
|
|
1035
|
-
textarea: "textbox",
|
|
1036
|
-
menu: "menu",
|
|
1037
|
-
menuitem: "menuitem",
|
|
1038
|
-
tab: "tab",
|
|
1039
|
-
dialog: "dialog",
|
|
1040
|
-
custom: void 0,
|
|
1041
|
-
switch: "switch",
|
|
1042
|
-
slider: "slider",
|
|
1043
|
-
combobox: "combobox",
|
|
1044
|
-
listbox: "listbox",
|
|
1045
|
-
option: "option",
|
|
1046
|
-
textbox: "textbox",
|
|
1047
|
-
generic: void 0
|
|
1048
|
-
};
|
|
1049
|
-
return roleMap[type];
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Register a component
|
|
1053
|
-
*/
|
|
1054
|
-
registerComponent(id, options) {
|
|
1055
|
-
const registered = {
|
|
1056
|
-
id,
|
|
1057
|
-
name: options.name,
|
|
1058
|
-
description: options.description,
|
|
1059
|
-
actions: options.actions?.map((a) => ({
|
|
1060
|
-
id: a.id,
|
|
1061
|
-
label: a.label,
|
|
1062
|
-
description: a.description,
|
|
1063
|
-
handler: a.handler
|
|
1064
|
-
})) ?? [],
|
|
1065
|
-
elementIds: options.elementIds,
|
|
1066
|
-
registeredAt: Date.now(),
|
|
1067
|
-
mounted: true
|
|
1068
|
-
};
|
|
1069
|
-
this.components.set(id, registered);
|
|
1070
|
-
this.emit("component:registered", { id, name: options.name });
|
|
1071
|
-
return registered;
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Unregister a component
|
|
1075
|
-
*/
|
|
1076
|
-
unregisterComponent(id) {
|
|
1077
|
-
const component = this.components.get(id);
|
|
1078
|
-
if (component) {
|
|
1079
|
-
component.mounted = false;
|
|
1080
|
-
this.components.delete(id);
|
|
1081
|
-
this.emit("component:unregistered", { id });
|
|
1082
|
-
return true;
|
|
1083
|
-
}
|
|
1084
|
-
return false;
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Get a registered component
|
|
1088
|
-
*/
|
|
1089
|
-
getComponent(id) {
|
|
1090
|
-
return this.components.get(id);
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Get all registered components
|
|
1094
|
-
*/
|
|
1095
|
-
getAllComponents() {
|
|
1096
|
-
return Array.from(this.components.values());
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Register a workflow
|
|
1100
|
-
*/
|
|
1101
|
-
registerWorkflow(workflow) {
|
|
1102
|
-
this.workflows.set(workflow.id, workflow);
|
|
1103
|
-
return workflow;
|
|
1104
|
-
}
|
|
1105
|
-
/**
|
|
1106
|
-
* Unregister a workflow
|
|
1107
|
-
*/
|
|
1108
|
-
unregisterWorkflow(id) {
|
|
1109
|
-
return this.workflows.delete(id);
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Get a workflow
|
|
1113
|
-
*/
|
|
1114
|
-
getWorkflow(id) {
|
|
1115
|
-
return this.workflows.get(id);
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Get all workflows
|
|
1119
|
-
*/
|
|
1120
|
-
getAllWorkflows() {
|
|
1121
|
-
return Array.from(this.workflows.values());
|
|
1122
|
-
}
|
|
1123
|
-
// ==========================================================================
|
|
1124
|
-
// State Management
|
|
1125
|
-
// ==========================================================================
|
|
1126
|
-
/**
|
|
1127
|
-
* Register a state
|
|
1128
|
-
*/
|
|
1129
|
-
registerState(state) {
|
|
1130
|
-
this.states.set(state.id, state);
|
|
1131
|
-
this.emit("element:registered", { id: state.id, type: "state", name: state.name });
|
|
1132
|
-
return state;
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Unregister a state
|
|
1136
|
-
*/
|
|
1137
|
-
unregisterState(id) {
|
|
1138
|
-
const state = this.states.get(id);
|
|
1139
|
-
if (state) {
|
|
1140
|
-
this.activeStates.delete(id);
|
|
1141
|
-
this.states.delete(id);
|
|
1142
|
-
this.emit("element:unregistered", { id, type: "state" });
|
|
1143
|
-
return true;
|
|
1144
|
-
}
|
|
1145
|
-
return false;
|
|
1146
|
-
}
|
|
1147
|
-
/**
|
|
1148
|
-
* Get a registered state
|
|
1149
|
-
*/
|
|
1150
|
-
getState(id) {
|
|
1151
|
-
return this.states.get(id);
|
|
1152
|
-
}
|
|
1153
|
-
/**
|
|
1154
|
-
* Get all registered states
|
|
1155
|
-
*/
|
|
1156
|
-
getAllStates() {
|
|
1157
|
-
return Array.from(this.states.values());
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Register a state group
|
|
1161
|
-
*/
|
|
1162
|
-
registerStateGroup(group) {
|
|
1163
|
-
this.stateGroups.set(group.id, group);
|
|
1164
|
-
return group;
|
|
1165
|
-
}
|
|
1166
|
-
/**
|
|
1167
|
-
* Unregister a state group
|
|
1168
|
-
*/
|
|
1169
|
-
unregisterStateGroup(id) {
|
|
1170
|
-
return this.stateGroups.delete(id);
|
|
1171
|
-
}
|
|
1172
|
-
/**
|
|
1173
|
-
* Get a state group
|
|
1174
|
-
*/
|
|
1175
|
-
getStateGroup(id) {
|
|
1176
|
-
return this.stateGroups.get(id);
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* Get all state groups
|
|
1180
|
-
*/
|
|
1181
|
-
getAllStateGroups() {
|
|
1182
|
-
return Array.from(this.stateGroups.values());
|
|
1183
|
-
}
|
|
1184
|
-
/**
|
|
1185
|
-
* Register a transition
|
|
1186
|
-
*/
|
|
1187
|
-
registerTransition(transition) {
|
|
1188
|
-
this.transitions.set(transition.id, transition);
|
|
1189
|
-
return transition;
|
|
1190
|
-
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Unregister a transition
|
|
1193
|
-
*/
|
|
1194
|
-
unregisterTransition(id) {
|
|
1195
|
-
return this.transitions.delete(id);
|
|
1196
|
-
}
|
|
1197
|
-
/**
|
|
1198
|
-
* Get a transition
|
|
1199
|
-
*/
|
|
1200
|
-
getTransition(id) {
|
|
1201
|
-
return this.transitions.get(id);
|
|
1202
|
-
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Get all transitions
|
|
1205
|
-
*/
|
|
1206
|
-
getAllTransitions() {
|
|
1207
|
-
return Array.from(this.transitions.values());
|
|
1208
|
-
}
|
|
1209
|
-
/**
|
|
1210
|
-
* Get currently active states
|
|
1211
|
-
*/
|
|
1212
|
-
getActiveStates() {
|
|
1213
|
-
return Array.from(this.activeStates);
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Check if a state is active
|
|
1217
|
-
*/
|
|
1218
|
-
isStateActive(id) {
|
|
1219
|
-
return this.activeStates.has(id);
|
|
1220
|
-
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Activate a state
|
|
1223
|
-
*/
|
|
1224
|
-
activateState(id) {
|
|
1225
|
-
const state = this.states.get(id);
|
|
1226
|
-
if (!state) {
|
|
1227
|
-
return false;
|
|
1228
|
-
}
|
|
1229
|
-
for (const activeId of this.activeStates) {
|
|
1230
|
-
const activeState = this.states.get(activeId);
|
|
1231
|
-
if (activeState?.blocking && activeState.id !== id) {
|
|
1232
|
-
return false;
|
|
1233
|
-
}
|
|
1234
|
-
if (activeState?.blocks?.includes(id)) {
|
|
1235
|
-
return false;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
const wasActive = this.activeStates.has(id);
|
|
1239
|
-
this.activeStates.add(id);
|
|
1240
|
-
if (!wasActive) {
|
|
1241
|
-
this.emit("element:stateChanged", {
|
|
1242
|
-
stateId: id,
|
|
1243
|
-
active: true,
|
|
1244
|
-
activeStates: this.getActiveStates()
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
return true;
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Deactivate a state
|
|
1251
|
-
*/
|
|
1252
|
-
deactivateState(id) {
|
|
1253
|
-
const wasActive = this.activeStates.has(id);
|
|
1254
|
-
this.activeStates.delete(id);
|
|
1255
|
-
if (wasActive) {
|
|
1256
|
-
this.emit("element:stateChanged", {
|
|
1257
|
-
stateId: id,
|
|
1258
|
-
active: false,
|
|
1259
|
-
activeStates: this.getActiveStates()
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
return wasActive;
|
|
1263
|
-
}
|
|
1264
|
-
/**
|
|
1265
|
-
* Activate multiple states
|
|
1266
|
-
*/
|
|
1267
|
-
activateStates(ids) {
|
|
1268
|
-
const activated = [];
|
|
1269
|
-
for (const id of ids) {
|
|
1270
|
-
if (this.activateState(id)) {
|
|
1271
|
-
activated.push(id);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
return activated;
|
|
1275
|
-
}
|
|
1276
|
-
/**
|
|
1277
|
-
* Deactivate multiple states
|
|
1278
|
-
*/
|
|
1279
|
-
deactivateStates(ids) {
|
|
1280
|
-
const deactivated = [];
|
|
1281
|
-
for (const id of ids) {
|
|
1282
|
-
if (this.deactivateState(id)) {
|
|
1283
|
-
deactivated.push(id);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
return deactivated;
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* Activate a state group (all states in the group)
|
|
1290
|
-
*/
|
|
1291
|
-
activateStateGroup(groupId) {
|
|
1292
|
-
const group = this.stateGroups.get(groupId);
|
|
1293
|
-
if (!group) return [];
|
|
1294
|
-
return this.activateStates(group.states);
|
|
1295
|
-
}
|
|
1296
|
-
/**
|
|
1297
|
-
* Deactivate a state group (all states in the group)
|
|
1298
|
-
*/
|
|
1299
|
-
deactivateStateGroup(groupId) {
|
|
1300
|
-
const group = this.stateGroups.get(groupId);
|
|
1301
|
-
if (!group) return [];
|
|
1302
|
-
return this.deactivateStates(group.states);
|
|
1303
|
-
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Check if a transition can be executed from current state
|
|
1306
|
-
*/
|
|
1307
|
-
canExecuteTransition(transitionId) {
|
|
1308
|
-
const transition = this.transitions.get(transitionId);
|
|
1309
|
-
if (!transition) return false;
|
|
1310
|
-
return transition.fromStates.some((stateId) => this.activeStates.has(stateId));
|
|
1311
|
-
}
|
|
1312
|
-
/**
|
|
1313
|
-
* Execute a transition
|
|
1314
|
-
*/
|
|
1315
|
-
async executeTransition(transitionId) {
|
|
1316
|
-
const startTime = performance.now();
|
|
1317
|
-
const transition = this.transitions.get(transitionId);
|
|
1318
|
-
if (!transition) {
|
|
1319
|
-
return {
|
|
1320
|
-
success: false,
|
|
1321
|
-
activatedStates: [],
|
|
1322
|
-
deactivatedStates: [],
|
|
1323
|
-
error: `Transition not found: ${transitionId}`,
|
|
1324
|
-
durationMs: performance.now() - startTime
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
if (!this.canExecuteTransition(transitionId)) {
|
|
1328
|
-
return {
|
|
1329
|
-
success: false,
|
|
1330
|
-
activatedStates: [],
|
|
1331
|
-
deactivatedStates: [],
|
|
1332
|
-
error: "Precondition not met: none of the fromStates are active",
|
|
1333
|
-
failedPhase: "precondition",
|
|
1334
|
-
durationMs: performance.now() - startTime
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
try {
|
|
1338
|
-
const deactivated = this.deactivateStates(transition.exitStates);
|
|
1339
|
-
if (transition.exitGroups) {
|
|
1340
|
-
for (const groupId of transition.exitGroups) {
|
|
1341
|
-
deactivated.push(...this.deactivateStateGroup(groupId));
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
const activated = this.activateStates(transition.activateStates);
|
|
1345
|
-
if (transition.activateGroups) {
|
|
1346
|
-
for (const groupId of transition.activateGroups) {
|
|
1347
|
-
activated.push(...this.activateStateGroup(groupId));
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
return {
|
|
1351
|
-
success: true,
|
|
1352
|
-
activatedStates: activated,
|
|
1353
|
-
deactivatedStates: deactivated,
|
|
1354
|
-
durationMs: performance.now() - startTime
|
|
1355
|
-
};
|
|
1356
|
-
} catch (error) {
|
|
1357
|
-
return {
|
|
1358
|
-
success: false,
|
|
1359
|
-
activatedStates: [],
|
|
1360
|
-
deactivatedStates: [],
|
|
1361
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1362
|
-
failedPhase: "execution",
|
|
1363
|
-
durationMs: performance.now() - startTime
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
/**
|
|
1368
|
-
* Find a path from current state to target states
|
|
1369
|
-
*
|
|
1370
|
-
* Uses a simple BFS algorithm for pathfinding.
|
|
1371
|
-
* For more advanced pathfinding (Dijkstra, A*), use the Python state manager service.
|
|
1372
|
-
*/
|
|
1373
|
-
findPath(targetStates) {
|
|
1374
|
-
if (targetStates.every((t) => this.activeStates.has(t))) {
|
|
1375
|
-
return {
|
|
1376
|
-
found: true,
|
|
1377
|
-
transitions: [],
|
|
1378
|
-
totalCost: 0,
|
|
1379
|
-
targetStates,
|
|
1380
|
-
estimatedSteps: 0
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
|
-
const queue = [
|
|
1384
|
-
{ activeStates: new Set(this.activeStates), path: [], cost: 0 }
|
|
1385
|
-
];
|
|
1386
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1387
|
-
while (queue.length > 0) {
|
|
1388
|
-
const current = queue.shift();
|
|
1389
|
-
const stateKey = Array.from(current.activeStates).sort().join(",");
|
|
1390
|
-
if (visited.has(stateKey)) continue;
|
|
1391
|
-
visited.add(stateKey);
|
|
1392
|
-
if (targetStates.every((t) => current.activeStates.has(t))) {
|
|
1393
|
-
return {
|
|
1394
|
-
found: true,
|
|
1395
|
-
transitions: current.path,
|
|
1396
|
-
totalCost: current.cost,
|
|
1397
|
-
targetStates,
|
|
1398
|
-
estimatedSteps: current.path.length
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
for (const transition of this.transitions.values()) {
|
|
1402
|
-
const canExecute = transition.fromStates.some((s) => current.activeStates.has(s));
|
|
1403
|
-
if (!canExecute) continue;
|
|
1404
|
-
const newActive = new Set(current.activeStates);
|
|
1405
|
-
for (const s of transition.exitStates) newActive.delete(s);
|
|
1406
|
-
for (const s of transition.activateStates) newActive.add(s);
|
|
1407
|
-
const newCost = current.cost + (transition.pathCost ?? 1);
|
|
1408
|
-
queue.push({
|
|
1409
|
-
activeStates: newActive,
|
|
1410
|
-
path: [...current.path, transition.id],
|
|
1411
|
-
cost: newCost
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
return {
|
|
1416
|
-
found: false,
|
|
1417
|
-
transitions: [],
|
|
1418
|
-
totalCost: 0,
|
|
1419
|
-
targetStates,
|
|
1420
|
-
estimatedSteps: 0
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
/**
|
|
1424
|
-
* Navigate to target states using pathfinding
|
|
1425
|
-
*/
|
|
1426
|
-
async navigateTo(targetStates) {
|
|
1427
|
-
const startTime = performance.now();
|
|
1428
|
-
const path = this.findPath(targetStates);
|
|
1429
|
-
if (!path.found) {
|
|
1430
|
-
return {
|
|
1431
|
-
success: false,
|
|
1432
|
-
path,
|
|
1433
|
-
executedTransitions: [],
|
|
1434
|
-
finalActiveStates: this.getActiveStates(),
|
|
1435
|
-
error: `No path found to target states: ${targetStates.join(", ")}`,
|
|
1436
|
-
durationMs: performance.now() - startTime
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
const executedTransitions = [];
|
|
1440
|
-
for (const transitionId of path.transitions) {
|
|
1441
|
-
const result = await this.executeTransition(transitionId);
|
|
1442
|
-
if (!result.success) {
|
|
1443
|
-
return {
|
|
1444
|
-
success: false,
|
|
1445
|
-
path,
|
|
1446
|
-
executedTransitions,
|
|
1447
|
-
finalActiveStates: this.getActiveStates(),
|
|
1448
|
-
error: result.error,
|
|
1449
|
-
durationMs: performance.now() - startTime
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
executedTransitions.push(transitionId);
|
|
1453
|
-
}
|
|
1454
|
-
return {
|
|
1455
|
-
success: true,
|
|
1456
|
-
path,
|
|
1457
|
-
executedTransitions,
|
|
1458
|
-
finalActiveStates: this.getActiveStates(),
|
|
1459
|
-
durationMs: performance.now() - startTime
|
|
1460
|
-
};
|
|
1461
|
-
}
|
|
1462
|
-
/**
|
|
1463
|
-
* Create a state snapshot
|
|
1464
|
-
*/
|
|
1465
|
-
createStateSnapshot() {
|
|
1466
|
-
return {
|
|
1467
|
-
timestamp: Date.now(),
|
|
1468
|
-
activeStates: this.getActiveStates(),
|
|
1469
|
-
states: this.getAllStates(),
|
|
1470
|
-
groups: this.getAllStateGroups(),
|
|
1471
|
-
transitions: this.getAllTransitions()
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
|
-
* Create a snapshot of the current state
|
|
1476
|
-
*/
|
|
1477
|
-
createSnapshot() {
|
|
1478
|
-
return {
|
|
1479
|
-
timestamp: Date.now(),
|
|
1480
|
-
elements: this.getAllElements().map((el) => ({
|
|
1481
|
-
id: el.id,
|
|
1482
|
-
type: el.type,
|
|
1483
|
-
label: el.label,
|
|
1484
|
-
identifier: el.getIdentifier(),
|
|
1485
|
-
state: el.getState(),
|
|
1486
|
-
actions: el.actions,
|
|
1487
|
-
customActions: el.customActions ? Object.keys(el.customActions) : void 0
|
|
1488
|
-
})),
|
|
1489
|
-
components: this.getAllComponents().map((comp) => ({
|
|
1490
|
-
id: comp.id,
|
|
1491
|
-
name: comp.name,
|
|
1492
|
-
description: comp.description,
|
|
1493
|
-
actions: comp.actions.map((a) => a.id),
|
|
1494
|
-
elementIds: comp.elementIds
|
|
1495
|
-
})),
|
|
1496
|
-
workflows: this.getAllWorkflows().map((wf) => ({
|
|
1497
|
-
id: wf.id,
|
|
1498
|
-
name: wf.name,
|
|
1499
|
-
description: wf.description,
|
|
1500
|
-
stepCount: wf.steps.length
|
|
1501
|
-
}))
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
|
-
/**
|
|
1505
|
-
* Clear all registrations
|
|
1506
|
-
*/
|
|
1507
|
-
clear() {
|
|
1508
|
-
this.elements.clear();
|
|
1509
|
-
this.components.clear();
|
|
1510
|
-
this.workflows.clear();
|
|
1511
|
-
this.eventListeners.clear();
|
|
1512
|
-
this.states.clear();
|
|
1513
|
-
this.stateGroups.clear();
|
|
1514
|
-
this.transitions.clear();
|
|
1515
|
-
this.activeStates.clear();
|
|
1516
|
-
}
|
|
1517
|
-
/**
|
|
1518
|
-
* Get registry statistics
|
|
1519
|
-
*/
|
|
1520
|
-
getStats() {
|
|
1521
|
-
const elements = this.getAllElements();
|
|
1522
|
-
const components = this.getAllComponents();
|
|
1523
|
-
return {
|
|
1524
|
-
elementCount: elements.length,
|
|
1525
|
-
componentCount: components.length,
|
|
1526
|
-
workflowCount: this.workflows.size,
|
|
1527
|
-
mountedElementCount: elements.filter((e) => e.mounted).length,
|
|
1528
|
-
mountedComponentCount: components.filter((c) => c.mounted).length,
|
|
1529
|
-
stateCount: this.states.size,
|
|
1530
|
-
stateGroupCount: this.stateGroups.size,
|
|
1531
|
-
transitionCount: this.transitions.size,
|
|
1532
|
-
activeStateCount: this.activeStates.size
|
|
1533
|
-
};
|
|
1534
|
-
}
|
|
1535
|
-
};
|
|
1536
|
-
var globalRegistry = null;
|
|
1537
|
-
function getGlobalRegistry() {
|
|
1538
|
-
if (!globalRegistry) {
|
|
1539
|
-
globalRegistry = new UIBridgeRegistry();
|
|
1540
|
-
}
|
|
1541
|
-
return globalRegistry;
|
|
1542
|
-
}
|
|
1543
|
-
function setGlobalRegistry(registry) {
|
|
1544
|
-
globalRegistry = registry;
|
|
1545
|
-
}
|
|
1546
|
-
function resetGlobalRegistry() {
|
|
1547
|
-
globalRegistry?.clear();
|
|
1548
|
-
globalRegistry = null;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
3
|
// src/core/websocket-client.ts
|
|
1552
4
|
function generateId() {
|
|
1553
5
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
@@ -1983,19 +435,7 @@ function createWSClient(config) {
|
|
|
1983
435
|
return new UIBridgeWSClient(config);
|
|
1984
436
|
}
|
|
1985
437
|
|
|
1986
|
-
exports.ID_ATTRIBUTES = ID_ATTRIBUTES;
|
|
1987
|
-
exports.UIBridgeRegistry = UIBridgeRegistry;
|
|
1988
438
|
exports.UIBridgeWSClient = UIBridgeWSClient;
|
|
1989
|
-
exports.createElementIdentifier = createElementIdentifier;
|
|
1990
439
|
exports.createWSClient = createWSClient;
|
|
1991
|
-
exports.elementMatchesIdentifier = elementMatchesIdentifier;
|
|
1992
|
-
exports.findAllElementsByIdentifier = findAllElementsByIdentifier;
|
|
1993
|
-
exports.findElementByIdentifier = findElementByIdentifier;
|
|
1994
|
-
exports.generateCSSSelector = generateCSSSelector;
|
|
1995
|
-
exports.generateXPath = generateXPath;
|
|
1996
|
-
exports.getBestIdentifier = getBestIdentifier;
|
|
1997
|
-
exports.getGlobalRegistry = getGlobalRegistry;
|
|
1998
|
-
exports.resetGlobalRegistry = resetGlobalRegistry;
|
|
1999
|
-
exports.setGlobalRegistry = setGlobalRegistry;
|
|
2000
440
|
//# sourceMappingURL=index.js.map
|
|
2001
441
|
//# sourceMappingURL=index.js.map
|