@jay-framework/runtime 0.15.5 → 0.16.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 CHANGED
@@ -576,13 +576,26 @@ declare class ConstructContext<ViewState> {
576
576
  /** The accumulated trackBy values from ancestor forEach loops (for __headlessInstances key lookup) */
577
577
  get dataIds(): Coordinate;
578
578
  coordinate: (refName: string) => Coordinate;
579
+ /**
580
+ * Create a child context for a forEach/slowForEach item.
581
+ *
582
+ * With scoped coordinates (DL#126), coordinateBase is NOT accumulated —
583
+ * scoped coordinates are fully qualified within each scope. Only dataIds
584
+ * accumulates (for __headlessInstances key lookup).
585
+ *
586
+ * coordinateBase is still maintained for the non-hydration path where
587
+ * coordinate() is used for refs.
588
+ */
579
589
  forItem<ChildViewState>(childViewState: ChildViewState, id: string): ConstructContext<ChildViewState>;
580
590
  /**
581
- * Create a child context scoped to a headless instance's coordinate prefix.
582
- * Extends coordinateBase for coordinate resolution but does NOT add to dataIds
583
- * (instance segments are not trackBy values).
591
+ * Create a child context scoped to a DOM subtree (DL#126).
592
+ *
593
+ * Builds a LOCAL coordinate map from the scope root element's subtree.
594
+ * All coordinate lookups within this scope search the local map only.
595
+ * This ensures forEach items with shared scope IDs resolve correctly —
596
+ * each item builds its own local map from its own DOM branch.
584
597
  */
585
- forInstance(instanceCoordinate: string): ConstructContext<ViewState>;
598
+ forScope(scopeRootElement: Element): ConstructContext<ViewState>;
586
599
  forAsync<ChildViewState>(childViewState: ChildViewState): ConstructContext<ChildViewState>;
587
600
  /** Whether this context is in hydration mode (adopting existing DOM). */
588
601
  get isHydrating(): boolean;
@@ -590,11 +603,14 @@ declare class ConstructContext<ViewState> {
590
603
  get rootElement(): Element | undefined;
591
604
  /**
592
605
  * Resolve an element by its coordinate key from the hydration map.
593
- * The key is scoped by the current coordinateBase — e.g., inside a forEach
594
- * item with trackBy "item-1", resolveCoordinate("addBtn") looks up "item-1/addBtn".
595
606
  *
596
- * When multiple elements share the same coordinate (e.g., duplicate ref names),
597
- * each call returns the next element in document order.
607
+ * With scoped coordinates (DL#126), the key is fully qualified within the
608
+ * scope (e.g., "S2/0"). No coordinateBase prefix is applied — coordinates
609
+ * are self-contained within their scope.
610
+ *
611
+ * When multiple elements share the same coordinate (e.g., forEach items
612
+ * sharing the same template scope IDs), each call returns the next element
613
+ * in document order.
598
614
  */
599
615
  resolveCoordinate(key: string): Element | undefined;
600
616
  /**
@@ -696,28 +712,25 @@ declare function hydrateConditional<ViewState>(condition: (vs: ViewState) => boo
696
712
  *
697
713
  * @param accessor - Function to get the array from the ViewState
698
714
  * @param trackBy - Property name used for item identity (reconciliation key)
715
+ * @param itemCoordinate - Scoped coordinate of each forEach item root element (DL#126).
716
+ * All items share this coordinate; each resolveCoordinate call consumes the next one.
699
717
  * @param adoptItem - Called per existing item during hydration (should use adoptText/adoptElement)
700
718
  * @param createItem - Called per new item (regular element()/dynamicText() from generated-element.ts)
701
719
  */
702
- declare function hydrateForEach<ViewState, Item>(accessor: (vs: ViewState) => Item[], trackBy: string, adoptItem: () => BaseJayElement<Item>[], createItem: (item: Item, id: string) => BaseJayElement<Item>): DynamicChild<ViewState>;
720
+ declare function hydrateForEach<ViewState, Item>(accessor: (vs: ViewState) => Item[], trackBy: string, itemCoordinate: string, adoptItem: () => BaseJayElement<Item>[], createItem: (item: Item, id: string) => BaseJayElement<Item>): DynamicChild<ViewState>;
703
721
  /**
704
722
  * Hydration-aware child component instantiation.
705
723
  *
706
- * Like childComp, but extends the current ConstructContext's coordinateBase
707
- * with the instance's coordinate key before calling the component factory.
708
- * This scopes coordinate resolution so that adoptElement('0') inside the
709
- * child's inline template resolves to '{instanceCoordinate}/0' in the
710
- * page's coordinate map.
711
- *
712
- * Used for headless component instances during hydration. The child
713
- * component's preRender calls ConstructContext.withHydrationChildContext()
714
- * which inherits the scoped coordinateBase.
724
+ * With scoped coordinates (DL#126), the child component creates a LOCAL
725
+ * coordinate map from its inline template root element's subtree.
726
+ * This ensures the child's adopt calls resolve against the correct DOM
727
+ * branch, not the global map.
715
728
  *
716
729
  * @param compCreator - Component factory (from makeHeadlessInstanceComponent)
717
730
  * @param getProps - Extracts component props from parent ViewState
718
- * @param instanceCoordinate - The instance's coordinate key (e.g., 'product-card:0')
731
+ * @param scopeRootCoordinate - Coordinate of the inline template root element (e.g., "S2/0")
719
732
  * @param ref - Optional ref for the component instance
720
733
  */
721
- declare function childCompHydrate<ParentVS, Props, ChildT, ChildElement extends BaseJayElement<ChildT>, ChildComp extends JayComponent<Props, ChildT, ChildElement>>(compCreator: JayComponentConstructor<Props>, getProps: (t: ParentVS) => Props, instanceCoordinate: string, ref?: PrivateRef<ParentVS, ChildComp>): BaseJayElement<ParentVS>;
734
+ declare function childCompHydrate<ParentVS, Props, ChildT, ChildElement extends BaseJayElement<ChildT>, ChildComp extends JayComponent<Props, ChildT, ChildElement>>(compCreator: JayComponentConstructor<Props>, getProps: (t: ParentVS) => Props, scopeRootCoordinate?: string, ref?: PrivateRef<ParentVS, ChildComp>): BaseJayElement<ParentVS>;
722
735
 
723
736
  export { type Attribute, type Attributes, type BaseJayElement, BaseReferencesManager, type ComponentCollectionProxy, ComponentCollectionRefImpl, type ComponentProxy, ComponentRefImpl, ComponentRefsImpl, type Conditional, ConstructContext, type ContextMarker, type Coordinate, type DynamicAttributeOrProperty, EVENT_TRAP, type ElementFrom, type EventEmitter, type EventTypeFrom, type ExtractFastViewState, type ExtractInteractiveViewState, type ExtractProps, type ExtractRefs, type ExtractSlowViewState, type ExtractViewState, type ForEach, GetTrapProxy, type GlobalJayEvents, type HTMLElementCollectionProxy, type HTMLElementCollectionProxyTarget, type HTMLElementProxy, type HTMLElementProxyTarget, type HTMLNativeExec, type HeadLink, type JayComponent, type JayComponentConstructor, type JayContract, type JayElement, type JayEvent, type JayEventHandler, type JayEventHandlerWrapper, type JayLog, type JayNativeEventBuilder, type JayNativeFunction, LogType, type ManagedRefConstructor, ManagedRefType, type ManagedRefs, type MapEventEmitterViewState, type MountFunc, type OnlyEventEmitters, type PreRenderElement, type PrivateRef, type PrivateRefConstructor, PrivateRefs, type PropsFrom, ReferencesManager, type RenderElement, type RenderElementOptions, STATIC, type TextElement, VIEW_STATE_CHANGE_EVENT, type ViewStateFrom, type When, WhenRole, type WithData, adoptDynamicElement, adoptElement, adoptText, booleanAttribute, childComp, childCompHydrate, clearGlobalContextRegistry, conditional, createJayContext, currentConstructionContext, defaultEventWrapper, dynamicAttribute, dynamicElement, dynamicElementNS, dynamicProperty, dynamicText, element, findContext, forEach, hydrateConditional, hydrateForEach, injectHeadLinks, isCondition, isForEach, isWhen, isWithData, jayLog, mathMLDynamicElement, mathMLElement, mkUpdateCollection, noop, noopMount, noopUpdate, normalizeMount, normalizeUpdates, pending, registerGlobalContext, rejected, resolved, restoreContext, saveContext, slowForEachItem, svgDynamicElement, svgElement, type updateFunc, useContext, useGlobalContext, withContext, withData };
package/dist/index.js CHANGED
@@ -196,6 +196,16 @@ class ConstructContext {
196
196
  get dataIds() {
197
197
  return this._dataIds;
198
198
  }
199
+ /**
200
+ * Create a child context for a forEach/slowForEach item.
201
+ *
202
+ * With scoped coordinates (DL#126), coordinateBase is NOT accumulated —
203
+ * scoped coordinates are fully qualified within each scope. Only dataIds
204
+ * accumulates (for __headlessInstances key lookup).
205
+ *
206
+ * coordinateBase is still maintained for the non-hydration path where
207
+ * coordinate() is used for refs.
208
+ */
199
209
  forItem(childViewState, id) {
200
210
  return new ConstructContext(
201
211
  childViewState,
@@ -207,18 +217,21 @@ class ConstructContext {
207
217
  );
208
218
  }
209
219
  /**
210
- * Create a child context scoped to a headless instance's coordinate prefix.
211
- * Extends coordinateBase for coordinate resolution but does NOT add to dataIds
212
- * (instance segments are not trackBy values).
220
+ * Create a child context scoped to a DOM subtree (DL#126).
221
+ *
222
+ * Builds a LOCAL coordinate map from the scope root element's subtree.
223
+ * All coordinate lookups within this scope search the local map only.
224
+ * This ensures forEach items with shared scope IDs resolve correctly —
225
+ * each item builds its own local map from its own DOM branch.
213
226
  */
214
- forInstance(instanceCoordinate) {
215
- const segments = instanceCoordinate.split("/");
227
+ forScope(scopeRootElement) {
228
+ const localMap = buildCoordinateMap(scopeRootElement);
216
229
  return new ConstructContext(
217
230
  this.data,
218
231
  false,
219
- [...this.coordinateBase, ...segments],
220
- this._coordinateMap,
221
- this._rootElement,
232
+ this.coordinateBase,
233
+ localMap,
234
+ scopeRootElement,
222
235
  this._dataIds
223
236
  );
224
237
  }
@@ -242,18 +255,19 @@ class ConstructContext {
242
255
  }
243
256
  /**
244
257
  * Resolve an element by its coordinate key from the hydration map.
245
- * The key is scoped by the current coordinateBase — e.g., inside a forEach
246
- * item with trackBy "item-1", resolveCoordinate("addBtn") looks up "item-1/addBtn".
247
258
  *
248
- * When multiple elements share the same coordinate (e.g., duplicate ref names),
249
- * each call returns the next element in document order.
259
+ * With scoped coordinates (DL#126), the key is fully qualified within the
260
+ * scope (e.g., "S2/0"). No coordinateBase prefix is applied — coordinates
261
+ * are self-contained within their scope.
262
+ *
263
+ * When multiple elements share the same coordinate (e.g., forEach items
264
+ * sharing the same template scope IDs), each call returns the next element
265
+ * in document order.
250
266
  */
251
267
  resolveCoordinate(key) {
252
268
  if (!this._coordinateMap)
253
269
  return void 0;
254
- const base = this.coordinateBase.length > 0 ? this.coordinateBase.join("/") : "";
255
- const fullKey = base ? key ? base + "/" + key : base : key;
256
- const elements = this._coordinateMap.get(fullKey);
270
+ const elements = this._coordinateMap.get(key);
257
271
  if (!elements || elements.length === 0)
258
272
  return void 0;
259
273
  return elements.shift();
@@ -267,9 +281,7 @@ class ConstructContext {
267
281
  peekCoordinate(key) {
268
282
  if (!this._coordinateMap)
269
283
  return void 0;
270
- const base = this.coordinateBase.length > 0 ? this.coordinateBase.join("/") : "";
271
- const fullKey = base ? key ? base + "/" + key : base : key;
272
- const elements = this._coordinateMap.get(fullKey);
284
+ const elements = this._coordinateMap.get(key);
273
285
  if (!elements || elements.length === 0)
274
286
  return void 0;
275
287
  return elements[0];
@@ -1256,11 +1268,19 @@ function adoptBase(coordinate, attributes, ref) {
1256
1268
  if (typeof value === "object" && value !== null && "valueFunc" in value) {
1257
1269
  const dynAttr = value;
1258
1270
  let attrValue = dynAttr.valueFunc(context.currData);
1271
+ const isBooleanAttr = dynAttr.style === 3;
1259
1272
  if (!(key === STYLE && element2 instanceof HTMLElement)) {
1260
1273
  updates.push((newData) => {
1261
1274
  const newAttrValue = dynAttr.valueFunc(newData);
1262
1275
  if (newAttrValue !== attrValue) {
1263
- element2.setAttribute(key, newAttrValue);
1276
+ if (isBooleanAttr) {
1277
+ if (newAttrValue)
1278
+ element2.setAttribute(key, "");
1279
+ else
1280
+ element2.removeAttribute(key);
1281
+ } else {
1282
+ element2.setAttribute(key, newAttrValue);
1283
+ }
1264
1284
  }
1265
1285
  attrValue = newAttrValue;
1266
1286
  });
@@ -1418,17 +1438,18 @@ function hydrateConditional(condition, adoptExisting, createFallback) {
1418
1438
  };
1419
1439
  return result;
1420
1440
  }
1421
- function hydrateForEach(accessor, trackBy, adoptItem, createItem) {
1441
+ function hydrateForEach(accessor, trackBy, itemCoordinate, adoptItem, createItem) {
1422
1442
  const context = currentConstructionContext();
1423
1443
  const savedContext = saveContext();
1424
1444
  const parentContext = context;
1425
1445
  const initialItems = accessor(context.currData) || [];
1426
1446
  const adoptedItems = [];
1427
- for (const item of initialItems) {
1447
+ for (let i = 0; i < initialItems.length; i++) {
1448
+ const item = initialItems[i];
1428
1449
  const id = String(item[trackBy]);
1429
- const itemDom = context.peekCoordinate(id);
1430
- const childContext = context.forItem(item, id);
1431
- const adopted = withContext(CONSTRUCTION_CONTEXT_MARKER, childContext, () => {
1450
+ const itemDom = context.resolveCoordinate(itemCoordinate);
1451
+ const scopedContext = itemDom ? context.forScope(itemDom).forItem(item, id) : context.forItem(item, id);
1452
+ const adopted = withContext(CONSTRUCTION_CONTEXT_MARKER, scopedContext, () => {
1432
1453
  const elements = adoptItem();
1433
1454
  const updates = [];
1434
1455
  const mounts = [];
@@ -1519,9 +1540,10 @@ function hydrateForEach(accessor, trackBy, adoptItem, createItem) {
1519
1540
  };
1520
1541
  return result;
1521
1542
  }
1522
- function childCompHydrate(compCreator, getProps, instanceCoordinate, ref) {
1543
+ function childCompHydrate(compCreator, getProps, scopeRootCoordinate, ref) {
1523
1544
  const context = currentConstructionContext();
1524
- const childContext = context.forInstance(instanceCoordinate);
1545
+ const scopeRoot = scopeRootCoordinate ? context.resolveCoordinate(scopeRootCoordinate) : void 0;
1546
+ const childContext = scopeRoot ? context.forScope(scopeRoot) : context;
1525
1547
  return withContext(CONSTRUCTION_CONTEXT_MARKER, childContext, () => {
1526
1548
  const childComp2 = compCreator(getProps(context.currData));
1527
1549
  const updates = [(t) => childComp2.update(getProps(t))];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/runtime",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -23,11 +23,11 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
- "@jay-framework/list-compare": "^0.15.5",
27
- "@jay-framework/reactive": "^0.15.5"
26
+ "@jay-framework/list-compare": "^0.16.0",
27
+ "@jay-framework/reactive": "^0.16.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@jay-framework/dev-environment": "^0.15.5",
30
+ "@jay-framework/dev-environment": "^0.16.0",
31
31
  "@testing-library/jest-dom": "^6.2.0",
32
32
  "@types/jsdom": "^21.1.6",
33
33
  "@types/node": "^20.11.5",