@jay-framework/runtime 0.13.0 → 0.15.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
@@ -18,6 +18,7 @@ declare class Kindergarten {
18
18
  private groups;
19
19
  constructor(parentNode: Element);
20
20
  newGroup(): KindergartenGroup;
21
+ getGroups(): ReadonlyArray<KindergartenGroup>;
21
22
  getOffsetFor(group: KindergartenGroup): number;
22
23
  }
23
24
 
@@ -567,14 +568,156 @@ declare class ConstructContext<ViewState> {
567
568
  private readonly data;
568
569
  readonly forStaticElements: boolean;
569
570
  private readonly coordinateBase;
570
- constructor(data: ViewState, forStaticElements?: boolean, coordinateBase?: Coordinate);
571
+ private readonly _coordinateMap?;
572
+ private readonly _rootElement?;
573
+ private readonly _dataIds;
574
+ constructor(data: ViewState, forStaticElements?: boolean, coordinateBase?: Coordinate, coordinateMap?: Map<string, Element[]>, rootElement?: Element, dataIds?: Coordinate);
571
575
  get currData(): ViewState;
572
- /** The accumulated coordinate base (trackBy values from ancestor forEach loops) */
576
+ /** The accumulated trackBy values from ancestor forEach loops (for __headlessInstances key lookup) */
573
577
  get dataIds(): Coordinate;
574
578
  coordinate: (refName: string) => Coordinate;
575
579
  forItem<ChildViewState>(childViewState: ChildViewState, id: string): ConstructContext<ChildViewState>;
580
+ /**
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).
584
+ */
585
+ forInstance(instanceCoordinate: string): ConstructContext<ViewState>;
576
586
  forAsync<ChildViewState>(childViewState: ChildViewState): ConstructContext<ChildViewState>;
587
+ /** Whether this context is in hydration mode (adopting existing DOM). */
588
+ get isHydrating(): boolean;
589
+ /** The root element being hydrated (undefined in non-hydration mode). */
590
+ get rootElement(): Element | undefined;
591
+ /**
592
+ * 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
+ *
596
+ * When multiple elements share the same coordinate (e.g., duplicate ref names),
597
+ * each call returns the next element in document order.
598
+ */
599
+ resolveCoordinate(key: string): Element | undefined;
600
+ /**
601
+ * Peek at an element by its coordinate key without consuming it.
602
+ * Used by hydrateForEach to resolve the container element — the same element
603
+ * is also consumed by the parent adoptElement call (which evaluates after
604
+ * hydrateForEach due to JavaScript argument evaluation order).
605
+ */
606
+ peekCoordinate(key: string): Element | undefined;
577
607
  static withRootContext<ViewState, Refs>(viewState: ViewState, refManager: ReferencesManager, elementConstructor: () => BaseJayElement<ViewState>): JayElement<ViewState, Refs>;
608
+ /**
609
+ * Hydrate a child component's inline template within the parent's coordinate scope.
610
+ *
611
+ * Like withRootContext, but inherits the coordinateBase and coordinateMap from
612
+ * the current (parent) ConstructContext. This allows adoptElement/adoptText calls
613
+ * inside the child to resolve coordinates scoped to the child's prefix.
614
+ *
615
+ * Used by headless component instances during hydration: the parent pushes a
616
+ * scoped context (via childCompHydrate), and the child's preRender calls this
617
+ * method which inherits the scoped coordinateBase.
618
+ */
619
+ static withHydrationChildContext<ViewState, Refs>(viewState: ViewState, refManager: ReferencesManager, elementConstructor: () => BaseJayElement<ViewState>): JayElement<ViewState, Refs>;
620
+ /**
621
+ * Hydrate existing server-rendered DOM.
622
+ *
623
+ * Builds a coordinate→element map from all [jay-coordinate] attributes
624
+ * inside rootElement, creates a ConstructContext in hydration mode,
625
+ * pushes it onto the context stack, then calls hydrateConstructor.
626
+ *
627
+ * The hydrateConstructor calls adoptText(), adoptElement(), etc. which
628
+ * read from the context stack — same pattern as element(), dynamicText().
629
+ * It returns a BaseJayElement whose update/mount/unmount are composed
630
+ * from all adopted children — same as withRootContext's elementConstructor.
631
+ */
632
+ static withHydrationRootContext<ViewState, Refs>(viewState: ViewState, refManager: ReferencesManager, rootElement: Element, hydrateConstructor: () => BaseJayElement<ViewState>): JayElement<ViewState, Refs>;
578
633
  }
579
634
 
580
- 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, type TextElement, VIEW_STATE_CHANGE_EVENT, type ViewStateFrom, type When, WhenRole, type WithData, booleanAttribute, childComp, clearGlobalContextRegistry, conditional, createJayContext, currentConstructionContext, defaultEventWrapper, dynamicAttribute, dynamicElement, dynamicElementNS, dynamicProperty, dynamicText, element, findContext, forEach, 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 };
635
+ /**
636
+ * Sentinel value for static children in adoptDynamicElement.
637
+ * Represents a child position occupied by a pre-existing DOM node
638
+ * that has no hydration code (no dynamic text, no dynamic attributes, no refs).
639
+ */
640
+ declare const STATIC: unique symbol;
641
+ /** Element returned by hydrateForEach/hydrateConditional that needs a group assigned later. */
642
+ type DynamicChild<ViewState> = BaseJayElement<ViewState> & {
643
+ _setGroup: (group: KindergartenGroup) => void;
644
+ };
645
+ /**
646
+ * Adopt an existing text node inside the element at the given coordinate.
647
+ *
648
+ * Reads the current ConstructContext from the stack (via currentConstructionContext())
649
+ * to resolve the coordinate to an existing DOM element. Finds the text child at
650
+ * the given index (by significant-child position) and connects a dynamic text updater.
651
+ *
652
+ * - coordinate: element containing the text (or parent in mixed content)
653
+ * - accessor: (vs) => string | number | boolean
654
+ * - ref?: optional ref for the element
655
+ * - childIndex?: index among significant children (default 0). Use when parent has
656
+ * mixed content (text + elements); matches element target's positional dynamicText.
657
+ */
658
+ declare function adoptText<ViewState>(coordinate: string, accessor: (vs: ViewState) => string | number | boolean, ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>, childIndex?: number): BaseJayElement<ViewState>;
659
+ /**
660
+ * Adopt an existing element at the given coordinate, connecting dynamic
661
+ * attributes and adopted children to it.
662
+ *
663
+ * Hydration counterpart to element() — adopts server-rendered HTML and wires
664
+ * up dynamic attribute bindings and children.
665
+ */
666
+ declare function adoptElement<ViewState>(coordinate: string, attributes: Attributes<ViewState>, children?: BaseJayElement<ViewState>[], ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>): BaseJayElement<ViewState>;
667
+ /**
668
+ * Adopt an existing element that has interactive children (forEach/conditional).
669
+ *
670
+ * Creates one Kindergarten per parent, one group per child position.
671
+ * STATIC sentinels represent children with no hydrate code.
672
+ * Children with `_setGroup` (forEach/conditional) receive their group via callback.
673
+ */
674
+ declare function adoptDynamicElement<ViewState>(coordinate: string, attributes: Attributes<ViewState>, children?: (BaseJayElement<ViewState> | typeof STATIC)[], ref?: PrivateRef<ViewState, BaseJayElement<ViewState>>): BaseJayElement<ViewState>;
675
+ /**
676
+ * Hydration-aware conditional.
677
+ *
678
+ * Returns an element with `_setGroup` callback. The parent `adoptDynamicElement`
679
+ * calls `_setGroup` to assign a KindergartenGroup, which is used for DOM
680
+ * positioning (ensureNode/removeNode) instead of anchor comments.
681
+ *
682
+ * Handles two cases:
683
+ * - **True at SSR**: Element exists in DOM. Adopt it, register in group, wire toggle.
684
+ * - **False at SSR**: Element absent. Use createFallback to lazily create when true.
685
+ */
686
+ declare function hydrateConditional<ViewState>(condition: (vs: ViewState) => boolean, adoptExisting: () => BaseJayElement<ViewState>, createFallback?: () => BaseJayElement<ViewState>): DynamicChild<ViewState>;
687
+ /**
688
+ * Hydration-aware forEach.
689
+ *
690
+ * Adopts existing items that were server-rendered, and creates new items via
691
+ * the regular element creation path when the list changes.
692
+ *
693
+ * Returns an element with `_setGroup` callback. The parent `adoptDynamicElement`
694
+ * calls `_setGroup` to assign a KindergartenGroup from the parent's Kindergarten,
695
+ * ensuring correct DOM positioning relative to siblings.
696
+ *
697
+ * @param accessor - Function to get the array from the ViewState
698
+ * @param trackBy - Property name used for item identity (reconciliation key)
699
+ * @param adoptItem - Called per existing item during hydration (should use adoptText/adoptElement)
700
+ * @param createItem - Called per new item (regular element()/dynamicText() from generated-element.ts)
701
+ */
702
+ declare function hydrateForEach<ViewState, Item>(accessor: (vs: ViewState) => Item[], trackBy: string, adoptItem: () => BaseJayElement<Item>[], createItem: (item: Item, id: string) => BaseJayElement<Item>): DynamicChild<ViewState>;
703
+ /**
704
+ * Hydration-aware child component instantiation.
705
+ *
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.
715
+ *
716
+ * @param compCreator - Component factory (from makeHeadlessInstanceComponent)
717
+ * @param getProps - Extracts component props from parent ViewState
718
+ * @param instanceCoordinate - The instance's coordinate key (e.g., 'product-card:0')
719
+ * @param ref - Optional ref for the component instance
720
+ */
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>;
722
+
723
+ 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
@@ -70,11 +70,18 @@ class Kindergarten {
70
70
  this.groups.push(kindergartenGroup);
71
71
  return kindergartenGroup;
72
72
  }
73
+ getGroups() {
74
+ return this.groups;
75
+ }
73
76
  getOffsetFor(group) {
74
77
  let index = 0;
75
78
  let offset = 0;
76
79
  while (index < this.groups.length && this.groups[index] !== group) {
77
- offset += this.groups[index].children.size;
80
+ for (const child of this.groups[index].children) {
81
+ if (child.parentNode === this.parentNode) {
82
+ offset++;
83
+ }
84
+ }
78
85
  index = index + 1;
79
86
  }
80
87
  return offset;
@@ -168,26 +175,104 @@ function wrapWithModifiedCheck(initialData, baseJayElement) {
168
175
  return baseJayElement;
169
176
  }
170
177
  class ConstructContext {
171
- constructor(data, forStaticElements = true, coordinateBase = []) {
178
+ constructor(data, forStaticElements = true, coordinateBase = [], coordinateMap, rootElement, dataIds) {
179
+ __publicField(this, "_coordinateMap");
180
+ __publicField(this, "_rootElement");
181
+ __publicField(this, "_dataIds");
172
182
  __publicField(this, "coordinate", (refName) => {
173
183
  return [...this.coordinateBase, refName];
174
184
  });
175
185
  this.data = data;
176
186
  this.forStaticElements = forStaticElements;
177
187
  this.coordinateBase = coordinateBase;
188
+ this._coordinateMap = coordinateMap;
189
+ this._rootElement = rootElement;
190
+ this._dataIds = dataIds ?? coordinateBase;
178
191
  }
179
192
  get currData() {
180
193
  return this.data;
181
194
  }
182
- /** The accumulated coordinate base (trackBy values from ancestor forEach loops) */
195
+ /** The accumulated trackBy values from ancestor forEach loops (for __headlessInstances key lookup) */
183
196
  get dataIds() {
184
- return this.coordinateBase;
197
+ return this._dataIds;
185
198
  }
186
199
  forItem(childViewState, id) {
187
- return new ConstructContext(childViewState, false, [...this.coordinateBase, id]);
200
+ return new ConstructContext(
201
+ childViewState,
202
+ false,
203
+ [...this.coordinateBase, id],
204
+ this._coordinateMap,
205
+ this._rootElement,
206
+ [...this._dataIds, id]
207
+ );
208
+ }
209
+ /**
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).
213
+ */
214
+ forInstance(instanceCoordinate) {
215
+ const segments = instanceCoordinate.split("/");
216
+ return new ConstructContext(
217
+ this.data,
218
+ false,
219
+ [...this.coordinateBase, ...segments],
220
+ this._coordinateMap,
221
+ this._rootElement,
222
+ this._dataIds
223
+ );
188
224
  }
189
225
  forAsync(childViewState) {
190
- return new ConstructContext(childViewState, false, [...this.coordinateBase]);
226
+ return new ConstructContext(
227
+ childViewState,
228
+ false,
229
+ [...this.coordinateBase],
230
+ this._coordinateMap,
231
+ this._rootElement,
232
+ this._dataIds
233
+ );
234
+ }
235
+ /** Whether this context is in hydration mode (adopting existing DOM). */
236
+ get isHydrating() {
237
+ return this._coordinateMap !== void 0;
238
+ }
239
+ /** The root element being hydrated (undefined in non-hydration mode). */
240
+ get rootElement() {
241
+ return this._rootElement;
242
+ }
243
+ /**
244
+ * 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
+ *
248
+ * When multiple elements share the same coordinate (e.g., duplicate ref names),
249
+ * each call returns the next element in document order.
250
+ */
251
+ resolveCoordinate(key) {
252
+ if (!this._coordinateMap)
253
+ 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);
257
+ if (!elements || elements.length === 0)
258
+ return void 0;
259
+ return elements.shift();
260
+ }
261
+ /**
262
+ * Peek at an element by its coordinate key without consuming it.
263
+ * Used by hydrateForEach to resolve the container element — the same element
264
+ * is also consumed by the parent adoptElement call (which evaluates after
265
+ * hydrateForEach due to JavaScript argument evaluation order).
266
+ */
267
+ peekCoordinate(key) {
268
+ if (!this._coordinateMap)
269
+ 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);
273
+ if (!elements || elements.length === 0)
274
+ return void 0;
275
+ return elements[0];
191
276
  }
192
277
  static withRootContext(viewState, refManager, elementConstructor) {
193
278
  let context = new ConstructContext(viewState);
@@ -199,8 +284,82 @@ class ConstructContext {
199
284
  element2.mount();
200
285
  return refManager.applyToElement(element2);
201
286
  }
287
+ /**
288
+ * Hydrate a child component's inline template within the parent's coordinate scope.
289
+ *
290
+ * Like withRootContext, but inherits the coordinateBase and coordinateMap from
291
+ * the current (parent) ConstructContext. This allows adoptElement/adoptText calls
292
+ * inside the child to resolve coordinates scoped to the child's prefix.
293
+ *
294
+ * Used by headless component instances during hydration: the parent pushes a
295
+ * scoped context (via childCompHydrate), and the child's preRender calls this
296
+ * method which inherits the scoped coordinateBase.
297
+ */
298
+ static withHydrationChildContext(viewState, refManager, elementConstructor) {
299
+ const parentContext = currentConstructionContext();
300
+ const context = new ConstructContext(
301
+ viewState,
302
+ false,
303
+ parentContext?.coordinateBase || [],
304
+ parentContext?._coordinateMap,
305
+ parentContext?._rootElement
306
+ );
307
+ const element2 = withContext(
308
+ CONSTRUCTION_CONTEXT_MARKER,
309
+ context,
310
+ () => wrapWithModifiedCheck(currentConstructionContext().currData, elementConstructor())
311
+ );
312
+ element2.mount();
313
+ return refManager.applyToElement(element2);
314
+ }
315
+ /**
316
+ * Hydrate existing server-rendered DOM.
317
+ *
318
+ * Builds a coordinate→element map from all [jay-coordinate] attributes
319
+ * inside rootElement, creates a ConstructContext in hydration mode,
320
+ * pushes it onto the context stack, then calls hydrateConstructor.
321
+ *
322
+ * The hydrateConstructor calls adoptText(), adoptElement(), etc. which
323
+ * read from the context stack — same pattern as element(), dynamicText().
324
+ * It returns a BaseJayElement whose update/mount/unmount are composed
325
+ * from all adopted children — same as withRootContext's elementConstructor.
326
+ */
327
+ static withHydrationRootContext(viewState, refManager, rootElement, hydrateConstructor) {
328
+ const coordinateMap = buildCoordinateMap(rootElement);
329
+ const context = new ConstructContext(viewState, true, [], coordinateMap, rootElement);
330
+ const element2 = withContext(CONSTRUCTION_CONTEXT_MARKER, context, () => {
331
+ const constructed = hydrateConstructor();
332
+ return wrapWithModifiedCheck(currentConstructionContext().currData, {
333
+ ...constructed,
334
+ dom: rootElement
335
+ });
336
+ });
337
+ element2.mount();
338
+ return refManager.applyToElement(element2);
339
+ }
202
340
  }
203
- const STYLE = "style";
341
+ function buildCoordinateMap(root) {
342
+ const map = /* @__PURE__ */ new Map();
343
+ const addToMap = (key, el) => {
344
+ const arr = map.get(key);
345
+ if (arr)
346
+ arr.push(el);
347
+ else
348
+ map.set(key, [el]);
349
+ };
350
+ const rootKey = root.getAttribute("jay-coordinate");
351
+ if (rootKey)
352
+ addToMap(rootKey, root);
353
+ const elements = root.querySelectorAll("[jay-coordinate]");
354
+ for (let i = 0; i < elements.length; i++) {
355
+ const el = elements[i];
356
+ const key = el.getAttribute("jay-coordinate");
357
+ if (key)
358
+ addToMap(key, el);
359
+ }
360
+ return map;
361
+ }
362
+ const STYLE$1 = "style";
204
363
  function mkRef(ref, referenced, updates, mounts, unmounts) {
205
364
  updates.push(ref.update);
206
365
  ref.set(referenced);
@@ -402,7 +561,7 @@ function slowForEachItem(getItems, index, trackByValue, elementCreator) {
402
561
  function withData(accessor, elem) {
403
562
  return { accessor, elem };
404
563
  }
405
- function applyListChanges(group, instructions) {
564
+ function applyListChanges$1(group, instructions) {
406
565
  instructions.forEach((instruction) => {
407
566
  if (instruction.action === ITEM_ADDED) {
408
567
  group.ensureNode(instruction.elem.dom, instruction.pos);
@@ -447,7 +606,7 @@ function mkUpdateCollection(child, group) {
447
606
  }
448
607
  );
449
608
  lastItemsList = itemsList;
450
- applyListChanges(group, instructions);
609
+ applyListChanges$1(group, instructions);
451
610
  itemsList.forEach((value, elem) => elem.update(value));
452
611
  }
453
612
  };
@@ -624,7 +783,7 @@ function createBaseElement(ns, tagName, attributes, ref) {
624
783
  if (ref)
625
784
  mkRef(ref, e, updates, mounts, unmounts);
626
785
  Object.entries(attributes).forEach(([key, value]) => {
627
- if (key === STYLE && e instanceof HTMLElement) {
786
+ if (key === STYLE$1 && e instanceof HTMLElement) {
628
787
  Object.entries(value).forEach(([styleKey, styleValue]) => {
629
788
  setAttribute(
630
789
  e.style,
@@ -1015,6 +1174,386 @@ class ReferencesManager extends BaseReferencesManager {
1015
1174
  ];
1016
1175
  }
1017
1176
  }
1177
+ const STYLE = "style";
1178
+ const STATIC = Symbol("STATIC");
1179
+ function getSignificantChild(element2, index) {
1180
+ const nodes = element2.childNodes;
1181
+ if (nodes.length <= 1)
1182
+ return nodes[index];
1183
+ let i = 0;
1184
+ for (let j = 0; j < nodes.length; j++) {
1185
+ const node = nodes[j];
1186
+ if (node.nodeType !== Node.TEXT_NODE || (node.textContent || "").trim() !== "") {
1187
+ if (i === index)
1188
+ return node;
1189
+ i++;
1190
+ }
1191
+ }
1192
+ return void 0;
1193
+ }
1194
+ function adoptText(coordinate, accessor, ref, childIndex) {
1195
+ const context = currentConstructionContext();
1196
+ const element2 = context.peekCoordinate(coordinate);
1197
+ if (!element2) {
1198
+ console.warn(`[jay hydration] adoptText coordinate "${coordinate}" not found in DOM`);
1199
+ return { dom: void 0, update: noopUpdate, mount: noopMount, unmount: noopMount };
1200
+ }
1201
+ const index = childIndex ?? 0;
1202
+ const textNode = getSignificantChild(element2, index);
1203
+ if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {
1204
+ console.warn(
1205
+ `[jay hydration] adoptText(${coordinate}, childIndex=${index}): expected text node`
1206
+ );
1207
+ return { dom: void 0, update: noopUpdate, mount: noopMount, unmount: noopMount };
1208
+ }
1209
+ let content = textNode.textContent ?? "";
1210
+ const updates = [];
1211
+ const mounts = [];
1212
+ const unmounts = [];
1213
+ updates.push((newData) => {
1214
+ const newContent = accessor(newData);
1215
+ if (newContent !== content) {
1216
+ textNode.textContent = String(newContent);
1217
+ }
1218
+ content = newContent;
1219
+ });
1220
+ if (ref) {
1221
+ ref.set(element2);
1222
+ updates.push(ref.update);
1223
+ mounts.push(ref.mount);
1224
+ unmounts.push(ref.unmount);
1225
+ }
1226
+ return {
1227
+ dom: element2,
1228
+ update: normalizeUpdates(updates),
1229
+ mount: normalizeMount(mounts),
1230
+ unmount: normalizeMount(unmounts)
1231
+ };
1232
+ }
1233
+ const NOOP_ELEMENT = {
1234
+ dom: void 0,
1235
+ update: noopUpdate,
1236
+ mount: noopMount,
1237
+ unmount: noopMount
1238
+ };
1239
+ function adoptBase(coordinate, attributes, ref) {
1240
+ const context = currentConstructionContext();
1241
+ const element2 = context.resolveCoordinate(coordinate);
1242
+ if (!element2) {
1243
+ console.warn(`[jay hydration] adoptBase coordinate "${coordinate}" not found in DOM`);
1244
+ return null;
1245
+ }
1246
+ const updates = [];
1247
+ const mounts = [];
1248
+ const unmounts = [];
1249
+ if (ref) {
1250
+ ref.set(element2);
1251
+ updates.push(ref.update);
1252
+ mounts.push(ref.mount);
1253
+ unmounts.push(ref.unmount);
1254
+ }
1255
+ Object.entries(attributes).forEach(([key, value]) => {
1256
+ if (typeof value === "object" && value !== null && "valueFunc" in value) {
1257
+ const dynAttr = value;
1258
+ let attrValue = dynAttr.valueFunc(context.currData);
1259
+ if (!(key === STYLE && element2 instanceof HTMLElement)) {
1260
+ updates.push((newData) => {
1261
+ const newAttrValue = dynAttr.valueFunc(newData);
1262
+ if (newAttrValue !== attrValue) {
1263
+ element2.setAttribute(key, newAttrValue);
1264
+ }
1265
+ attrValue = newAttrValue;
1266
+ });
1267
+ }
1268
+ }
1269
+ });
1270
+ return { element: element2, updates, mounts, unmounts };
1271
+ }
1272
+ function collectChild(child, updates, mounts, unmounts) {
1273
+ if (child.update !== noopUpdate)
1274
+ updates.push(child.update);
1275
+ if (child.mount !== noopMount)
1276
+ mounts.push(child.mount);
1277
+ if (child.unmount !== noopMount)
1278
+ unmounts.push(child.unmount);
1279
+ }
1280
+ function adoptElement(coordinate, attributes, children = [], ref) {
1281
+ const base = adoptBase(coordinate, attributes, ref);
1282
+ if (!base)
1283
+ return NOOP_ELEMENT;
1284
+ const { element: element2, updates, mounts, unmounts } = base;
1285
+ for (const child of children) {
1286
+ collectChild(child, updates, mounts, unmounts);
1287
+ }
1288
+ return {
1289
+ dom: element2,
1290
+ update: normalizeUpdates(updates),
1291
+ mount: normalizeMount(mounts),
1292
+ unmount: normalizeMount(unmounts)
1293
+ };
1294
+ }
1295
+ function adoptDynamicElement(coordinate, attributes, children = [], ref) {
1296
+ const base = adoptBase(coordinate, attributes, ref);
1297
+ if (!base)
1298
+ return NOOP_ELEMENT;
1299
+ const { element: element2, updates, mounts, unmounts } = base;
1300
+ const kindergarten = new Kindergarten(element2);
1301
+ const significantChildren = getSignificantChildren(element2);
1302
+ let significantIndex = 0;
1303
+ const staticGroups = [];
1304
+ for (const child of children) {
1305
+ const group = kindergarten.newGroup();
1306
+ if (child === STATIC) {
1307
+ const domNode = significantChildren[significantIndex];
1308
+ if (domNode)
1309
+ group.children.add(domNode);
1310
+ significantIndex++;
1311
+ staticGroups.push(group);
1312
+ } else if ("_setGroup" in child) {
1313
+ child._setGroup(group);
1314
+ collectChild(child, updates, mounts, unmounts);
1315
+ } else {
1316
+ const domNode = significantChildren[significantIndex];
1317
+ if (domNode)
1318
+ group.children.add(domNode);
1319
+ collectChild(child, updates, mounts, unmounts);
1320
+ significantIndex++;
1321
+ }
1322
+ }
1323
+ const dynamicNodes = /* @__PURE__ */ new Set();
1324
+ for (const group of kindergarten.getGroups()) {
1325
+ if (!staticGroups.includes(group)) {
1326
+ for (const child of group.children) {
1327
+ dynamicNodes.add(child);
1328
+ }
1329
+ }
1330
+ }
1331
+ for (const group of staticGroups) {
1332
+ for (const child of group.children) {
1333
+ if (dynamicNodes.has(child)) {
1334
+ group.children.delete(child);
1335
+ }
1336
+ }
1337
+ }
1338
+ return {
1339
+ dom: element2,
1340
+ update: normalizeUpdates(updates),
1341
+ mount: normalizeMount(mounts),
1342
+ unmount: normalizeMount(unmounts)
1343
+ };
1344
+ }
1345
+ function getSignificantChildren(element2) {
1346
+ const nodes = element2.childNodes;
1347
+ if (nodes.length <= 1)
1348
+ return Array.from(nodes);
1349
+ const result = [];
1350
+ for (let j = 0; j < nodes.length; j++) {
1351
+ const node = nodes[j];
1352
+ if (node.nodeType !== Node.TEXT_NODE || (node.textContent || "").trim() !== "") {
1353
+ result.push(node);
1354
+ }
1355
+ }
1356
+ return result;
1357
+ }
1358
+ function hydrateConditional(condition, adoptExisting, createFallback) {
1359
+ const context = currentConstructionContext();
1360
+ const savedContext = saveContext();
1361
+ const currData = context.currData;
1362
+ const wasTrue = currData != null && condition(currData);
1363
+ let adopted;
1364
+ if (wasTrue) {
1365
+ adopted = adoptExisting();
1366
+ }
1367
+ let group;
1368
+ let created = wasTrue ? adopted : void 0;
1369
+ let visible = wasTrue;
1370
+ const update = (newData) => {
1371
+ if (!group)
1372
+ return;
1373
+ const result2 = condition(newData);
1374
+ if (!created && result2) {
1375
+ if (wasTrue)
1376
+ ;
1377
+ else if (createFallback) {
1378
+ restoreContext(savedContext, () => {
1379
+ created = wrapWithModifiedCheck(
1380
+ currentConstructionContext().currData,
1381
+ createFallback()
1382
+ );
1383
+ });
1384
+ }
1385
+ }
1386
+ if (result2 && !visible && created) {
1387
+ group.ensureNode(created.dom);
1388
+ created.mount();
1389
+ } else if (!result2 && visible && created) {
1390
+ group.removeNode(created.dom);
1391
+ created.unmount();
1392
+ }
1393
+ if (result2 && created) {
1394
+ created.update(newData);
1395
+ }
1396
+ visible = result2;
1397
+ };
1398
+ const result = {
1399
+ dom: wasTrue && adopted ? adopted.dom : void 0,
1400
+ update,
1401
+ mount: () => {
1402
+ if (created && visible)
1403
+ created.mount();
1404
+ },
1405
+ unmount: () => {
1406
+ if (created && visible)
1407
+ created.unmount();
1408
+ },
1409
+ _setGroup: (g) => {
1410
+ group = g;
1411
+ if (wasTrue && adopted?.dom) {
1412
+ group.children.add(adopted.dom);
1413
+ }
1414
+ if (created && visible && !wasTrue) {
1415
+ group.ensureNode(created.dom);
1416
+ }
1417
+ }
1418
+ };
1419
+ return result;
1420
+ }
1421
+ function hydrateForEach(accessor, trackBy, adoptItem, createItem) {
1422
+ const context = currentConstructionContext();
1423
+ const savedContext = saveContext();
1424
+ const parentContext = context;
1425
+ const initialItems = accessor(context.currData) || [];
1426
+ const adoptedItems = [];
1427
+ for (const item of initialItems) {
1428
+ const id = String(item[trackBy]);
1429
+ const itemDom = context.resolveCoordinate(id);
1430
+ const childContext = context.forItem(item, id);
1431
+ const adopted = withContext(CONSTRUCTION_CONTEXT_MARKER, childContext, () => {
1432
+ const elements = adoptItem();
1433
+ const updates = [];
1434
+ const mounts = [];
1435
+ const unmounts = [];
1436
+ for (const el of elements) {
1437
+ if (el.update !== noopUpdate)
1438
+ updates.push(el.update);
1439
+ if (el.mount !== noopMount)
1440
+ mounts.push(el.mount);
1441
+ if (el.unmount !== noopMount)
1442
+ unmounts.push(el.unmount);
1443
+ }
1444
+ return {
1445
+ dom: itemDom || elements[0]?.dom,
1446
+ update: normalizeUpdates(updates),
1447
+ mount: normalizeMount(mounts),
1448
+ unmount: normalizeMount(unmounts)
1449
+ };
1450
+ });
1451
+ adoptedItems.push(adopted);
1452
+ }
1453
+ let group;
1454
+ let lastItems = initialItems;
1455
+ let lastItemsList = new RandomAccessLinkedList(initialItems, trackBy);
1456
+ for (let i = 0; i < initialItems.length; i++) {
1457
+ const id = String(initialItems[i][trackBy]);
1458
+ const node = lastItemsList.get(id);
1459
+ if (node) {
1460
+ node.attach = adoptedItems[i];
1461
+ }
1462
+ }
1463
+ const mount = () => {
1464
+ lastItemsList.forEach((_value, elem) => {
1465
+ if (elem && elem.mount !== noopMount)
1466
+ elem.mount();
1467
+ });
1468
+ };
1469
+ const unmount = () => {
1470
+ lastItemsList.forEach((_value, elem) => {
1471
+ if (elem && elem.unmount !== noopMount)
1472
+ elem.unmount();
1473
+ });
1474
+ };
1475
+ const update = (newData) => {
1476
+ if (!group)
1477
+ return;
1478
+ const items = accessor(newData) || [];
1479
+ const isModified = items !== lastItems;
1480
+ lastItems = items;
1481
+ if (isModified) {
1482
+ const itemsList = new RandomAccessLinkedList(items, trackBy);
1483
+ const instructions = listCompare(
1484
+ lastItemsList,
1485
+ itemsList,
1486
+ (item, id) => {
1487
+ const childContext = parentContext.forItem(item, id);
1488
+ return restoreContext(
1489
+ savedContext,
1490
+ () => withContext(
1491
+ CONSTRUCTION_CONTEXT_MARKER,
1492
+ childContext,
1493
+ () => wrapWithModifiedCheck(
1494
+ currentConstructionContext().currData,
1495
+ createItem(item, id)
1496
+ )
1497
+ )
1498
+ );
1499
+ }
1500
+ );
1501
+ lastItemsList = itemsList;
1502
+ applyListChanges(group, instructions);
1503
+ itemsList.forEach((value, elem) => elem.update(value));
1504
+ }
1505
+ };
1506
+ const result = {
1507
+ dom: void 0,
1508
+ update,
1509
+ mount,
1510
+ unmount,
1511
+ _setGroup: (g) => {
1512
+ group = g;
1513
+ for (const adopted of adoptedItems) {
1514
+ if (adopted.dom) {
1515
+ group.children.add(adopted.dom);
1516
+ }
1517
+ }
1518
+ }
1519
+ };
1520
+ return result;
1521
+ }
1522
+ function childCompHydrate(compCreator, getProps, instanceCoordinate, ref) {
1523
+ const context = currentConstructionContext();
1524
+ const childContext = context.forInstance(instanceCoordinate);
1525
+ return withContext(CONSTRUCTION_CONTEXT_MARKER, childContext, () => {
1526
+ const childComp2 = compCreator(getProps(context.currData));
1527
+ const updates = [(t) => childComp2.update(getProps(t))];
1528
+ const mounts = [childComp2.mount];
1529
+ const unmounts = [childComp2.unmount];
1530
+ if (ref) {
1531
+ updates.push(ref.update);
1532
+ ref.set(childComp2);
1533
+ mounts.push(ref.mount);
1534
+ unmounts.push(ref.unmount);
1535
+ }
1536
+ return {
1537
+ dom: childComp2.element.dom,
1538
+ update: normalizeUpdates(updates),
1539
+ mount: normalizeMount(mounts),
1540
+ unmount: normalizeMount(unmounts)
1541
+ };
1542
+ });
1543
+ }
1544
+ function applyListChanges(group, instructions) {
1545
+ instructions.forEach((instruction) => {
1546
+ if (instruction.action === ITEM_ADDED) {
1547
+ group.ensureNode(instruction.elem.dom, instruction.pos);
1548
+ instruction.elem.mount();
1549
+ } else if (instruction.action === ITEM_REMOVED) {
1550
+ group.removeNodeAt(instruction.pos);
1551
+ instruction.elem.unmount();
1552
+ } else {
1553
+ group.moveNode(instruction.fromPos, instruction.pos);
1554
+ }
1555
+ });
1556
+ }
1018
1557
  export {
1019
1558
  BaseReferencesManager,
1020
1559
  ComponentCollectionRefImpl,
@@ -1027,10 +1566,15 @@ export {
1027
1566
  ManagedRefType,
1028
1567
  PrivateRefs,
1029
1568
  ReferencesManager,
1569
+ STATIC,
1030
1570
  VIEW_STATE_CHANGE_EVENT,
1031
1571
  WhenRole,
1572
+ adoptDynamicElement,
1573
+ adoptElement,
1574
+ adoptText,
1032
1575
  booleanAttribute,
1033
1576
  childComp,
1577
+ childCompHydrate,
1034
1578
  clearGlobalContextRegistry,
1035
1579
  conditional,
1036
1580
  createJayContext,
@@ -1044,6 +1588,8 @@ export {
1044
1588
  element,
1045
1589
  findContext,
1046
1590
  forEach,
1591
+ hydrateConditional,
1592
+ hydrateForEach,
1047
1593
  injectHeadLinks,
1048
1594
  isCondition,
1049
1595
  isForEach,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/runtime",
3
- "version": "0.13.0",
3
+ "version": "0.15.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.13.0",
27
- "@jay-framework/reactive": "^0.13.0"
26
+ "@jay-framework/list-compare": "^0.15.0",
27
+ "@jay-framework/reactive": "^0.15.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@jay-framework/dev-environment": "^0.13.0",
30
+ "@jay-framework/dev-environment": "^0.15.0",
31
31
  "@testing-library/jest-dom": "^6.2.0",
32
32
  "@types/jsdom": "^21.1.6",
33
33
  "@types/node": "^20.11.5",