@qontinui/ui-bridge 0.1.1

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.
Files changed (47) hide show
  1. package/dist/control/index.d.mts +134 -0
  2. package/dist/control/index.d.ts +134 -0
  3. package/dist/control/index.js +924 -0
  4. package/dist/control/index.js.map +1 -0
  5. package/dist/control/index.mjs +919 -0
  6. package/dist/control/index.mjs.map +1 -0
  7. package/dist/core/index.d.mts +52 -0
  8. package/dist/core/index.d.ts +52 -0
  9. package/dist/core/index.js +1424 -0
  10. package/dist/core/index.js.map +1 -0
  11. package/dist/core/index.mjs +1409 -0
  12. package/dist/core/index.mjs.map +1 -0
  13. package/dist/debug/index.d.mts +93 -0
  14. package/dist/debug/index.d.ts +93 -0
  15. package/dist/debug/index.js +673 -0
  16. package/dist/debug/index.js.map +1 -0
  17. package/dist/debug/index.mjs +664 -0
  18. package/dist/debug/index.mjs.map +1 -0
  19. package/dist/index.d.mts +12 -0
  20. package/dist/index.d.ts +12 -0
  21. package/dist/index.js +4719 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/index.mjs +4665 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/metrics-BCG7z7Aq.d.mts +147 -0
  26. package/dist/metrics-QCnK0EFw.d.ts +147 -0
  27. package/dist/react/index.d.mts +786 -0
  28. package/dist/react/index.d.ts +786 -0
  29. package/dist/react/index.js +4312 -0
  30. package/dist/react/index.js.map +1 -0
  31. package/dist/react/index.mjs +4290 -0
  32. package/dist/react/index.mjs.map +1 -0
  33. package/dist/registry-CT6BVVKr.d.mts +253 -0
  34. package/dist/registry-D4mQ01B3.d.ts +253 -0
  35. package/dist/render-log/index.d.mts +340 -0
  36. package/dist/render-log/index.d.ts +340 -0
  37. package/dist/render-log/index.js +702 -0
  38. package/dist/render-log/index.js.map +1 -0
  39. package/dist/render-log/index.mjs +695 -0
  40. package/dist/render-log/index.mjs.map +1 -0
  41. package/dist/types-BDkXy5si.d.ts +354 -0
  42. package/dist/types-BpvpStn3.d.mts +802 -0
  43. package/dist/types-BpvpStn3.d.ts +802 -0
  44. package/dist/types-DdJD9yw5.d.mts +354 -0
  45. package/dist/websocket-client-B2LC9CYc.d.mts +124 -0
  46. package/dist/websocket-client-DupH0X7B.d.ts +124 -0
  47. package/package.json +83 -0
@@ -0,0 +1,919 @@
1
+ // src/core/element-identifier.ts
2
+ function findElementByIdentifier(identifier, root = document) {
3
+ if (typeof identifier === "string") {
4
+ const byUiId = root.querySelector(`[data-ui-id="${identifier}"]`);
5
+ if (byUiId) return byUiId;
6
+ const byTestId = root.querySelector(`[data-testid="${identifier}"]`);
7
+ if (byTestId) return byTestId;
8
+ const byAwasId = root.querySelector(`[data-awas-element="${identifier}"]`);
9
+ if (byAwasId) return byAwasId;
10
+ const byId = root.querySelector(`#${CSS.escape(identifier)}`);
11
+ if (byId) return byId;
12
+ try {
13
+ const bySelector = root.querySelector(identifier);
14
+ if (bySelector) return bySelector;
15
+ } catch {
16
+ }
17
+ try {
18
+ const result = document.evaluate(
19
+ identifier,
20
+ root,
21
+ null,
22
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
23
+ null
24
+ );
25
+ if (result.singleNodeValue instanceof HTMLElement) {
26
+ return result.singleNodeValue;
27
+ }
28
+ } catch {
29
+ }
30
+ return null;
31
+ }
32
+ if (identifier.uiId) {
33
+ const el = root.querySelector(`[data-ui-id="${identifier.uiId}"]`);
34
+ if (el) return el;
35
+ }
36
+ if (identifier.testId) {
37
+ const el = root.querySelector(`[data-testid="${identifier.testId}"]`);
38
+ if (el) return el;
39
+ }
40
+ if (identifier.awasId) {
41
+ const el = root.querySelector(`[data-awas-element="${identifier.awasId}"]`);
42
+ if (el) return el;
43
+ }
44
+ if (identifier.htmlId) {
45
+ const el = root.querySelector(`#${CSS.escape(identifier.htmlId)}`);
46
+ if (el) return el;
47
+ }
48
+ if (identifier.selector) {
49
+ try {
50
+ const el = root.querySelector(identifier.selector);
51
+ if (el) return el;
52
+ } catch {
53
+ }
54
+ }
55
+ if (identifier.xpath) {
56
+ try {
57
+ const result = document.evaluate(
58
+ identifier.xpath,
59
+ root,
60
+ null,
61
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
62
+ null
63
+ );
64
+ if (result.singleNodeValue instanceof HTMLElement) {
65
+ return result.singleNodeValue;
66
+ }
67
+ } catch {
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+
73
+ // src/control/action-executor.ts
74
+ var DEFAULT_WAIT_OPTIONS = {
75
+ visible: true,
76
+ enabled: true,
77
+ focused: false,
78
+ state: {},
79
+ timeout: 1e4,
80
+ interval: 100
81
+ };
82
+ function getElementState(element) {
83
+ const rect = element.getBoundingClientRect();
84
+ const style = window.getComputedStyle(element);
85
+ const state = {
86
+ visible: isVisible(element, rect, style),
87
+ enabled: !isDisabled(element),
88
+ focused: document.activeElement === element,
89
+ rect: {
90
+ x: rect.x,
91
+ y: rect.y,
92
+ width: rect.width,
93
+ height: rect.height,
94
+ top: rect.top,
95
+ right: rect.right,
96
+ bottom: rect.bottom,
97
+ left: rect.left
98
+ },
99
+ computedStyles: {
100
+ display: style.display,
101
+ visibility: style.visibility,
102
+ opacity: style.opacity,
103
+ pointerEvents: style.pointerEvents
104
+ }
105
+ };
106
+ if (element instanceof HTMLInputElement) {
107
+ state.value = element.value;
108
+ if (element.type === "checkbox" || element.type === "radio") {
109
+ state.checked = element.checked;
110
+ }
111
+ } else if (element instanceof HTMLTextAreaElement) {
112
+ state.value = element.value;
113
+ } else if (element instanceof HTMLSelectElement) {
114
+ state.value = element.value;
115
+ state.selectedOptions = Array.from(element.selectedOptions).map((opt) => opt.value);
116
+ }
117
+ return state;
118
+ }
119
+ function isVisible(element, rect, style) {
120
+ if (rect.width === 0 || rect.height === 0) return false;
121
+ if (style.display === "none") return false;
122
+ if (style.visibility === "hidden") return false;
123
+ if (parseFloat(style.opacity) === 0) return false;
124
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
125
+ }
126
+ function isDisabled(element) {
127
+ if ("disabled" in element && element.disabled) return true;
128
+ if (element.getAttribute("aria-disabled") === "true") return true;
129
+ return false;
130
+ }
131
+ function sleep(ms) {
132
+ return new Promise((resolve) => setTimeout(resolve, ms));
133
+ }
134
+ function createMouseEvent(type, element, options) {
135
+ const rect = element.getBoundingClientRect();
136
+ const x = options?.position?.x ?? rect.width / 2;
137
+ const y = options?.position?.y ?? rect.height / 2;
138
+ return new MouseEvent(type, {
139
+ bubbles: true,
140
+ cancelable: true,
141
+ view: window,
142
+ button: options?.button === "right" ? 2 : options?.button === "middle" ? 1 : 0,
143
+ clientX: rect.left + x,
144
+ clientY: rect.top + y
145
+ });
146
+ }
147
+ var DefaultActionExecutor = class {
148
+ constructor(registry) {
149
+ this.registry = registry;
150
+ }
151
+ /**
152
+ * Execute an action on an element
153
+ */
154
+ async executeAction(elementId, request) {
155
+ const startTime = performance.now();
156
+ let waitDurationMs = 0;
157
+ try {
158
+ const registered = this.registry.getElement(elementId);
159
+ let element = registered?.element ?? null;
160
+ if (!element) {
161
+ element = findElementByIdentifier(elementId);
162
+ }
163
+ if (!element) {
164
+ return {
165
+ success: false,
166
+ error: `Element not found: ${elementId}`,
167
+ durationMs: performance.now() - startTime,
168
+ timestamp: Date.now(),
169
+ requestId: request.requestId
170
+ };
171
+ }
172
+ if (request.waitOptions) {
173
+ const waitResult = await this.waitForElement(element, request.waitOptions);
174
+ waitDurationMs = waitResult.waitedMs;
175
+ if (!waitResult.met) {
176
+ return {
177
+ success: false,
178
+ error: waitResult.error || "Wait condition not met",
179
+ durationMs: performance.now() - startTime,
180
+ timestamp: Date.now(),
181
+ requestId: request.requestId,
182
+ waitDurationMs
183
+ };
184
+ }
185
+ }
186
+ const result = await this.performAction(element, request.action, request.params);
187
+ return {
188
+ success: true,
189
+ elementState: getElementState(element),
190
+ result,
191
+ durationMs: performance.now() - startTime,
192
+ timestamp: Date.now(),
193
+ requestId: request.requestId,
194
+ waitDurationMs
195
+ };
196
+ } catch (error) {
197
+ return {
198
+ success: false,
199
+ error: error instanceof Error ? error.message : String(error),
200
+ stack: error instanceof Error ? error.stack : void 0,
201
+ durationMs: performance.now() - startTime,
202
+ timestamp: Date.now(),
203
+ requestId: request.requestId,
204
+ waitDurationMs
205
+ };
206
+ }
207
+ }
208
+ /**
209
+ * Execute an action on a component
210
+ */
211
+ async executeComponentAction(componentId, request) {
212
+ const startTime = performance.now();
213
+ try {
214
+ const component = this.registry.getComponent(componentId);
215
+ if (!component) {
216
+ return {
217
+ success: false,
218
+ error: `Component not found: ${componentId}`,
219
+ durationMs: performance.now() - startTime,
220
+ timestamp: Date.now(),
221
+ requestId: request.requestId
222
+ };
223
+ }
224
+ const action = component.actions.find((a) => a.id === request.action);
225
+ if (!action) {
226
+ return {
227
+ success: false,
228
+ error: `Action not found: ${request.action}`,
229
+ durationMs: performance.now() - startTime,
230
+ timestamp: Date.now(),
231
+ requestId: request.requestId
232
+ };
233
+ }
234
+ const result = await action.handler(request.params);
235
+ return {
236
+ success: true,
237
+ result,
238
+ durationMs: performance.now() - startTime,
239
+ timestamp: Date.now(),
240
+ requestId: request.requestId
241
+ };
242
+ } catch (error) {
243
+ return {
244
+ success: false,
245
+ error: error instanceof Error ? error.message : String(error),
246
+ stack: error instanceof Error ? error.stack : void 0,
247
+ durationMs: performance.now() - startTime,
248
+ timestamp: Date.now(),
249
+ requestId: request.requestId
250
+ };
251
+ }
252
+ }
253
+ /**
254
+ * Wait for a condition on an element
255
+ */
256
+ async waitFor(elementId, options) {
257
+ const registered = this.registry.getElement(elementId);
258
+ let element = registered?.element ?? null;
259
+ if (!element) {
260
+ element = findElementByIdentifier(elementId);
261
+ }
262
+ if (!element) {
263
+ return {
264
+ met: false,
265
+ waitedMs: 0,
266
+ error: `Element not found: ${elementId}`
267
+ };
268
+ }
269
+ return this.waitForElement(element, options);
270
+ }
271
+ /**
272
+ * Find controllable elements
273
+ */
274
+ async find(options) {
275
+ const startTime = performance.now();
276
+ const elements = [];
277
+ let root = document.body;
278
+ if (options?.root) {
279
+ const rootEl = document.querySelector(options.root);
280
+ if (rootEl) root = rootEl;
281
+ }
282
+ const interactiveSelectors = [
283
+ "a[href]",
284
+ "button",
285
+ "input",
286
+ "select",
287
+ "textarea",
288
+ "[onclick]",
289
+ '[role="button"]',
290
+ '[role="link"]',
291
+ '[role="checkbox"]',
292
+ '[role="radio"]',
293
+ '[role="menuitem"]',
294
+ '[role="tab"]',
295
+ '[role="switch"]',
296
+ '[tabindex]:not([tabindex="-1"])',
297
+ '[contenteditable="true"]',
298
+ "[data-ui-id]",
299
+ "[data-testid]"
300
+ ];
301
+ const selector = options?.selector || interactiveSelectors.join(", ");
302
+ const foundElements = root.querySelectorAll(selector);
303
+ for (const el of foundElements) {
304
+ if (options?.limit && elements.length >= options.limit) break;
305
+ const state = getElementState(el);
306
+ if (!options?.includeHidden && !state.visible) continue;
307
+ if (options?.types) {
308
+ const type = this.inferElementType(el);
309
+ if (!options.types.includes(type)) continue;
310
+ }
311
+ const registered = this.registry.findByDOMElement(el);
312
+ elements.push({
313
+ id: registered?.id || this.getElementId(el),
314
+ type: registered?.type || this.inferElementType(el),
315
+ label: registered?.label || this.getElementLabel(el),
316
+ tagName: el.tagName.toLowerCase(),
317
+ role: el.getAttribute("role") || void 0,
318
+ accessibleName: this.getAccessibleName(el),
319
+ actions: registered?.actions || this.inferActions(el),
320
+ state,
321
+ registered: !!registered
322
+ });
323
+ }
324
+ return {
325
+ elements,
326
+ total: elements.length,
327
+ durationMs: performance.now() - startTime,
328
+ timestamp: Date.now()
329
+ };
330
+ }
331
+ /**
332
+ * Discover controllable elements
333
+ * @deprecated Use find() instead
334
+ */
335
+ async discover(options) {
336
+ return this.find(options);
337
+ }
338
+ /**
339
+ * Get control snapshot
340
+ */
341
+ async getSnapshot() {
342
+ const elements = this.registry.getAllElements();
343
+ const components = this.registry.getAllComponents();
344
+ const workflows = this.registry.getAllWorkflows();
345
+ return {
346
+ timestamp: Date.now(),
347
+ elements: elements.map((el) => ({
348
+ id: el.id,
349
+ type: el.type,
350
+ label: el.label,
351
+ actions: [...el.actions, ...el.customActions ? Object.keys(el.customActions) : []],
352
+ state: el.getState()
353
+ })),
354
+ components: components.map((comp) => ({
355
+ id: comp.id,
356
+ name: comp.name,
357
+ actions: comp.actions.map((a) => a.id)
358
+ })),
359
+ workflows: workflows.map((wf) => ({
360
+ id: wf.id,
361
+ name: wf.name,
362
+ stepCount: wf.steps.length
363
+ })),
364
+ activeRuns: []
365
+ // Workflow engine manages this
366
+ };
367
+ }
368
+ /**
369
+ * Wait for element conditions
370
+ */
371
+ async waitForElement(element, options) {
372
+ const opts = { ...DEFAULT_WAIT_OPTIONS, ...options };
373
+ const startTime = performance.now();
374
+ const deadline = startTime + opts.timeout;
375
+ while (Date.now() < deadline) {
376
+ const state = getElementState(element);
377
+ let allMet = true;
378
+ if (opts.visible && !state.visible) allMet = false;
379
+ if (opts.enabled && !state.enabled) allMet = false;
380
+ if (opts.focused && !state.focused) allMet = false;
381
+ if (opts.state) {
382
+ for (const [key, value] of Object.entries(opts.state)) {
383
+ if (state[key] !== value) {
384
+ allMet = false;
385
+ break;
386
+ }
387
+ }
388
+ }
389
+ if (allMet) {
390
+ return {
391
+ met: true,
392
+ waitedMs: performance.now() - startTime,
393
+ state
394
+ };
395
+ }
396
+ await sleep(opts.interval);
397
+ }
398
+ return {
399
+ met: false,
400
+ waitedMs: performance.now() - startTime,
401
+ state: getElementState(element),
402
+ error: `Timeout waiting for conditions after ${opts.timeout}ms`
403
+ };
404
+ }
405
+ /**
406
+ * Perform an action on an element
407
+ */
408
+ async performAction(element, action, params) {
409
+ switch (action) {
410
+ case "click":
411
+ return this.performClick(element, params);
412
+ case "doubleClick":
413
+ return this.performDoubleClick(element, params);
414
+ case "rightClick":
415
+ return this.performRightClick(element, params);
416
+ case "type":
417
+ return this.performType(element, params);
418
+ case "clear":
419
+ return this.performClear(element);
420
+ case "select":
421
+ return this.performSelect(element, params);
422
+ case "focus":
423
+ return this.performFocus(element);
424
+ case "blur":
425
+ return this.performBlur(element);
426
+ case "hover":
427
+ return this.performHover(element);
428
+ case "scroll":
429
+ return this.performScroll(element, params);
430
+ case "check":
431
+ return this.performCheck(element, true);
432
+ case "uncheck":
433
+ return this.performCheck(element, false);
434
+ case "toggle":
435
+ return this.performToggle(element);
436
+ default: {
437
+ const registered = this.registry.findByDOMElement(element);
438
+ if (registered?.customActions?.[action]) {
439
+ return registered.customActions[action].handler(params);
440
+ }
441
+ throw new Error(`Unknown action: ${action}`);
442
+ }
443
+ }
444
+ }
445
+ performClick(element, options) {
446
+ element.dispatchEvent(createMouseEvent("mousedown", element, options));
447
+ element.dispatchEvent(createMouseEvent("mouseup", element, options));
448
+ element.dispatchEvent(createMouseEvent("click", element, options));
449
+ }
450
+ performDoubleClick(element, options) {
451
+ this.performClick(element, options);
452
+ this.performClick(element, options);
453
+ element.dispatchEvent(createMouseEvent("dblclick", element, options));
454
+ }
455
+ performRightClick(element, options) {
456
+ const opts = { ...options, button: "right" };
457
+ element.dispatchEvent(createMouseEvent("mousedown", element, opts));
458
+ element.dispatchEvent(createMouseEvent("mouseup", element, opts));
459
+ element.dispatchEvent(createMouseEvent("contextmenu", element, opts));
460
+ }
461
+ async performType(element, options) {
462
+ if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) {
463
+ throw new Error("Type action requires an input or textarea element");
464
+ }
465
+ element.focus();
466
+ if (options?.clear) {
467
+ element.value = "";
468
+ element.dispatchEvent(new Event("input", { bubbles: true }));
469
+ }
470
+ const text = options?.text || "";
471
+ const delay = options?.delay || 0;
472
+ for (const char of text) {
473
+ element.value += char;
474
+ if (options?.triggerEvents !== false) {
475
+ element.dispatchEvent(new Event("input", { bubbles: true }));
476
+ }
477
+ if (delay > 0) {
478
+ await sleep(delay);
479
+ }
480
+ }
481
+ if (options?.triggerEvents !== false) {
482
+ element.dispatchEvent(new Event("change", { bubbles: true }));
483
+ }
484
+ }
485
+ performClear(element) {
486
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
487
+ element.value = "";
488
+ element.dispatchEvent(new Event("input", { bubbles: true }));
489
+ element.dispatchEvent(new Event("change", { bubbles: true }));
490
+ }
491
+ }
492
+ performSelect(element, options) {
493
+ if (!(element instanceof HTMLSelectElement)) {
494
+ throw new Error("Select action requires a select element");
495
+ }
496
+ const values = Array.isArray(options?.value) ? options.value : [options?.value];
497
+ if (!options?.additive) {
498
+ for (const option of element.options) {
499
+ option.selected = false;
500
+ }
501
+ }
502
+ for (const option of element.options) {
503
+ const matchValue = options?.byLabel ? option.text : option.value;
504
+ if (values.includes(matchValue)) {
505
+ option.selected = true;
506
+ }
507
+ }
508
+ element.dispatchEvent(new Event("change", { bubbles: true }));
509
+ }
510
+ performFocus(element) {
511
+ element.focus();
512
+ element.dispatchEvent(new FocusEvent("focus", { bubbles: true }));
513
+ }
514
+ performBlur(element) {
515
+ element.blur();
516
+ element.dispatchEvent(new FocusEvent("blur", { bubbles: true }));
517
+ }
518
+ performHover(element) {
519
+ element.dispatchEvent(createMouseEvent("mouseenter", element));
520
+ element.dispatchEvent(createMouseEvent("mouseover", element));
521
+ }
522
+ performScroll(element, options) {
523
+ if (options?.toElement) {
524
+ const target = document.querySelector(options.toElement);
525
+ if (target) {
526
+ target.scrollIntoView({ behavior: options.smooth ? "smooth" : "auto" });
527
+ return;
528
+ }
529
+ }
530
+ if (options?.position) {
531
+ element.scrollTo({
532
+ left: options.position.x,
533
+ top: options.position.y,
534
+ behavior: options.smooth ? "smooth" : "auto"
535
+ });
536
+ return;
537
+ }
538
+ const amount = options?.amount || 100;
539
+ const direction = options?.direction || "down";
540
+ switch (direction) {
541
+ case "up":
542
+ element.scrollBy({ top: -amount, behavior: options?.smooth ? "smooth" : "auto" });
543
+ break;
544
+ case "down":
545
+ element.scrollBy({ top: amount, behavior: options?.smooth ? "smooth" : "auto" });
546
+ break;
547
+ case "left":
548
+ element.scrollBy({ left: -amount, behavior: options?.smooth ? "smooth" : "auto" });
549
+ break;
550
+ case "right":
551
+ element.scrollBy({ left: amount, behavior: options?.smooth ? "smooth" : "auto" });
552
+ break;
553
+ }
554
+ }
555
+ performCheck(element, checked) {
556
+ if (element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio")) {
557
+ if (element.checked !== checked) {
558
+ element.checked = checked;
559
+ element.dispatchEvent(new Event("change", { bubbles: true }));
560
+ }
561
+ }
562
+ }
563
+ performToggle(element) {
564
+ if (element instanceof HTMLInputElement && element.type === "checkbox") {
565
+ element.checked = !element.checked;
566
+ element.dispatchEvent(new Event("change", { bubbles: true }));
567
+ }
568
+ }
569
+ getElementId(element) {
570
+ return element.getAttribute("data-ui-id") || element.getAttribute("data-testid") || element.id || `${element.tagName.toLowerCase()}-${Math.random().toString(36).substr(2, 8)}`;
571
+ }
572
+ getElementLabel(element) {
573
+ return element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent?.trim().substring(0, 50) || void 0;
574
+ }
575
+ getAccessibleName(element) {
576
+ const ariaLabel = element.getAttribute("aria-label");
577
+ if (ariaLabel) return ariaLabel;
578
+ const labelledBy = element.getAttribute("aria-labelledby");
579
+ if (labelledBy) {
580
+ const labels = labelledBy.split(" ").map((id) => document.getElementById(id)?.textContent?.trim()).filter(Boolean);
581
+ if (labels.length > 0) return labels.join(" ");
582
+ }
583
+ if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
584
+ if (element.id) {
585
+ const label = document.querySelector(`label[for="${element.id}"]`);
586
+ if (label) return label.textContent?.trim();
587
+ }
588
+ }
589
+ return element.getAttribute("title") || element.textContent?.trim().substring(0, 50) || void 0;
590
+ }
591
+ inferElementType(element) {
592
+ const tagName = element.tagName.toLowerCase();
593
+ const role = element.getAttribute("role");
594
+ if (role) {
595
+ switch (role) {
596
+ case "button":
597
+ return "button";
598
+ case "textbox":
599
+ return "input";
600
+ case "checkbox":
601
+ return "checkbox";
602
+ case "radio":
603
+ return "radio";
604
+ case "link":
605
+ return "link";
606
+ case "listbox":
607
+ case "combobox":
608
+ return "select";
609
+ case "menu":
610
+ return "menu";
611
+ case "menuitem":
612
+ return "menuitem";
613
+ case "tab":
614
+ return "tab";
615
+ case "dialog":
616
+ return "dialog";
617
+ }
618
+ }
619
+ switch (tagName) {
620
+ case "button":
621
+ return "button";
622
+ case "input": {
623
+ const type = element.type;
624
+ if (type === "checkbox") return "checkbox";
625
+ if (type === "radio") return "radio";
626
+ if (type === "submit" || type === "button") return "button";
627
+ return "input";
628
+ }
629
+ case "textarea":
630
+ return "textarea";
631
+ case "select":
632
+ return "select";
633
+ case "a":
634
+ return "link";
635
+ case "form":
636
+ return "form";
637
+ default:
638
+ return "custom";
639
+ }
640
+ }
641
+ inferActions(element) {
642
+ const type = this.inferElementType(element);
643
+ const baseActions = ["focus", "blur", "hover"];
644
+ switch (type) {
645
+ case "button":
646
+ return [...baseActions, "click", "doubleClick", "rightClick"];
647
+ case "input":
648
+ return [...baseActions, "click", "type", "clear"];
649
+ case "textarea":
650
+ return [...baseActions, "click", "type", "clear"];
651
+ case "select":
652
+ return [...baseActions, "click", "select"];
653
+ case "checkbox":
654
+ return [...baseActions, "click", "check", "uncheck", "toggle"];
655
+ case "radio":
656
+ return [...baseActions, "click", "check"];
657
+ case "link":
658
+ return [...baseActions, "click"];
659
+ default:
660
+ return [...baseActions, "click"];
661
+ }
662
+ }
663
+ };
664
+ function createActionExecutor(registry) {
665
+ return new DefaultActionExecutor(registry);
666
+ }
667
+
668
+ // src/control/workflow-engine.ts
669
+ function generateRunId() {
670
+ return `run-${Date.now()}-${Math.random().toString(36).substr(2, 8)}`;
671
+ }
672
+ var DefaultWorkflowEngine = class {
673
+ constructor(registry, executor) {
674
+ this.registry = registry;
675
+ this.executor = executor;
676
+ this.activeRuns = /* @__PURE__ */ new Map();
677
+ }
678
+ /**
679
+ * Run a workflow
680
+ */
681
+ async run(workflowId, request) {
682
+ const workflow = this.registry.getWorkflow(workflowId);
683
+ if (!workflow) {
684
+ return {
685
+ workflowId,
686
+ runId: generateRunId(),
687
+ status: "failed",
688
+ steps: [],
689
+ totalSteps: 0,
690
+ success: false,
691
+ error: `Workflow not found: ${workflowId}`,
692
+ startedAt: Date.now(),
693
+ completedAt: Date.now(),
694
+ durationMs: 0
695
+ };
696
+ }
697
+ const runId = generateRunId();
698
+ const state = {
699
+ workflowId,
700
+ runId,
701
+ workflow,
702
+ request,
703
+ status: "running",
704
+ steps: [],
705
+ currentStep: 0,
706
+ startedAt: Date.now()
707
+ };
708
+ this.activeRuns.set(runId, state);
709
+ try {
710
+ await this.executeWorkflow(state);
711
+ } catch (error) {
712
+ state.status = "failed";
713
+ state.error = error instanceof Error ? error.message : String(error);
714
+ }
715
+ state.completedAt = Date.now();
716
+ state.durationMs = state.completedAt - state.startedAt;
717
+ state.success = state.status === "completed" && state.steps.every((s) => s.success);
718
+ setTimeout(() => {
719
+ this.activeRuns.delete(runId);
720
+ }, 6e4);
721
+ return this.buildResponse(state);
722
+ }
723
+ /**
724
+ * Get workflow run status
725
+ */
726
+ async getRunStatus(runId) {
727
+ const state = this.activeRuns.get(runId);
728
+ if (!state) return null;
729
+ return this.buildResponse(state);
730
+ }
731
+ /**
732
+ * Cancel a running workflow
733
+ */
734
+ async cancel(runId) {
735
+ const state = this.activeRuns.get(runId);
736
+ if (!state || state.status !== "running") return false;
737
+ state.status = "cancelled";
738
+ state.completedAt = Date.now();
739
+ state.durationMs = state.completedAt - state.startedAt;
740
+ state.error = "Workflow cancelled by user";
741
+ return true;
742
+ }
743
+ /**
744
+ * List active runs
745
+ */
746
+ async listActiveRuns() {
747
+ return Array.from(this.activeRuns.values()).filter((state) => state.status === "running").map((state) => this.buildResponse(state));
748
+ }
749
+ /**
750
+ * Execute a workflow
751
+ */
752
+ async executeWorkflow(state) {
753
+ const { workflow, request } = state;
754
+ const params = { ...workflow.defaultParams, ...request?.params };
755
+ let startIndex = 0;
756
+ if (request?.startStep) {
757
+ const idx = workflow.steps.findIndex((s) => s.id === request.startStep);
758
+ if (idx >= 0) startIndex = idx;
759
+ }
760
+ let stopIndex = workflow.steps.length;
761
+ if (request?.stopStep) {
762
+ const idx = workflow.steps.findIndex((s) => s.id === request.stopStep);
763
+ if (idx >= 0) stopIndex = idx + 1;
764
+ }
765
+ for (let i = startIndex; i < stopIndex; i++) {
766
+ if (state.status === "cancelled") break;
767
+ state.currentStep = i;
768
+ const step = workflow.steps[i];
769
+ const stepResult = await this.executeStep(step, params, request?.stepTimeout);
770
+ state.steps.push(stepResult);
771
+ if (!stepResult.success) {
772
+ state.status = "failed";
773
+ state.error = stepResult.error;
774
+ return;
775
+ }
776
+ }
777
+ state.status = "completed";
778
+ }
779
+ /**
780
+ * Execute a single step
781
+ */
782
+ async executeStep(step, params, timeout) {
783
+ const startTime = performance.now();
784
+ try {
785
+ const timeoutPromise = timeout ? new Promise(
786
+ (_, reject) => setTimeout(() => reject(new Error("Step timeout")), timeout)
787
+ ) : null;
788
+ const executePromise = this.executeStepInternal(step, params);
789
+ const result = timeoutPromise ? await Promise.race([executePromise, timeoutPromise]) : await executePromise;
790
+ return {
791
+ stepId: step.id,
792
+ stepType: step.type,
793
+ success: true,
794
+ result,
795
+ durationMs: performance.now() - startTime,
796
+ timestamp: Date.now()
797
+ };
798
+ } catch (error) {
799
+ return {
800
+ stepId: step.id,
801
+ stepType: step.type,
802
+ success: false,
803
+ error: error instanceof Error ? error.message : String(error),
804
+ durationMs: performance.now() - startTime,
805
+ timestamp: Date.now()
806
+ };
807
+ }
808
+ }
809
+ /**
810
+ * Execute step internal logic
811
+ */
812
+ async executeStepInternal(step, params) {
813
+ const resolvedParams = this.interpolateParams(step.params || {}, params);
814
+ switch (step.type) {
815
+ case "element-action":
816
+ if (!step.target || !step.action) {
817
+ throw new Error("Element action requires target and action");
818
+ }
819
+ return this.executor.executeAction(step.target, {
820
+ action: step.action,
821
+ params: resolvedParams,
822
+ waitOptions: step.waitOptions
823
+ });
824
+ case "component-action":
825
+ if (!step.target || !step.action) {
826
+ throw new Error("Component action requires target and action");
827
+ }
828
+ return this.executor.executeComponentAction(step.target, {
829
+ action: step.action,
830
+ params: resolvedParams
831
+ });
832
+ case "wait": {
833
+ if (!step.target) {
834
+ throw new Error("Wait step requires target");
835
+ }
836
+ const waitResult = await this.executor.waitFor(step.target, step.waitOptions || {});
837
+ if (!waitResult.met) {
838
+ throw new Error(waitResult.error || "Wait condition not met");
839
+ }
840
+ return waitResult;
841
+ }
842
+ case "assert":
843
+ if (!step.target || !step.expectedState) {
844
+ throw new Error("Assert step requires target and expectedState");
845
+ }
846
+ return this.performAssertion(step.target, step.expectedState);
847
+ case "custom":
848
+ if (!step.handler) {
849
+ throw new Error("Custom step requires handler");
850
+ }
851
+ return step.handler();
852
+ default:
853
+ throw new Error(`Unknown step type: ${step.type}`);
854
+ }
855
+ }
856
+ /**
857
+ * Perform state assertion
858
+ */
859
+ async performAssertion(target, expectedState) {
860
+ const snapshot = await this.executor.getSnapshot();
861
+ const element = snapshot.elements.find((e) => e.id === target);
862
+ if (!element) {
863
+ throw new Error(`Element not found for assertion: ${target}`);
864
+ }
865
+ const differences = [];
866
+ for (const [key, expected] of Object.entries(expectedState)) {
867
+ const actual = element.state[key];
868
+ if (actual !== expected) {
869
+ differences.push(`${key}: expected ${expected}, got ${actual}`);
870
+ }
871
+ }
872
+ if (differences.length > 0) {
873
+ throw new Error(`Assertion failed:
874
+ ${differences.join("\n")}`);
875
+ }
876
+ return { passed: true, differences };
877
+ }
878
+ /**
879
+ * Interpolate parameters with {{param}} syntax
880
+ */
881
+ interpolateParams(stepParams, workflowParams) {
882
+ const result = {};
883
+ for (const [key, value] of Object.entries(stepParams)) {
884
+ if (typeof value === "string") {
885
+ result[key] = value.replace(/\{\{(\w+)\}\}/g, (_, name) => {
886
+ return String(workflowParams[name] ?? "");
887
+ });
888
+ } else {
889
+ result[key] = value;
890
+ }
891
+ }
892
+ return result;
893
+ }
894
+ /**
895
+ * Build response from state
896
+ */
897
+ buildResponse(state) {
898
+ return {
899
+ workflowId: state.workflowId,
900
+ runId: state.runId,
901
+ status: state.status,
902
+ steps: [...state.steps],
903
+ currentStep: state.currentStep,
904
+ totalSteps: state.workflow.steps.length,
905
+ success: state.success,
906
+ error: state.error,
907
+ startedAt: state.startedAt,
908
+ completedAt: state.completedAt,
909
+ durationMs: state.durationMs
910
+ };
911
+ }
912
+ };
913
+ function createWorkflowEngine(registry, executor) {
914
+ return new DefaultWorkflowEngine(registry, executor);
915
+ }
916
+
917
+ export { DefaultActionExecutor, DefaultWorkflowEngine, createActionExecutor, createWorkflowEngine };
918
+ //# sourceMappingURL=index.mjs.map
919
+ //# sourceMappingURL=index.mjs.map