@jay-framework/stack-client-runtime 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.cjs +102 -19
- package/dist/index.d.ts +45 -2
- package/dist/index.js +104 -21
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -79,6 +79,24 @@ function mergeArraysByTrackBy$1(baseArray, overlayArray, trackByField, trackByMa
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
+
const HEADLESS_INSTANCES = runtime.createJayContext();
|
|
83
|
+
function makeSignals$1(obj) {
|
|
84
|
+
return Object.keys(obj).reduce((signals, key) => {
|
|
85
|
+
signals[key] = component.createSignal(obj[key]);
|
|
86
|
+
return signals;
|
|
87
|
+
}, {});
|
|
88
|
+
}
|
|
89
|
+
function makeHeadlessInstanceComponent(preRender, interactiveConstructor, coordinateKey, pluginContexts = []) {
|
|
90
|
+
const wrappedConstructor = (props, refs, ...pluginResolvedContexts) => {
|
|
91
|
+
const instanceData = runtime.useContext(HEADLESS_INSTANCES);
|
|
92
|
+
const resolvedKey = typeof coordinateKey === "function" ? coordinateKey(runtime.currentConstructionContext()?.dataIds ?? []) : coordinateKey;
|
|
93
|
+
const fastVS = instanceData?.viewStates?.[resolvedKey];
|
|
94
|
+
const cf = instanceData?.carryForwards?.[resolvedKey] || {};
|
|
95
|
+
const signalVS = fastVS ? makeSignals$1(fastVS) : void 0;
|
|
96
|
+
return interactiveConstructor(props, refs, signalVS, cf, ...pluginResolvedContexts);
|
|
97
|
+
};
|
|
98
|
+
return component.makeJayComponent(preRender, wrappedConstructor, ...pluginContexts);
|
|
99
|
+
}
|
|
82
100
|
function makeSignals(obj) {
|
|
83
101
|
return Object.keys(obj).reduce((signals, key) => {
|
|
84
102
|
signals[key] = component.createSignal(obj[key]);
|
|
@@ -88,7 +106,21 @@ function makeSignals(obj) {
|
|
|
88
106
|
function makeCompositeJayComponent(preRender, defaultViewState, fastCarryForward, parts, trackByMap = {}) {
|
|
89
107
|
const interactiveParts = parts.filter((part) => part.comp !== void 0);
|
|
90
108
|
const hasFastRendering = defaultViewState !== null && defaultViewState !== void 0;
|
|
109
|
+
const headlessInstanceViewStates = defaultViewState?.__headlessInstances;
|
|
110
|
+
const headlessInstanceCarryForwards = fastCarryForward?.__headlessInstances;
|
|
111
|
+
if (headlessInstanceViewStates)
|
|
112
|
+
delete defaultViewState.__headlessInstances;
|
|
113
|
+
if (headlessInstanceCarryForwards)
|
|
114
|
+
delete fastCarryForward.__headlessInstances;
|
|
91
115
|
const comp = (props, refs, ...contexts) => {
|
|
116
|
+
if (headlessInstanceViewStates || headlessInstanceCarryForwards) {
|
|
117
|
+
const componentContext = runtime.useContext(component.COMPONENT_CONTEXT);
|
|
118
|
+
const instancesData = {
|
|
119
|
+
viewStates: headlessInstanceViewStates || {},
|
|
120
|
+
carryForwards: headlessInstanceCarryForwards || {}
|
|
121
|
+
};
|
|
122
|
+
componentContext.provideContexts.push([HEADLESS_INSTANCES, instancesData]);
|
|
123
|
+
}
|
|
92
124
|
const instances = interactiveParts.map(
|
|
93
125
|
(part) => {
|
|
94
126
|
const partRefs = part.key ? refs[part.key] : refs;
|
|
@@ -326,14 +358,13 @@ function collectInteractionsRecursive(refs, interactions) {
|
|
|
326
358
|
continue;
|
|
327
359
|
if (refImpl.elements && refImpl.elements instanceof Set) {
|
|
328
360
|
for (const elem of refImpl.elements) {
|
|
329
|
-
if (elem.element) {
|
|
361
|
+
if (elem.element && !isDisabled(elem.element)) {
|
|
330
362
|
interactions.push({
|
|
331
363
|
refName,
|
|
332
364
|
coordinate: elem.coordinate || [refName],
|
|
333
365
|
element: elem.element,
|
|
334
|
-
elementType: getElementType(elem.element),
|
|
335
366
|
supportedEvents: getSupportedEvents(elem.element),
|
|
336
|
-
|
|
367
|
+
description: elem.description
|
|
337
368
|
});
|
|
338
369
|
}
|
|
339
370
|
}
|
|
@@ -352,8 +383,12 @@ function isNestedRefsObject(obj) {
|
|
|
352
383
|
return false;
|
|
353
384
|
return true;
|
|
354
385
|
}
|
|
355
|
-
function
|
|
356
|
-
|
|
386
|
+
function isDisabled(element) {
|
|
387
|
+
if ("disabled" in element && element.disabled) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
const fieldset = element.closest?.("fieldset:disabled");
|
|
391
|
+
return !!fieldset;
|
|
357
392
|
}
|
|
358
393
|
function getSupportedEvents(element) {
|
|
359
394
|
const base = ["click", "focus", "blur"];
|
|
@@ -377,11 +412,45 @@ function getSupportedEvents(element) {
|
|
|
377
412
|
}
|
|
378
413
|
return base;
|
|
379
414
|
}
|
|
415
|
+
function groupInteractions(raw) {
|
|
416
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
417
|
+
for (const item of raw) {
|
|
418
|
+
let group = byRef.get(item.refName);
|
|
419
|
+
if (!group) {
|
|
420
|
+
group = [];
|
|
421
|
+
byRef.set(item.refName, group);
|
|
422
|
+
}
|
|
423
|
+
group.push(item);
|
|
424
|
+
}
|
|
425
|
+
return Array.from(byRef.entries()).map(([refName, items]) => ({
|
|
426
|
+
refName,
|
|
427
|
+
description: items[0].description,
|
|
428
|
+
items: items.map(toInstance)
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
function toInstance(raw) {
|
|
432
|
+
return {
|
|
433
|
+
coordinate: raw.coordinate,
|
|
434
|
+
element: raw.element,
|
|
435
|
+
events: relevantEvents(raw)
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function relevantEvents(raw) {
|
|
439
|
+
const el = raw.element;
|
|
440
|
+
if (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement) {
|
|
441
|
+
return ["click"];
|
|
442
|
+
}
|
|
443
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
444
|
+
return raw.supportedEvents.filter((e) => e === "input" || e === "change");
|
|
445
|
+
}
|
|
446
|
+
return raw.supportedEvents;
|
|
447
|
+
}
|
|
380
448
|
const VIEW_STATE_CHANGE = "viewStateChange";
|
|
381
449
|
class AutomationAgent {
|
|
382
450
|
constructor(component2, options) {
|
|
383
451
|
__publicField2(this, "stateListeners", /* @__PURE__ */ new Set());
|
|
384
|
-
__publicField2(this, "
|
|
452
|
+
__publicField2(this, "cachedRaw", null);
|
|
453
|
+
__publicField2(this, "cachedGrouped", null);
|
|
385
454
|
__publicField2(this, "viewStateHandler", null);
|
|
386
455
|
__publicField2(this, "mergedViewState");
|
|
387
456
|
__publicField2(this, "initialSlowViewState");
|
|
@@ -400,7 +469,8 @@ class AutomationAgent {
|
|
|
400
469
|
}
|
|
401
470
|
subscribeToUpdates() {
|
|
402
471
|
this.viewStateHandler = () => {
|
|
403
|
-
this.
|
|
472
|
+
this.cachedRaw = null;
|
|
473
|
+
this.cachedGrouped = null;
|
|
404
474
|
if (this.initialSlowViewState && this.trackByMap) {
|
|
405
475
|
this.mergedViewState = deepMergeViewStates(
|
|
406
476
|
this.initialSlowViewState,
|
|
@@ -418,33 +488,43 @@ class AutomationAgent {
|
|
|
418
488
|
const state = this.getPageState();
|
|
419
489
|
this.stateListeners.forEach((callback) => callback(state));
|
|
420
490
|
}
|
|
421
|
-
|
|
422
|
-
if (!this.
|
|
423
|
-
|
|
491
|
+
getGrouped() {
|
|
492
|
+
if (!this.cachedGrouped) {
|
|
493
|
+
if (!this.cachedRaw) {
|
|
494
|
+
this.cachedRaw = collectInteractions(this.component.element?.refs);
|
|
495
|
+
}
|
|
496
|
+
this.cachedGrouped = groupInteractions(this.cachedRaw);
|
|
424
497
|
}
|
|
498
|
+
return this.cachedGrouped;
|
|
499
|
+
}
|
|
500
|
+
getPageState() {
|
|
425
501
|
return {
|
|
426
502
|
// Use merged state if available (slow+fast), otherwise component's viewState
|
|
427
503
|
viewState: this.mergedViewState || this.component.viewState,
|
|
428
|
-
interactions: this.
|
|
504
|
+
interactions: this.getGrouped(),
|
|
429
505
|
customEvents: this.getCustomEvents()
|
|
430
506
|
};
|
|
431
507
|
}
|
|
432
508
|
triggerEvent(eventType, coordinate, eventData) {
|
|
433
|
-
const
|
|
434
|
-
if (!
|
|
509
|
+
const instance = this.getInteraction(coordinate);
|
|
510
|
+
if (!instance) {
|
|
435
511
|
throw new Error(`No element found at coordinate: ${coordinate.join("/")}`);
|
|
436
512
|
}
|
|
437
513
|
const event = new Event(eventType, { bubbles: true });
|
|
438
514
|
if (eventData) {
|
|
439
515
|
Object.assign(event, eventData);
|
|
440
516
|
}
|
|
441
|
-
|
|
517
|
+
instance.element.dispatchEvent(event);
|
|
442
518
|
}
|
|
443
519
|
getInteraction(coordinate) {
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
520
|
+
for (const group of this.getGrouped()) {
|
|
521
|
+
for (const instance of group.items) {
|
|
522
|
+
if (instance.coordinate.length === coordinate.length && instance.coordinate.every((c, idx) => c === coordinate[idx])) {
|
|
523
|
+
return instance;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return void 0;
|
|
448
528
|
}
|
|
449
529
|
onStateChange(callback) {
|
|
450
530
|
this.stateListeners.add(callback);
|
|
@@ -477,7 +557,8 @@ class AutomationAgent {
|
|
|
477
557
|
this.viewStateHandler = null;
|
|
478
558
|
}
|
|
479
559
|
this.stateListeners.clear();
|
|
480
|
-
this.
|
|
560
|
+
this.cachedRaw = null;
|
|
561
|
+
this.cachedGrouped = null;
|
|
481
562
|
}
|
|
482
563
|
}
|
|
483
564
|
function wrapWithAutomation(component2, options) {
|
|
@@ -487,7 +568,9 @@ function wrapWithAutomation(component2, options) {
|
|
|
487
568
|
const AUTOMATION_CONTEXT = runtime.createJayContext();
|
|
488
569
|
exports.AUTOMATION_CONTEXT = AUTOMATION_CONTEXT;
|
|
489
570
|
exports.ActionError = ActionError;
|
|
571
|
+
exports.HEADLESS_INSTANCES = HEADLESS_INSTANCES;
|
|
490
572
|
exports.createActionCaller = createActionCaller;
|
|
491
573
|
exports.makeCompositeJayComponent = makeCompositeJayComponent;
|
|
574
|
+
exports.makeHeadlessInstanceComponent = makeHeadlessInstanceComponent;
|
|
492
575
|
exports.setActionCallerOptions = setActionCallerOptions;
|
|
493
576
|
exports.wrapWithAutomation = wrapWithAutomation;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _jay_framework_component from '@jay-framework/component';
|
|
2
2
|
import { ComponentConstructor, ContextMarkers, JayComponentCore } from '@jay-framework/component';
|
|
3
|
-
import { JayElement, PreRenderElement } from '@jay-framework/runtime';
|
|
3
|
+
import { JayElement, PreRenderElement, ContextMarker } from '@jay-framework/runtime';
|
|
4
4
|
import { TrackByMap } from '@jay-framework/view-state-merge';
|
|
5
5
|
export { AUTOMATION_CONTEXT, AutomationAPI, AutomationWrappedComponent, Coordinate, Interaction, PageState, wrapWithAutomation } from '@jay-framework/runtime-automation';
|
|
6
6
|
|
|
@@ -17,6 +17,49 @@ interface CompositePart {
|
|
|
17
17
|
|
|
18
18
|
declare function makeCompositeJayComponent<PropsT extends object, ViewState extends object, Refs extends object, JayElementT extends JayElement<ViewState, Refs>, CompCore extends JayComponentCore<PropsT, ViewState>>(preRender: PreRenderElement<ViewState, Refs, JayElementT>, defaultViewState: ViewState, fastCarryForward: object, parts: Array<CompositePart>, trackByMap?: TrackByMap): (props: PropsT) => _jay_framework_component.ConcreteJayComponent<PropsT, ViewState, Refs, CompCore, JayElementT>;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Client-side context and helpers for headless component instances.
|
|
22
|
+
*
|
|
23
|
+
* Provides the mechanism to deliver server-produced fast ViewState and carryForward
|
|
24
|
+
* to headless component instances on the client.
|
|
25
|
+
*
|
|
26
|
+
* Flow:
|
|
27
|
+
* 1. makeCompositeJayComponent extracts __headlessInstances from ViewState/carryForward
|
|
28
|
+
* 2. Registers HEADLESS_INSTANCES context during component construction
|
|
29
|
+
* 3. makeHeadlessInstanceComponent creates instance components that resolve their data
|
|
30
|
+
* from this context by coordinate key
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Data structure for headless instance ViewStates and carryForwards.
|
|
35
|
+
* Keyed by coordinate path (e.g., "product-card:0", "p1/product-card:0").
|
|
36
|
+
*/
|
|
37
|
+
interface HeadlessInstancesData {
|
|
38
|
+
viewStates: Record<string, object>;
|
|
39
|
+
carryForwards: Record<string, object>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Context marker for headless instance data.
|
|
43
|
+
* Provided by makeCompositeJayComponent, consumed by makeHeadlessInstanceComponent.
|
|
44
|
+
*/
|
|
45
|
+
declare const HEADLESS_INSTANCES: ContextMarker<HeadlessInstancesData>;
|
|
46
|
+
/**
|
|
47
|
+
* Create a headless instance component that receives its fast ViewState from the
|
|
48
|
+
* HEADLESS_INSTANCES context, matched by coordinate key.
|
|
49
|
+
*
|
|
50
|
+
* This replaces makeJayComponent for headless instances. It wraps the plugin's
|
|
51
|
+
* interactive constructor to inject the instance's fast ViewState signals and
|
|
52
|
+
* carryForward before any plugin-defined context markers.
|
|
53
|
+
*
|
|
54
|
+
* @param preRender - The inline template's render function
|
|
55
|
+
* @param interactiveConstructor - The plugin's interactive constructor
|
|
56
|
+
* @param coordinateKey - Static coordinate key (e.g., "product-card:0") or a
|
|
57
|
+
* factory function for forEach instances that receives the current dataIds
|
|
58
|
+
* (accumulated trackBy values from ancestor forEach loops) and returns the key.
|
|
59
|
+
* @param pluginContexts - Additional context markers from the plugin (if any)
|
|
60
|
+
*/
|
|
61
|
+
declare function makeHeadlessInstanceComponent<PropsT extends object, ViewState extends object, Refs extends object, JayElementT extends JayElement<ViewState, Refs>, CompCore extends JayComponentCore<PropsT, ViewState>>(preRender: PreRenderElement<ViewState, Refs, JayElementT>, interactiveConstructor: ComponentConstructor<PropsT, Refs, ViewState, any, CompCore>, coordinateKey: string | ((dataIds: string[]) => string), pluginContexts?: ContextMarkers<any>): any;
|
|
62
|
+
|
|
20
63
|
/**
|
|
21
64
|
* Client-side action caller for Jay Stack.
|
|
22
65
|
*
|
|
@@ -82,4 +125,4 @@ declare function setActionCallerOptions(options: ActionCallerOptions): void;
|
|
|
82
125
|
*/
|
|
83
126
|
declare function createActionCaller<Input, Output>(actionName: string, method?: HttpMethod): (input: Input) => Promise<Output>;
|
|
84
127
|
|
|
85
|
-
export { type ActionCallerOptions, ActionError, type CompositePart, type HttpMethod, createActionCaller, makeCompositeJayComponent, setActionCallerOptions };
|
|
128
|
+
export { type ActionCallerOptions, ActionError, type CompositePart, HEADLESS_INSTANCES, type HeadlessInstancesData, type HttpMethod, createActionCaller, makeCompositeJayComponent, makeHeadlessInstanceComponent, setActionCallerOptions };
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@ var __publicField = (obj, key, value) => {
|
|
|
4
4
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
|
-
import { makeJayComponent,
|
|
8
|
-
import { createJayContext } from "@jay-framework/runtime";
|
|
7
|
+
import { makeJayComponent, createSignal, COMPONENT_CONTEXT, materializeViewState } from "@jay-framework/component";
|
|
8
|
+
import { createJayContext, useContext, currentConstructionContext } from "@jay-framework/runtime";
|
|
9
9
|
function deepMergeViewStates$1(base, overlay, trackByMap, path = "") {
|
|
10
10
|
if (!base && !overlay)
|
|
11
11
|
return {};
|
|
@@ -77,6 +77,24 @@ function mergeArraysByTrackBy$1(baseArray, overlayArray, trackByField, trackByMa
|
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
+
const HEADLESS_INSTANCES = createJayContext();
|
|
81
|
+
function makeSignals$1(obj) {
|
|
82
|
+
return Object.keys(obj).reduce((signals, key) => {
|
|
83
|
+
signals[key] = createSignal(obj[key]);
|
|
84
|
+
return signals;
|
|
85
|
+
}, {});
|
|
86
|
+
}
|
|
87
|
+
function makeHeadlessInstanceComponent(preRender, interactiveConstructor, coordinateKey, pluginContexts = []) {
|
|
88
|
+
const wrappedConstructor = (props, refs, ...pluginResolvedContexts) => {
|
|
89
|
+
const instanceData = useContext(HEADLESS_INSTANCES);
|
|
90
|
+
const resolvedKey = typeof coordinateKey === "function" ? coordinateKey(currentConstructionContext()?.dataIds ?? []) : coordinateKey;
|
|
91
|
+
const fastVS = instanceData?.viewStates?.[resolvedKey];
|
|
92
|
+
const cf = instanceData?.carryForwards?.[resolvedKey] || {};
|
|
93
|
+
const signalVS = fastVS ? makeSignals$1(fastVS) : void 0;
|
|
94
|
+
return interactiveConstructor(props, refs, signalVS, cf, ...pluginResolvedContexts);
|
|
95
|
+
};
|
|
96
|
+
return makeJayComponent(preRender, wrappedConstructor, ...pluginContexts);
|
|
97
|
+
}
|
|
80
98
|
function makeSignals(obj) {
|
|
81
99
|
return Object.keys(obj).reduce((signals, key) => {
|
|
82
100
|
signals[key] = createSignal(obj[key]);
|
|
@@ -86,7 +104,21 @@ function makeSignals(obj) {
|
|
|
86
104
|
function makeCompositeJayComponent(preRender, defaultViewState, fastCarryForward, parts, trackByMap = {}) {
|
|
87
105
|
const interactiveParts = parts.filter((part) => part.comp !== void 0);
|
|
88
106
|
const hasFastRendering = defaultViewState !== null && defaultViewState !== void 0;
|
|
107
|
+
const headlessInstanceViewStates = defaultViewState?.__headlessInstances;
|
|
108
|
+
const headlessInstanceCarryForwards = fastCarryForward?.__headlessInstances;
|
|
109
|
+
if (headlessInstanceViewStates)
|
|
110
|
+
delete defaultViewState.__headlessInstances;
|
|
111
|
+
if (headlessInstanceCarryForwards)
|
|
112
|
+
delete fastCarryForward.__headlessInstances;
|
|
89
113
|
const comp = (props, refs, ...contexts) => {
|
|
114
|
+
if (headlessInstanceViewStates || headlessInstanceCarryForwards) {
|
|
115
|
+
const componentContext = useContext(COMPONENT_CONTEXT);
|
|
116
|
+
const instancesData = {
|
|
117
|
+
viewStates: headlessInstanceViewStates || {},
|
|
118
|
+
carryForwards: headlessInstanceCarryForwards || {}
|
|
119
|
+
};
|
|
120
|
+
componentContext.provideContexts.push([HEADLESS_INSTANCES, instancesData]);
|
|
121
|
+
}
|
|
90
122
|
const instances = interactiveParts.map(
|
|
91
123
|
(part) => {
|
|
92
124
|
const partRefs = part.key ? refs[part.key] : refs;
|
|
@@ -324,14 +356,13 @@ function collectInteractionsRecursive(refs, interactions) {
|
|
|
324
356
|
continue;
|
|
325
357
|
if (refImpl.elements && refImpl.elements instanceof Set) {
|
|
326
358
|
for (const elem of refImpl.elements) {
|
|
327
|
-
if (elem.element) {
|
|
359
|
+
if (elem.element && !isDisabled(elem.element)) {
|
|
328
360
|
interactions.push({
|
|
329
361
|
refName,
|
|
330
362
|
coordinate: elem.coordinate || [refName],
|
|
331
363
|
element: elem.element,
|
|
332
|
-
elementType: getElementType(elem.element),
|
|
333
364
|
supportedEvents: getSupportedEvents(elem.element),
|
|
334
|
-
|
|
365
|
+
description: elem.description
|
|
335
366
|
});
|
|
336
367
|
}
|
|
337
368
|
}
|
|
@@ -350,8 +381,12 @@ function isNestedRefsObject(obj) {
|
|
|
350
381
|
return false;
|
|
351
382
|
return true;
|
|
352
383
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
384
|
+
function isDisabled(element) {
|
|
385
|
+
if ("disabled" in element && element.disabled) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
const fieldset = element.closest?.("fieldset:disabled");
|
|
389
|
+
return !!fieldset;
|
|
355
390
|
}
|
|
356
391
|
function getSupportedEvents(element) {
|
|
357
392
|
const base = ["click", "focus", "blur"];
|
|
@@ -375,11 +410,45 @@ function getSupportedEvents(element) {
|
|
|
375
410
|
}
|
|
376
411
|
return base;
|
|
377
412
|
}
|
|
413
|
+
function groupInteractions(raw) {
|
|
414
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
415
|
+
for (const item of raw) {
|
|
416
|
+
let group = byRef.get(item.refName);
|
|
417
|
+
if (!group) {
|
|
418
|
+
group = [];
|
|
419
|
+
byRef.set(item.refName, group);
|
|
420
|
+
}
|
|
421
|
+
group.push(item);
|
|
422
|
+
}
|
|
423
|
+
return Array.from(byRef.entries()).map(([refName, items]) => ({
|
|
424
|
+
refName,
|
|
425
|
+
description: items[0].description,
|
|
426
|
+
items: items.map(toInstance)
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
function toInstance(raw) {
|
|
430
|
+
return {
|
|
431
|
+
coordinate: raw.coordinate,
|
|
432
|
+
element: raw.element,
|
|
433
|
+
events: relevantEvents(raw)
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function relevantEvents(raw) {
|
|
437
|
+
const el = raw.element;
|
|
438
|
+
if (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement) {
|
|
439
|
+
return ["click"];
|
|
440
|
+
}
|
|
441
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
442
|
+
return raw.supportedEvents.filter((e) => e === "input" || e === "change");
|
|
443
|
+
}
|
|
444
|
+
return raw.supportedEvents;
|
|
445
|
+
}
|
|
378
446
|
const VIEW_STATE_CHANGE = "viewStateChange";
|
|
379
447
|
class AutomationAgent {
|
|
380
448
|
constructor(component, options) {
|
|
381
449
|
__publicField2(this, "stateListeners", /* @__PURE__ */ new Set());
|
|
382
|
-
__publicField2(this, "
|
|
450
|
+
__publicField2(this, "cachedRaw", null);
|
|
451
|
+
__publicField2(this, "cachedGrouped", null);
|
|
383
452
|
__publicField2(this, "viewStateHandler", null);
|
|
384
453
|
__publicField2(this, "mergedViewState");
|
|
385
454
|
__publicField2(this, "initialSlowViewState");
|
|
@@ -398,7 +467,8 @@ class AutomationAgent {
|
|
|
398
467
|
}
|
|
399
468
|
subscribeToUpdates() {
|
|
400
469
|
this.viewStateHandler = () => {
|
|
401
|
-
this.
|
|
470
|
+
this.cachedRaw = null;
|
|
471
|
+
this.cachedGrouped = null;
|
|
402
472
|
if (this.initialSlowViewState && this.trackByMap) {
|
|
403
473
|
this.mergedViewState = deepMergeViewStates(
|
|
404
474
|
this.initialSlowViewState,
|
|
@@ -416,33 +486,43 @@ class AutomationAgent {
|
|
|
416
486
|
const state = this.getPageState();
|
|
417
487
|
this.stateListeners.forEach((callback) => callback(state));
|
|
418
488
|
}
|
|
419
|
-
|
|
420
|
-
if (!this.
|
|
421
|
-
|
|
489
|
+
getGrouped() {
|
|
490
|
+
if (!this.cachedGrouped) {
|
|
491
|
+
if (!this.cachedRaw) {
|
|
492
|
+
this.cachedRaw = collectInteractions(this.component.element?.refs);
|
|
493
|
+
}
|
|
494
|
+
this.cachedGrouped = groupInteractions(this.cachedRaw);
|
|
422
495
|
}
|
|
496
|
+
return this.cachedGrouped;
|
|
497
|
+
}
|
|
498
|
+
getPageState() {
|
|
423
499
|
return {
|
|
424
500
|
// Use merged state if available (slow+fast), otherwise component's viewState
|
|
425
501
|
viewState: this.mergedViewState || this.component.viewState,
|
|
426
|
-
interactions: this.
|
|
502
|
+
interactions: this.getGrouped(),
|
|
427
503
|
customEvents: this.getCustomEvents()
|
|
428
504
|
};
|
|
429
505
|
}
|
|
430
506
|
triggerEvent(eventType, coordinate, eventData) {
|
|
431
|
-
const
|
|
432
|
-
if (!
|
|
507
|
+
const instance = this.getInteraction(coordinate);
|
|
508
|
+
if (!instance) {
|
|
433
509
|
throw new Error(`No element found at coordinate: ${coordinate.join("/")}`);
|
|
434
510
|
}
|
|
435
511
|
const event = new Event(eventType, { bubbles: true });
|
|
436
512
|
if (eventData) {
|
|
437
513
|
Object.assign(event, eventData);
|
|
438
514
|
}
|
|
439
|
-
|
|
515
|
+
instance.element.dispatchEvent(event);
|
|
440
516
|
}
|
|
441
517
|
getInteraction(coordinate) {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
518
|
+
for (const group of this.getGrouped()) {
|
|
519
|
+
for (const instance of group.items) {
|
|
520
|
+
if (instance.coordinate.length === coordinate.length && instance.coordinate.every((c, idx) => c === coordinate[idx])) {
|
|
521
|
+
return instance;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return void 0;
|
|
446
526
|
}
|
|
447
527
|
onStateChange(callback) {
|
|
448
528
|
this.stateListeners.add(callback);
|
|
@@ -475,7 +555,8 @@ class AutomationAgent {
|
|
|
475
555
|
this.viewStateHandler = null;
|
|
476
556
|
}
|
|
477
557
|
this.stateListeners.clear();
|
|
478
|
-
this.
|
|
558
|
+
this.cachedRaw = null;
|
|
559
|
+
this.cachedGrouped = null;
|
|
479
560
|
}
|
|
480
561
|
}
|
|
481
562
|
function wrapWithAutomation(component, options) {
|
|
@@ -486,8 +567,10 @@ const AUTOMATION_CONTEXT = createJayContext();
|
|
|
486
567
|
export {
|
|
487
568
|
AUTOMATION_CONTEXT,
|
|
488
569
|
ActionError,
|
|
570
|
+
HEADLESS_INSTANCES,
|
|
489
571
|
createActionCaller,
|
|
490
572
|
makeCompositeJayComponent,
|
|
573
|
+
makeHeadlessInstanceComponent,
|
|
491
574
|
setActionCallerOptions,
|
|
492
575
|
wrapWithAutomation
|
|
493
576
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/stack-client-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"test:watch": "vitest"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@jay-framework/component": "^0.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.
|
|
32
|
-
"@jay-framework/runtime": "^0.
|
|
33
|
-
"@jay-framework/runtime-automation": "^0.
|
|
34
|
-
"@jay-framework/view-state-merge": "^0.
|
|
30
|
+
"@jay-framework/component": "^0.13.0",
|
|
31
|
+
"@jay-framework/fullstack-component": "^0.13.0",
|
|
32
|
+
"@jay-framework/runtime": "^0.13.0",
|
|
33
|
+
"@jay-framework/runtime-automation": "^0.13.0",
|
|
34
|
+
"@jay-framework/view-state-merge": "^0.13.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@jay-framework/dev-environment": "^0.
|
|
38
|
-
"@jay-framework/jay-cli": "^0.
|
|
37
|
+
"@jay-framework/dev-environment": "^0.13.0",
|
|
38
|
+
"@jay-framework/jay-cli": "^0.13.0",
|
|
39
39
|
"@types/express": "^5.0.2",
|
|
40
40
|
"@types/node": "^22.15.21",
|
|
41
41
|
"nodemon": "^3.0.3",
|