@streetui/renderer 1.0.0 → 1.2.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.js CHANGED
@@ -115,6 +115,7 @@ function patchProp(dom, element, name, oldValue, newValue) {
115
115
 
116
116
  // src/events.ts
117
117
  function wireEvents(dom, graph, node, element, instance) {
118
+ if (node.events.length === 0) return;
118
119
  for (const eventDesc of node.events) {
119
120
  const handler = graph.getHandler(eventDesc.handlerKey);
120
121
  if (handler === void 0) continue;
@@ -244,6 +245,109 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
244
245
  reorderDom(ctx, parentDom, newInstances);
245
246
  return { instances: newInstances, removed };
246
247
  }
248
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
249
+ const oldByKey = /* @__PURE__ */ new Map();
250
+ for (const inst of oldInstances) {
251
+ oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
252
+ }
253
+ const newInstances = [];
254
+ const usedKeys = /* @__PURE__ */ new Set();
255
+ const built = [];
256
+ for (const entry of plan) {
257
+ const existing = oldByKey.get(entry.key);
258
+ if (existing !== void 0) {
259
+ usedKeys.add(entry.key);
260
+ const oldItem = existing.graphNode.getProp("_item");
261
+ if (!Object.is(oldItem, entry.item)) {
262
+ const newSig = entry.sig();
263
+ const oldSig = existing.graphNode.getProp("_sig");
264
+ if (!Object.is(oldSig, newSig)) {
265
+ const freshNode = entry.build();
266
+ built.push(freshNode);
267
+ patchExistingInstance(ctx, existing, freshNode);
268
+ reconcileItemChildren(ctx, existing, freshNode, mountFn);
269
+ existing.graphNode.setProp("_sig", newSig);
270
+ }
271
+ existing.graphNode.setProp("_item", entry.item);
272
+ }
273
+ newInstances.push(existing);
274
+ } else {
275
+ const freshNode = entry.build();
276
+ built.push(freshNode);
277
+ const inst = mountFn(freshNode, parentDom);
278
+ newInstances.push(inst);
279
+ }
280
+ }
281
+ const removed = [];
282
+ for (const inst of oldInstances) {
283
+ const key = inst.graphNode.key ?? inst.graphNode.id;
284
+ if (!usedKeys.has(key)) removed.push(inst);
285
+ }
286
+ for (const inst of removed) {
287
+ const parent = ctx.dom.parentNode(inst.domNode);
288
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
289
+ inst.dispose();
290
+ }
291
+ reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
292
+ return { instances: newInstances, removed, built };
293
+ }
294
+ function reorderDomMinimal(ctx, parentDom, oldInstances, newInstances) {
295
+ const n = newInstances.length;
296
+ if (n === 0) return;
297
+ const oldIndexOf = /* @__PURE__ */ new Map();
298
+ for (let i = 0; i < oldInstances.length; i++) oldIndexOf.set(oldInstances[i], i);
299
+ const source = new Array(n);
300
+ let moved = false;
301
+ let lastSeen = -1;
302
+ for (let i = 0; i < n; i++) {
303
+ const oi = oldIndexOf.get(newInstances[i]);
304
+ if (oi === void 0) {
305
+ source[i] = -1;
306
+ moved = true;
307
+ } else {
308
+ source[i] = oi;
309
+ if (oi < lastSeen) moved = true;
310
+ else lastSeen = oi;
311
+ }
312
+ }
313
+ if (!moved) return;
314
+ const keep = longestIncreasingSubsequence(source);
315
+ let refNode = null;
316
+ for (let i = n - 1; i >= 0; i--) {
317
+ const domNode = newInstances[i].domNode;
318
+ if (source[i] === -1 || !keep.has(i)) {
319
+ if (ctx.dom.nextSibling(domNode) !== refNode) {
320
+ ctx.dom.insertBefore(parentDom, domNode, refNode);
321
+ }
322
+ }
323
+ refNode = domNode;
324
+ }
325
+ }
326
+ function longestIncreasingSubsequence(source) {
327
+ const keep = /* @__PURE__ */ new Set();
328
+ const n = source.length;
329
+ const tails = [];
330
+ const prev = new Array(n).fill(-1);
331
+ for (let i = 0; i < n; i++) {
332
+ const v = source[i];
333
+ if (v < 0) continue;
334
+ let lo = 0;
335
+ let hi = tails.length;
336
+ while (lo < hi) {
337
+ const mid = lo + hi >> 1;
338
+ if (source[tails[mid]] < v) lo = mid + 1;
339
+ else hi = mid;
340
+ }
341
+ if (lo > 0) prev[i] = tails[lo - 1];
342
+ tails[lo] = i;
343
+ }
344
+ let idx = tails.length > 0 ? tails[tails.length - 1] : -1;
345
+ while (idx >= 0) {
346
+ keep.add(idx);
347
+ idx = prev[idx];
348
+ }
349
+ return keep;
350
+ }
247
351
  function reconcileItemChildren(ctx, itemInstance, newItemNode, mountFn) {
248
352
  const el = itemInstance.domNode;
249
353
  if (!ctx.dom.isElement(el)) return;
@@ -345,10 +449,12 @@ function mountNode(ctx, graphNode, parentDom) {
345
449
  const textNode = dom.createTextNode(text);
346
450
  dom.appendChild(el2, textNode);
347
451
  applyNodeProps(ctx, graphNode, el2);
348
- wireEvents(dom, graph, graphNode, el2, new NodeInstance(graphNode, el2));
349
452
  const instance2 = new NodeInstance(graphNode, el2);
350
453
  ctx.instances.set(graphNode.id, instance2);
351
- wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
454
+ wireEvents(dom, graph, graphNode, el2, instance2);
455
+ if (graphNode.stateRefs.length !== 0) {
456
+ wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
457
+ }
352
458
  dom.appendChild(parentDom, el2);
353
459
  return instance2;
354
460
  }
@@ -362,7 +468,9 @@ function mountNode(ctx, graphNode, parentDom) {
362
468
  const instance2 = new NodeInstance(graphNode, el2);
363
469
  ctx.instances.set(graphNode.id, instance2);
364
470
  wireEvents(dom, graph, graphNode, el2, instance2);
365
- wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
471
+ if (graphNode.stateRefs.length !== 0) {
472
+ wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
473
+ }
366
474
  dom.appendChild(parentDom, el2);
367
475
  return instance2;
368
476
  }
@@ -378,7 +486,9 @@ function mountNode(ctx, graphNode, parentDom) {
378
486
  const instance2 = new NodeInstance(graphNode, el2);
379
487
  ctx.instances.set(graphNode.id, instance2);
380
488
  wireEvents(dom, graph, graphNode, el2, instance2);
381
- wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
489
+ if (graphNode.stateRefs.length !== 0) {
490
+ wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
491
+ }
382
492
  dom.appendChild(parentDom, el2);
383
493
  return instance2;
384
494
  }
@@ -426,7 +536,9 @@ function mountNode(ctx, graphNode, parentDom) {
426
536
  const instance2 = new NodeInstance(graphNode, el2);
427
537
  ctx.instances.set(graphNode.id, instance2);
428
538
  wireEvents(dom, graph, graphNode, el2, instance2);
429
- wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
539
+ if (graphNode.stateRefs.length !== 0) {
540
+ wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
541
+ }
430
542
  dom.appendChild(parentDom, el2);
431
543
  return instance2;
432
544
  }
@@ -453,11 +565,11 @@ function mountNode(ctx, graphNode, parentDom) {
453
565
  dom.setAttribute(el, "data-streetui-key", String(itemKey));
454
566
  }
455
567
  }
456
- if (graphNode.type === "form") {
457
- wireEvents(dom, graph, graphNode, el, new NodeInstance(graphNode, el));
458
- }
459
568
  const instance = new NodeInstance(graphNode, el);
460
569
  ctx.instances.set(graphNode.id, instance);
570
+ if (graphNode.type === "form") {
571
+ wireEvents(dom, graph, graphNode, el, instance);
572
+ }
461
573
  for (const child of graphNode.children) {
462
574
  const childInstance = mountNode(ctx, child, el);
463
575
  instance.addChild(childInstance);
@@ -508,12 +620,15 @@ function buttonUpdate(dom, el) {
508
620
  };
509
621
  }
510
622
  function applyNodeProps(ctx, graphNode, el) {
511
- for (const [key, value] of Object.entries(graphNode.props)) {
623
+ const props = graphNode.props;
624
+ for (const key in props) {
625
+ if (!Object.hasOwn(props, key)) continue;
512
626
  if (SKIP_PROP_KEYS.has(key)) continue;
513
- applyProp(ctx.dom, el, key, value);
627
+ applyProp(ctx.dom, el, key, props[key]);
514
628
  }
515
629
  }
516
630
  function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
631
+ if (graphNode.stateRefs.length === 0) return;
517
632
  for (const stateRef of graphNode.stateRefs) {
518
633
  const signalKey = `__signal__${stateRef.signalId}`;
519
634
  const maybeSig = ctx.graph.getHandler(signalKey);
@@ -525,18 +640,45 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
525
640
  }
526
641
  }
527
642
  function wireReactiveList(ctx, graphNode, instance, el) {
643
+ const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
528
644
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
529
- if (build === void 0) return;
645
+ if (plan === void 0 && build === void 0) return;
530
646
  for (const stateRef of graphNode.stateRefs) {
531
647
  if (stateRef.propKey !== "items") continue;
532
648
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
533
649
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
534
650
  const unsub = sig.subscribe((value) => {
535
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
651
+ if (plan !== void 0) {
652
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
653
+ } else {
654
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value));
655
+ }
536
656
  });
537
657
  instance.trackCleanup(unsub);
538
658
  }
539
659
  }
660
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
661
+ const oldInstances = [...listInstance.children];
662
+ const result = reconcileChildrenByPlan(
663
+ ctx,
664
+ listEl,
665
+ oldInstances,
666
+ plan,
667
+ (node, parent) => mountNode(ctx, node, parent)
668
+ );
669
+ listInstance.children.length = 0;
670
+ for (const inst of result.instances) listInstance.children.push(inst);
671
+ for (const removed of result.removed) {
672
+ forgetInstance(ctx, removed);
673
+ ctx.graph.detachNode(removed.graphNode);
674
+ }
675
+ const adopted = new Set(result.instances.map((i) => i.graphNode));
676
+ for (const node of result.built ?? []) {
677
+ if (!adopted.has(node)) ctx.graph.detachNode(node);
678
+ }
679
+ for (const child of [...listNode.children]) listNode.removeChild(child);
680
+ for (const inst of result.instances) listNode.appendChild(inst.graphNode);
681
+ }
540
682
  function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
541
683
  const oldInstances = [...listInstance.children];
542
684
  const result = reconcileChildren(
@@ -619,14 +761,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
619
761
  const instance = new NodeInstance(graphNode, domNode);
620
762
  ctx.instances.set(graphNode.id, instance);
621
763
  wireEvents(dom, graph, graphNode, domNode, instance);
622
- wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
764
+ if (graphNode.stateRefs.length !== 0) {
765
+ wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
766
+ }
623
767
  return instance;
624
768
  }
625
769
  case "heading": {
626
770
  const instance = new NodeInstance(graphNode, domNode);
627
771
  ctx.instances.set(graphNode.id, instance);
628
772
  wireEvents(dom, graph, graphNode, domNode, instance);
629
- wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
773
+ if (graphNode.stateRefs.length !== 0) {
774
+ wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
775
+ }
630
776
  return instance;
631
777
  }
632
778
  case "input": {
@@ -635,14 +781,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
635
781
  const value = graphNode.getProp("value");
636
782
  if (value !== void 0) dom.setProperty(domNode, "value", String(value));
637
783
  wireEvents(dom, graph, graphNode, domNode, instance);
638
- wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
784
+ if (graphNode.stateRefs.length !== 0) {
785
+ wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
786
+ }
639
787
  return instance;
640
788
  }
641
789
  case "button": {
642
790
  const instance = new NodeInstance(graphNode, domNode);
643
791
  ctx.instances.set(graphNode.id, instance);
644
792
  wireEvents(dom, graph, graphNode, domNode, instance);
645
- wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
793
+ if (graphNode.stateRefs.length !== 0) {
794
+ wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
795
+ }
646
796
  return instance;
647
797
  }
648
798
  case "image":
@@ -678,10 +828,11 @@ function hydrateChildren(ctx, parentGraphNode, parentInstance, parentDom, parent
678
828
  const expected = parentGraphNode.children;
679
829
  const actual = elementChildren(ctx, parentDom);
680
830
  let cursor = 0;
831
+ const diag = ctx.hydrationDiagnostics !== void 0;
681
832
  for (let i = 0; i < expected.length; i++) {
682
833
  const childNode = expected[i];
683
834
  const want = expectedTag(ctx, childNode);
684
- const childPath = `${parentPath} / ${childNode.type}[${i}]`;
835
+ const childPath = diag ? `${parentPath} / ${childNode.type}[${i}]` : parentPath;
685
836
  const actualEl = actual[cursor];
686
837
  if (actualEl !== void 0 && ctx.dom.isElement(actualEl) && ctx.dom.tagName(actualEl) === want) {
687
838
  const inst = hydrateNode(ctx, childNode, actualEl, childPath);
@@ -907,6 +1058,7 @@ export {
907
1058
  patchProp,
908
1059
  readState,
909
1060
  reconcileChildren,
1061
+ reconcileChildrenByPlan,
910
1062
  renderToString,
911
1063
  resolveTag,
912
1064
  serializeState,