@jay-framework/stack-client-runtime 0.12.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 CHANGED
@@ -89,8 +89,9 @@ function makeSignals$1(obj) {
89
89
  function makeHeadlessInstanceComponent(preRender, interactiveConstructor, coordinateKey, pluginContexts = []) {
90
90
  const wrappedConstructor = (props, refs, ...pluginResolvedContexts) => {
91
91
  const instanceData = runtime.useContext(HEADLESS_INSTANCES);
92
- const fastVS = instanceData?.viewStates?.[coordinateKey];
93
- const cf = instanceData?.carryForwards?.[coordinateKey] || {};
92
+ const resolvedKey = typeof coordinateKey === "function" ? coordinateKey(runtime.currentConstructionContext()?.dataIds ?? []) : coordinateKey;
93
+ const fastVS = instanceData?.viewStates?.[resolvedKey];
94
+ const cf = instanceData?.carryForwards?.[resolvedKey] || {};
94
95
  const signalVS = fastVS ? makeSignals$1(fastVS) : void 0;
95
96
  return interactiveConstructor(props, refs, signalVS, cf, ...pluginResolvedContexts);
96
97
  };
@@ -357,14 +358,13 @@ function collectInteractionsRecursive(refs, interactions) {
357
358
  continue;
358
359
  if (refImpl.elements && refImpl.elements instanceof Set) {
359
360
  for (const elem of refImpl.elements) {
360
- if (elem.element) {
361
+ if (elem.element && !isDisabled(elem.element)) {
361
362
  interactions.push({
362
363
  refName,
363
364
  coordinate: elem.coordinate || [refName],
364
365
  element: elem.element,
365
- elementType: getElementType(elem.element),
366
366
  supportedEvents: getSupportedEvents(elem.element),
367
- itemContext: elem.viewState
367
+ description: elem.description
368
368
  });
369
369
  }
370
370
  }
@@ -383,8 +383,12 @@ function isNestedRefsObject(obj) {
383
383
  return false;
384
384
  return true;
385
385
  }
386
- function getElementType(element) {
387
- return element.constructor.name;
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;
388
392
  }
389
393
  function getSupportedEvents(element) {
390
394
  const base = ["click", "focus", "blur"];
@@ -408,11 +412,45 @@ function getSupportedEvents(element) {
408
412
  }
409
413
  return base;
410
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
+ }
411
448
  const VIEW_STATE_CHANGE = "viewStateChange";
412
449
  class AutomationAgent {
413
450
  constructor(component2, options) {
414
451
  __publicField2(this, "stateListeners", /* @__PURE__ */ new Set());
415
- __publicField2(this, "cachedInteractions", null);
452
+ __publicField2(this, "cachedRaw", null);
453
+ __publicField2(this, "cachedGrouped", null);
416
454
  __publicField2(this, "viewStateHandler", null);
417
455
  __publicField2(this, "mergedViewState");
418
456
  __publicField2(this, "initialSlowViewState");
@@ -431,7 +469,8 @@ class AutomationAgent {
431
469
  }
432
470
  subscribeToUpdates() {
433
471
  this.viewStateHandler = () => {
434
- this.cachedInteractions = null;
472
+ this.cachedRaw = null;
473
+ this.cachedGrouped = null;
435
474
  if (this.initialSlowViewState && this.trackByMap) {
436
475
  this.mergedViewState = deepMergeViewStates(
437
476
  this.initialSlowViewState,
@@ -449,33 +488,43 @@ class AutomationAgent {
449
488
  const state = this.getPageState();
450
489
  this.stateListeners.forEach((callback) => callback(state));
451
490
  }
452
- getPageState() {
453
- if (!this.cachedInteractions) {
454
- this.cachedInteractions = collectInteractions(this.component.element?.refs);
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);
455
497
  }
498
+ return this.cachedGrouped;
499
+ }
500
+ getPageState() {
456
501
  return {
457
502
  // Use merged state if available (slow+fast), otherwise component's viewState
458
503
  viewState: this.mergedViewState || this.component.viewState,
459
- interactions: this.cachedInteractions,
504
+ interactions: this.getGrouped(),
460
505
  customEvents: this.getCustomEvents()
461
506
  };
462
507
  }
463
508
  triggerEvent(eventType, coordinate, eventData) {
464
- const interaction = this.getInteraction(coordinate);
465
- if (!interaction) {
509
+ const instance = this.getInteraction(coordinate);
510
+ if (!instance) {
466
511
  throw new Error(`No element found at coordinate: ${coordinate.join("/")}`);
467
512
  }
468
513
  const event = new Event(eventType, { bubbles: true });
469
514
  if (eventData) {
470
515
  Object.assign(event, eventData);
471
516
  }
472
- interaction.element.dispatchEvent(event);
517
+ instance.element.dispatchEvent(event);
473
518
  }
474
519
  getInteraction(coordinate) {
475
- const state = this.getPageState();
476
- return state.interactions.find(
477
- (i) => i.coordinate.length === coordinate.length && i.coordinate.every((c, idx) => c === coordinate[idx])
478
- );
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;
479
528
  }
480
529
  onStateChange(callback) {
481
530
  this.stateListeners.add(callback);
@@ -508,7 +557,8 @@ class AutomationAgent {
508
557
  this.viewStateHandler = null;
509
558
  }
510
559
  this.stateListeners.clear();
511
- this.cachedInteractions = null;
560
+ this.cachedRaw = null;
561
+ this.cachedGrouped = null;
512
562
  }
513
563
  }
514
564
  function wrapWithAutomation(component2, options) {
package/dist/index.d.ts CHANGED
@@ -53,10 +53,12 @@ declare const HEADLESS_INSTANCES: ContextMarker<HeadlessInstancesData>;
53
53
  *
54
54
  * @param preRender - The inline template's render function
55
55
  * @param interactiveConstructor - The plugin's interactive constructor
56
- * @param coordinateKey - The coordinate key for this instance (e.g., "product-card:0")
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.
57
59
  * @param pluginContexts - Additional context markers from the plugin (if any)
58
60
  */
59
- 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, pluginContexts?: ContextMarkers<any>): any;
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;
60
62
 
61
63
  /**
62
64
  * Client-side action caller for Jay Stack.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
7
  import { makeJayComponent, createSignal, COMPONENT_CONTEXT, materializeViewState } from "@jay-framework/component";
8
- import { createJayContext, useContext } from "@jay-framework/runtime";
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 {};
@@ -87,8 +87,9 @@ function makeSignals$1(obj) {
87
87
  function makeHeadlessInstanceComponent(preRender, interactiveConstructor, coordinateKey, pluginContexts = []) {
88
88
  const wrappedConstructor = (props, refs, ...pluginResolvedContexts) => {
89
89
  const instanceData = useContext(HEADLESS_INSTANCES);
90
- const fastVS = instanceData?.viewStates?.[coordinateKey];
91
- const cf = instanceData?.carryForwards?.[coordinateKey] || {};
90
+ const resolvedKey = typeof coordinateKey === "function" ? coordinateKey(currentConstructionContext()?.dataIds ?? []) : coordinateKey;
91
+ const fastVS = instanceData?.viewStates?.[resolvedKey];
92
+ const cf = instanceData?.carryForwards?.[resolvedKey] || {};
92
93
  const signalVS = fastVS ? makeSignals$1(fastVS) : void 0;
93
94
  return interactiveConstructor(props, refs, signalVS, cf, ...pluginResolvedContexts);
94
95
  };
@@ -355,14 +356,13 @@ function collectInteractionsRecursive(refs, interactions) {
355
356
  continue;
356
357
  if (refImpl.elements && refImpl.elements instanceof Set) {
357
358
  for (const elem of refImpl.elements) {
358
- if (elem.element) {
359
+ if (elem.element && !isDisabled(elem.element)) {
359
360
  interactions.push({
360
361
  refName,
361
362
  coordinate: elem.coordinate || [refName],
362
363
  element: elem.element,
363
- elementType: getElementType(elem.element),
364
364
  supportedEvents: getSupportedEvents(elem.element),
365
- itemContext: elem.viewState
365
+ description: elem.description
366
366
  });
367
367
  }
368
368
  }
@@ -381,8 +381,12 @@ function isNestedRefsObject(obj) {
381
381
  return false;
382
382
  return true;
383
383
  }
384
- function getElementType(element) {
385
- return element.constructor.name;
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;
386
390
  }
387
391
  function getSupportedEvents(element) {
388
392
  const base = ["click", "focus", "blur"];
@@ -406,11 +410,45 @@ function getSupportedEvents(element) {
406
410
  }
407
411
  return base;
408
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
+ }
409
446
  const VIEW_STATE_CHANGE = "viewStateChange";
410
447
  class AutomationAgent {
411
448
  constructor(component, options) {
412
449
  __publicField2(this, "stateListeners", /* @__PURE__ */ new Set());
413
- __publicField2(this, "cachedInteractions", null);
450
+ __publicField2(this, "cachedRaw", null);
451
+ __publicField2(this, "cachedGrouped", null);
414
452
  __publicField2(this, "viewStateHandler", null);
415
453
  __publicField2(this, "mergedViewState");
416
454
  __publicField2(this, "initialSlowViewState");
@@ -429,7 +467,8 @@ class AutomationAgent {
429
467
  }
430
468
  subscribeToUpdates() {
431
469
  this.viewStateHandler = () => {
432
- this.cachedInteractions = null;
470
+ this.cachedRaw = null;
471
+ this.cachedGrouped = null;
433
472
  if (this.initialSlowViewState && this.trackByMap) {
434
473
  this.mergedViewState = deepMergeViewStates(
435
474
  this.initialSlowViewState,
@@ -447,33 +486,43 @@ class AutomationAgent {
447
486
  const state = this.getPageState();
448
487
  this.stateListeners.forEach((callback) => callback(state));
449
488
  }
450
- getPageState() {
451
- if (!this.cachedInteractions) {
452
- this.cachedInteractions = collectInteractions(this.component.element?.refs);
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);
453
495
  }
496
+ return this.cachedGrouped;
497
+ }
498
+ getPageState() {
454
499
  return {
455
500
  // Use merged state if available (slow+fast), otherwise component's viewState
456
501
  viewState: this.mergedViewState || this.component.viewState,
457
- interactions: this.cachedInteractions,
502
+ interactions: this.getGrouped(),
458
503
  customEvents: this.getCustomEvents()
459
504
  };
460
505
  }
461
506
  triggerEvent(eventType, coordinate, eventData) {
462
- const interaction = this.getInteraction(coordinate);
463
- if (!interaction) {
507
+ const instance = this.getInteraction(coordinate);
508
+ if (!instance) {
464
509
  throw new Error(`No element found at coordinate: ${coordinate.join("/")}`);
465
510
  }
466
511
  const event = new Event(eventType, { bubbles: true });
467
512
  if (eventData) {
468
513
  Object.assign(event, eventData);
469
514
  }
470
- interaction.element.dispatchEvent(event);
515
+ instance.element.dispatchEvent(event);
471
516
  }
472
517
  getInteraction(coordinate) {
473
- const state = this.getPageState();
474
- return state.interactions.find(
475
- (i) => i.coordinate.length === coordinate.length && i.coordinate.every((c, idx) => c === coordinate[idx])
476
- );
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;
477
526
  }
478
527
  onStateChange(callback) {
479
528
  this.stateListeners.add(callback);
@@ -506,7 +555,8 @@ class AutomationAgent {
506
555
  this.viewStateHandler = null;
507
556
  }
508
557
  this.stateListeners.clear();
509
- this.cachedInteractions = null;
558
+ this.cachedRaw = null;
559
+ this.cachedGrouped = null;
510
560
  }
511
561
  }
512
562
  function wrapWithAutomation(component, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/stack-client-runtime",
3
- "version": "0.12.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.12.0",
31
- "@jay-framework/fullstack-component": "^0.12.0",
32
- "@jay-framework/runtime": "^0.12.0",
33
- "@jay-framework/runtime-automation": "^0.12.0",
34
- "@jay-framework/view-state-merge": "^0.12.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.12.0",
38
- "@jay-framework/jay-cli": "^0.12.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",