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