@jay-framework/runtime-automation 0.11.0 → 0.13.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/index.d.ts +39 -18
- package/dist/index.js +69 -19
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -4,27 +4,43 @@ import { TrackByMap } from '@jay-framework/view-state-merge';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Coordinate path identifying an element.
|
|
7
|
+
* For simple elements: ['refName']
|
|
7
8
|
* For forEach items: ['trackByValue', 'refName']
|
|
8
|
-
* For nested: ['parentTrackBy', 'childTrackBy', 'refName']
|
|
9
|
+
* For nested forEach: ['parentTrackBy', 'childTrackBy', 'refName']
|
|
9
10
|
*/
|
|
10
11
|
type Coordinate = string[];
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* A single interactive element instance — has the DOM element and its coordinate.
|
|
14
|
+
*/
|
|
15
|
+
interface InteractionInstance {
|
|
16
|
+
/** Full coordinate path identifying this element */
|
|
17
|
+
coordinate: Coordinate;
|
|
18
|
+
/** The actual DOM element — can be used to read/set values or call click() */
|
|
19
|
+
element: HTMLElement;
|
|
20
|
+
/** Relevant events this element handles (e.g., ["click"] or ["input", "change"]) */
|
|
21
|
+
events: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Interactions grouped by refName.
|
|
25
|
+
* Each ref has one or more instances (multiple when inside a forEach).
|
|
13
26
|
*/
|
|
14
27
|
interface Interaction {
|
|
15
28
|
/** Ref name from jay-html */
|
|
16
29
|
refName: string;
|
|
17
|
-
/**
|
|
30
|
+
/** All instances of this ref (one per forEach item, or a single entry for non-forEach) */
|
|
31
|
+
items: InteractionInstance[];
|
|
32
|
+
/** Human-readable description (from contract if available) */
|
|
33
|
+
description?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Internal: raw per-element data from the interaction collector.
|
|
37
|
+
* Not part of the public API — consumed by the grouping function.
|
|
38
|
+
*/
|
|
39
|
+
interface CollectedInteraction {
|
|
40
|
+
refName: string;
|
|
18
41
|
coordinate: Coordinate;
|
|
19
|
-
/** The actual DOM element - can be used to read/set values or call click() */
|
|
20
42
|
element: HTMLElement;
|
|
21
|
-
/** HTML element type (e.g., "HTMLButtonElement") */
|
|
22
|
-
elementType: string;
|
|
23
|
-
/** Events this element can handle (e.g., ["click", "input"]) */
|
|
24
43
|
supportedEvents: string[];
|
|
25
|
-
/** For collection items: the item's ViewState */
|
|
26
|
-
itemContext?: object;
|
|
27
|
-
/** Human-readable description (from contract if available) */
|
|
28
44
|
description?: string;
|
|
29
45
|
}
|
|
30
46
|
/**
|
|
@@ -33,7 +49,7 @@ interface Interaction {
|
|
|
33
49
|
interface PageState {
|
|
34
50
|
/** Current ViewState of the component (includes headless component data under their keys) */
|
|
35
51
|
viewState: object;
|
|
36
|
-
/**
|
|
52
|
+
/** Available interactions grouped by ref name */
|
|
37
53
|
interactions: Interaction[];
|
|
38
54
|
/** Custom events the component can emit */
|
|
39
55
|
customEvents: Array<{
|
|
@@ -44,21 +60,21 @@ interface PageState {
|
|
|
44
60
|
* Automation API attached to wrapped components.
|
|
45
61
|
*/
|
|
46
62
|
interface AutomationAPI {
|
|
47
|
-
/** Get current page state and available interactions */
|
|
63
|
+
/** Get current page state and available interactions (grouped by ref) */
|
|
48
64
|
getPageState(): PageState;
|
|
49
65
|
/** Trigger an event on an element by coordinate */
|
|
50
66
|
triggerEvent(eventType: string, coordinate: Coordinate, eventData?: object): void;
|
|
51
|
-
/** Subscribe to ViewState changes
|
|
67
|
+
/** Subscribe to ViewState changes — called on every ViewState update */
|
|
52
68
|
onStateChange(callback: (state: PageState) => void): () => void;
|
|
53
|
-
/** Get a specific interaction by coordinate */
|
|
54
|
-
getInteraction(coordinate: Coordinate):
|
|
69
|
+
/** Get a specific interaction instance by coordinate (returns the DOM element) */
|
|
70
|
+
getInteraction(coordinate: Coordinate): InteractionInstance | undefined;
|
|
55
71
|
/** Get list of custom events the component emits */
|
|
56
72
|
getCustomEvents(): Array<{
|
|
57
73
|
name: string;
|
|
58
74
|
}>;
|
|
59
75
|
/** Subscribe to a custom component event (e.g., 'AddToCart') */
|
|
60
76
|
onComponentEvent(eventName: string, callback: (eventData: any) => void): () => void;
|
|
61
|
-
/** Cleanup
|
|
77
|
+
/** Cleanup — call when component is unmounted */
|
|
62
78
|
dispose(): void;
|
|
63
79
|
}
|
|
64
80
|
/** @deprecated Use Interaction instead */
|
|
@@ -132,6 +148,11 @@ declare const AUTOMATION_CONTEXT: _jay_framework_runtime.ContextMarker<Automatio
|
|
|
132
148
|
* Collects all interactive elements from a component's refs.
|
|
133
149
|
* Handles nested refs (e.g., headless components) by recursively traversing the refs tree.
|
|
134
150
|
*/
|
|
135
|
-
declare function collectInteractions(refs: any):
|
|
151
|
+
declare function collectInteractions(refs: any): CollectedInteraction[];
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Groups raw collected interactions by refName and filters events to relevant ones.
|
|
155
|
+
*/
|
|
156
|
+
declare function groupInteractions(raw: CollectedInteraction[]): Interaction[];
|
|
136
157
|
|
|
137
|
-
export { type AIAgentAPI, type AIInteraction, type AIPageState, AUTOMATION_CONTEXT, type AutomationAPI, type AutomationAgentOptions, type AutomationWrappedComponent, type Coordinate, type Interaction, type PageState, collectInteractions, wrapWithAutomation };
|
|
158
|
+
export { type AIAgentAPI, type AIInteraction, type AIPageState, AUTOMATION_CONTEXT, type AutomationAPI, type AutomationAgentOptions, type AutomationWrappedComponent, type Coordinate, type Interaction, type InteractionInstance, type PageState, collectInteractions, groupInteractions, wrapWithAutomation };
|
package/dist/index.js
CHANGED
|
@@ -91,14 +91,13 @@ function collectInteractionsRecursive(refs, interactions) {
|
|
|
91
91
|
continue;
|
|
92
92
|
if (refImpl.elements && refImpl.elements instanceof Set) {
|
|
93
93
|
for (const elem of refImpl.elements) {
|
|
94
|
-
if (elem.element) {
|
|
94
|
+
if (elem.element && !isDisabled(elem.element)) {
|
|
95
95
|
interactions.push({
|
|
96
96
|
refName,
|
|
97
97
|
coordinate: elem.coordinate || [refName],
|
|
98
98
|
element: elem.element,
|
|
99
|
-
elementType: getElementType(elem.element),
|
|
100
99
|
supportedEvents: getSupportedEvents(elem.element),
|
|
101
|
-
|
|
100
|
+
description: elem.description
|
|
102
101
|
});
|
|
103
102
|
}
|
|
104
103
|
}
|
|
@@ -117,8 +116,12 @@ function isNestedRefsObject(obj) {
|
|
|
117
116
|
return false;
|
|
118
117
|
return true;
|
|
119
118
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
119
|
+
function isDisabled(element) {
|
|
120
|
+
if ("disabled" in element && element.disabled) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const fieldset = element.closest?.("fieldset:disabled");
|
|
124
|
+
return !!fieldset;
|
|
122
125
|
}
|
|
123
126
|
function getSupportedEvents(element) {
|
|
124
127
|
const base = ["click", "focus", "blur"];
|
|
@@ -142,11 +145,45 @@ function getSupportedEvents(element) {
|
|
|
142
145
|
}
|
|
143
146
|
return base;
|
|
144
147
|
}
|
|
148
|
+
function groupInteractions(raw) {
|
|
149
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const item of raw) {
|
|
151
|
+
let group = byRef.get(item.refName);
|
|
152
|
+
if (!group) {
|
|
153
|
+
group = [];
|
|
154
|
+
byRef.set(item.refName, group);
|
|
155
|
+
}
|
|
156
|
+
group.push(item);
|
|
157
|
+
}
|
|
158
|
+
return Array.from(byRef.entries()).map(([refName, items]) => ({
|
|
159
|
+
refName,
|
|
160
|
+
description: items[0].description,
|
|
161
|
+
items: items.map(toInstance)
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
function toInstance(raw) {
|
|
165
|
+
return {
|
|
166
|
+
coordinate: raw.coordinate,
|
|
167
|
+
element: raw.element,
|
|
168
|
+
events: relevantEvents(raw)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function relevantEvents(raw) {
|
|
172
|
+
const el = raw.element;
|
|
173
|
+
if (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement) {
|
|
174
|
+
return ["click"];
|
|
175
|
+
}
|
|
176
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
177
|
+
return raw.supportedEvents.filter((e) => e === "input" || e === "change");
|
|
178
|
+
}
|
|
179
|
+
return raw.supportedEvents;
|
|
180
|
+
}
|
|
145
181
|
const VIEW_STATE_CHANGE = "viewStateChange";
|
|
146
182
|
class AutomationAgent {
|
|
147
183
|
constructor(component, options) {
|
|
148
184
|
__publicField(this, "stateListeners", /* @__PURE__ */ new Set());
|
|
149
|
-
__publicField(this, "
|
|
185
|
+
__publicField(this, "cachedRaw", null);
|
|
186
|
+
__publicField(this, "cachedGrouped", null);
|
|
150
187
|
__publicField(this, "viewStateHandler", null);
|
|
151
188
|
/**
|
|
152
189
|
* When slow rendering is used, this holds the merged slow+fast ViewState.
|
|
@@ -169,7 +206,8 @@ class AutomationAgent {
|
|
|
169
206
|
}
|
|
170
207
|
subscribeToUpdates() {
|
|
171
208
|
this.viewStateHandler = () => {
|
|
172
|
-
this.
|
|
209
|
+
this.cachedRaw = null;
|
|
210
|
+
this.cachedGrouped = null;
|
|
173
211
|
if (this.initialSlowViewState && this.trackByMap) {
|
|
174
212
|
this.mergedViewState = deepMergeViewStates(
|
|
175
213
|
this.initialSlowViewState,
|
|
@@ -187,33 +225,43 @@ class AutomationAgent {
|
|
|
187
225
|
const state = this.getPageState();
|
|
188
226
|
this.stateListeners.forEach((callback) => callback(state));
|
|
189
227
|
}
|
|
190
|
-
|
|
191
|
-
if (!this.
|
|
192
|
-
|
|
228
|
+
getGrouped() {
|
|
229
|
+
if (!this.cachedGrouped) {
|
|
230
|
+
if (!this.cachedRaw) {
|
|
231
|
+
this.cachedRaw = collectInteractions(this.component.element?.refs);
|
|
232
|
+
}
|
|
233
|
+
this.cachedGrouped = groupInteractions(this.cachedRaw);
|
|
193
234
|
}
|
|
235
|
+
return this.cachedGrouped;
|
|
236
|
+
}
|
|
237
|
+
getPageState() {
|
|
194
238
|
return {
|
|
195
239
|
// Use merged state if available (slow+fast), otherwise component's viewState
|
|
196
240
|
viewState: this.mergedViewState || this.component.viewState,
|
|
197
|
-
interactions: this.
|
|
241
|
+
interactions: this.getGrouped(),
|
|
198
242
|
customEvents: this.getCustomEvents()
|
|
199
243
|
};
|
|
200
244
|
}
|
|
201
245
|
triggerEvent(eventType, coordinate, eventData) {
|
|
202
|
-
const
|
|
203
|
-
if (!
|
|
246
|
+
const instance = this.getInteraction(coordinate);
|
|
247
|
+
if (!instance) {
|
|
204
248
|
throw new Error(`No element found at coordinate: ${coordinate.join("/")}`);
|
|
205
249
|
}
|
|
206
250
|
const event = new Event(eventType, { bubbles: true });
|
|
207
251
|
if (eventData) {
|
|
208
252
|
Object.assign(event, eventData);
|
|
209
253
|
}
|
|
210
|
-
|
|
254
|
+
instance.element.dispatchEvent(event);
|
|
211
255
|
}
|
|
212
256
|
getInteraction(coordinate) {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
257
|
+
for (const group of this.getGrouped()) {
|
|
258
|
+
for (const instance of group.items) {
|
|
259
|
+
if (instance.coordinate.length === coordinate.length && instance.coordinate.every((c, idx) => c === coordinate[idx])) {
|
|
260
|
+
return instance;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return void 0;
|
|
217
265
|
}
|
|
218
266
|
onStateChange(callback) {
|
|
219
267
|
this.stateListeners.add(callback);
|
|
@@ -246,7 +294,8 @@ class AutomationAgent {
|
|
|
246
294
|
this.viewStateHandler = null;
|
|
247
295
|
}
|
|
248
296
|
this.stateListeners.clear();
|
|
249
|
-
this.
|
|
297
|
+
this.cachedRaw = null;
|
|
298
|
+
this.cachedGrouped = null;
|
|
250
299
|
}
|
|
251
300
|
}
|
|
252
301
|
function wrapWithAutomation(component, options) {
|
|
@@ -257,5 +306,6 @@ const AUTOMATION_CONTEXT = createJayContext();
|
|
|
257
306
|
export {
|
|
258
307
|
AUTOMATION_CONTEXT,
|
|
259
308
|
collectInteractions,
|
|
309
|
+
groupInteractions,
|
|
260
310
|
wrapWithAutomation
|
|
261
311
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/runtime-automation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Automation API for Jay components - enables programmatic state inspection and interaction triggering",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"test:watch": "vitest"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jay-framework/runtime": "^0.
|
|
25
|
-
"@jay-framework/view-state-merge": "^0.
|
|
24
|
+
"@jay-framework/runtime": "^0.13.0",
|
|
25
|
+
"@jay-framework/view-state-merge": "^0.13.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@jay-framework/component": "^0.
|
|
29
|
-
"@jay-framework/dev-environment": "^0.
|
|
28
|
+
"@jay-framework/component": "^0.13.0",
|
|
29
|
+
"@jay-framework/dev-environment": "^0.13.0",
|
|
30
30
|
"@types/node": "^20.11.5",
|
|
31
31
|
"rimraf": "^5.0.5",
|
|
32
32
|
"tsup": "^8.0.1",
|