@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.cjs CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  patchProp: () => patchProp,
42
42
  readState: () => readState,
43
43
  reconcileChildren: () => reconcileChildren,
44
+ reconcileChildrenByPlan: () => reconcileChildrenByPlan,
44
45
  renderToString: () => renderToString,
45
46
  resolveTag: () => resolveTag,
46
47
  serializeState: () => serializeState,
@@ -168,6 +169,7 @@ function patchProp(dom, element, name, oldValue, newValue) {
168
169
 
169
170
  // src/events.ts
170
171
  function wireEvents(dom, graph, node, element, instance) {
172
+ if (node.events.length === 0) return;
171
173
  for (const eventDesc of node.events) {
172
174
  const handler = graph.getHandler(eventDesc.handlerKey);
173
175
  if (handler === void 0) continue;
@@ -297,6 +299,109 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
297
299
  reorderDom(ctx, parentDom, newInstances);
298
300
  return { instances: newInstances, removed };
299
301
  }
302
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
303
+ const oldByKey = /* @__PURE__ */ new Map();
304
+ for (const inst of oldInstances) {
305
+ oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
306
+ }
307
+ const newInstances = [];
308
+ const usedKeys = /* @__PURE__ */ new Set();
309
+ const built = [];
310
+ for (const entry of plan) {
311
+ const existing = oldByKey.get(entry.key);
312
+ if (existing !== void 0) {
313
+ usedKeys.add(entry.key);
314
+ const oldItem = existing.graphNode.getProp("_item");
315
+ if (!Object.is(oldItem, entry.item)) {
316
+ const newSig = entry.sig();
317
+ const oldSig = existing.graphNode.getProp("_sig");
318
+ if (!Object.is(oldSig, newSig)) {
319
+ const freshNode = entry.build();
320
+ built.push(freshNode);
321
+ patchExistingInstance(ctx, existing, freshNode);
322
+ reconcileItemChildren(ctx, existing, freshNode, mountFn);
323
+ existing.graphNode.setProp("_sig", newSig);
324
+ }
325
+ existing.graphNode.setProp("_item", entry.item);
326
+ }
327
+ newInstances.push(existing);
328
+ } else {
329
+ const freshNode = entry.build();
330
+ built.push(freshNode);
331
+ const inst = mountFn(freshNode, parentDom);
332
+ newInstances.push(inst);
333
+ }
334
+ }
335
+ const removed = [];
336
+ for (const inst of oldInstances) {
337
+ const key = inst.graphNode.key ?? inst.graphNode.id;
338
+ if (!usedKeys.has(key)) removed.push(inst);
339
+ }
340
+ for (const inst of removed) {
341
+ const parent = ctx.dom.parentNode(inst.domNode);
342
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
343
+ inst.dispose();
344
+ }
345
+ reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
346
+ return { instances: newInstances, removed, built };
347
+ }
348
+ function reorderDomMinimal(ctx, parentDom, oldInstances, newInstances) {
349
+ const n = newInstances.length;
350
+ if (n === 0) return;
351
+ const oldIndexOf = /* @__PURE__ */ new Map();
352
+ for (let i = 0; i < oldInstances.length; i++) oldIndexOf.set(oldInstances[i], i);
353
+ const source = new Array(n);
354
+ let moved = false;
355
+ let lastSeen = -1;
356
+ for (let i = 0; i < n; i++) {
357
+ const oi = oldIndexOf.get(newInstances[i]);
358
+ if (oi === void 0) {
359
+ source[i] = -1;
360
+ moved = true;
361
+ } else {
362
+ source[i] = oi;
363
+ if (oi < lastSeen) moved = true;
364
+ else lastSeen = oi;
365
+ }
366
+ }
367
+ if (!moved) return;
368
+ const keep = longestIncreasingSubsequence(source);
369
+ let refNode = null;
370
+ for (let i = n - 1; i >= 0; i--) {
371
+ const domNode = newInstances[i].domNode;
372
+ if (source[i] === -1 || !keep.has(i)) {
373
+ if (ctx.dom.nextSibling(domNode) !== refNode) {
374
+ ctx.dom.insertBefore(parentDom, domNode, refNode);
375
+ }
376
+ }
377
+ refNode = domNode;
378
+ }
379
+ }
380
+ function longestIncreasingSubsequence(source) {
381
+ const keep = /* @__PURE__ */ new Set();
382
+ const n = source.length;
383
+ const tails = [];
384
+ const prev = new Array(n).fill(-1);
385
+ for (let i = 0; i < n; i++) {
386
+ const v = source[i];
387
+ if (v < 0) continue;
388
+ let lo = 0;
389
+ let hi = tails.length;
390
+ while (lo < hi) {
391
+ const mid = lo + hi >> 1;
392
+ if (source[tails[mid]] < v) lo = mid + 1;
393
+ else hi = mid;
394
+ }
395
+ if (lo > 0) prev[i] = tails[lo - 1];
396
+ tails[lo] = i;
397
+ }
398
+ let idx = tails.length > 0 ? tails[tails.length - 1] : -1;
399
+ while (idx >= 0) {
400
+ keep.add(idx);
401
+ idx = prev[idx];
402
+ }
403
+ return keep;
404
+ }
300
405
  function reconcileItemChildren(ctx, itemInstance, newItemNode, mountFn) {
301
406
  const el = itemInstance.domNode;
302
407
  if (!ctx.dom.isElement(el)) return;
@@ -398,10 +503,12 @@ function mountNode(ctx, graphNode, parentDom) {
398
503
  const textNode = dom.createTextNode(text);
399
504
  dom.appendChild(el2, textNode);
400
505
  applyNodeProps(ctx, graphNode, el2);
401
- wireEvents(dom, graph, graphNode, el2, new NodeInstance(graphNode, el2));
402
506
  const instance2 = new NodeInstance(graphNode, el2);
403
507
  ctx.instances.set(graphNode.id, instance2);
404
- wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
508
+ wireEvents(dom, graph, graphNode, el2, instance2);
509
+ if (graphNode.stateRefs.length !== 0) {
510
+ wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
511
+ }
405
512
  dom.appendChild(parentDom, el2);
406
513
  return instance2;
407
514
  }
@@ -415,7 +522,9 @@ function mountNode(ctx, graphNode, parentDom) {
415
522
  const instance2 = new NodeInstance(graphNode, el2);
416
523
  ctx.instances.set(graphNode.id, instance2);
417
524
  wireEvents(dom, graph, graphNode, el2, instance2);
418
- wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
525
+ if (graphNode.stateRefs.length !== 0) {
526
+ wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
527
+ }
419
528
  dom.appendChild(parentDom, el2);
420
529
  return instance2;
421
530
  }
@@ -431,7 +540,9 @@ function mountNode(ctx, graphNode, parentDom) {
431
540
  const instance2 = new NodeInstance(graphNode, el2);
432
541
  ctx.instances.set(graphNode.id, instance2);
433
542
  wireEvents(dom, graph, graphNode, el2, instance2);
434
- wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
543
+ if (graphNode.stateRefs.length !== 0) {
544
+ wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
545
+ }
435
546
  dom.appendChild(parentDom, el2);
436
547
  return instance2;
437
548
  }
@@ -479,7 +590,9 @@ function mountNode(ctx, graphNode, parentDom) {
479
590
  const instance2 = new NodeInstance(graphNode, el2);
480
591
  ctx.instances.set(graphNode.id, instance2);
481
592
  wireEvents(dom, graph, graphNode, el2, instance2);
482
- wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
593
+ if (graphNode.stateRefs.length !== 0) {
594
+ wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
595
+ }
483
596
  dom.appendChild(parentDom, el2);
484
597
  return instance2;
485
598
  }
@@ -506,11 +619,11 @@ function mountNode(ctx, graphNode, parentDom) {
506
619
  dom.setAttribute(el, "data-streetui-key", String(itemKey));
507
620
  }
508
621
  }
509
- if (graphNode.type === "form") {
510
- wireEvents(dom, graph, graphNode, el, new NodeInstance(graphNode, el));
511
- }
512
622
  const instance = new NodeInstance(graphNode, el);
513
623
  ctx.instances.set(graphNode.id, instance);
624
+ if (graphNode.type === "form") {
625
+ wireEvents(dom, graph, graphNode, el, instance);
626
+ }
514
627
  for (const child of graphNode.children) {
515
628
  const childInstance = mountNode(ctx, child, el);
516
629
  instance.addChild(childInstance);
@@ -561,12 +674,15 @@ function buttonUpdate(dom, el) {
561
674
  };
562
675
  }
563
676
  function applyNodeProps(ctx, graphNode, el) {
564
- for (const [key, value] of Object.entries(graphNode.props)) {
677
+ const props = graphNode.props;
678
+ for (const key in props) {
679
+ if (!Object.hasOwn(props, key)) continue;
565
680
  if (SKIP_PROP_KEYS.has(key)) continue;
566
- applyProp(ctx.dom, el, key, value);
681
+ applyProp(ctx.dom, el, key, props[key]);
567
682
  }
568
683
  }
569
684
  function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
685
+ if (graphNode.stateRefs.length === 0) return;
570
686
  for (const stateRef of graphNode.stateRefs) {
571
687
  const signalKey = `__signal__${stateRef.signalId}`;
572
688
  const maybeSig = ctx.graph.getHandler(signalKey);
@@ -578,18 +694,45 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
578
694
  }
579
695
  }
580
696
  function wireReactiveList(ctx, graphNode, instance, el) {
697
+ const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
581
698
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
582
- if (build === void 0) return;
699
+ if (plan === void 0 && build === void 0) return;
583
700
  for (const stateRef of graphNode.stateRefs) {
584
701
  if (stateRef.propKey !== "items") continue;
585
702
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
586
703
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
587
704
  const unsub = sig.subscribe((value) => {
588
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
705
+ if (plan !== void 0) {
706
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
707
+ } else {
708
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value));
709
+ }
589
710
  });
590
711
  instance.trackCleanup(unsub);
591
712
  }
592
713
  }
714
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
715
+ const oldInstances = [...listInstance.children];
716
+ const result = reconcileChildrenByPlan(
717
+ ctx,
718
+ listEl,
719
+ oldInstances,
720
+ plan,
721
+ (node, parent) => mountNode(ctx, node, parent)
722
+ );
723
+ listInstance.children.length = 0;
724
+ for (const inst of result.instances) listInstance.children.push(inst);
725
+ for (const removed of result.removed) {
726
+ forgetInstance(ctx, removed);
727
+ ctx.graph.detachNode(removed.graphNode);
728
+ }
729
+ const adopted = new Set(result.instances.map((i) => i.graphNode));
730
+ for (const node of result.built ?? []) {
731
+ if (!adopted.has(node)) ctx.graph.detachNode(node);
732
+ }
733
+ for (const child of [...listNode.children]) listNode.removeChild(child);
734
+ for (const inst of result.instances) listNode.appendChild(inst.graphNode);
735
+ }
593
736
  function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
594
737
  const oldInstances = [...listInstance.children];
595
738
  const result = reconcileChildren(
@@ -672,14 +815,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
672
815
  const instance = new NodeInstance(graphNode, domNode);
673
816
  ctx.instances.set(graphNode.id, instance);
674
817
  wireEvents(dom, graph, graphNode, domNode, instance);
675
- wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
818
+ if (graphNode.stateRefs.length !== 0) {
819
+ wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
820
+ }
676
821
  return instance;
677
822
  }
678
823
  case "heading": {
679
824
  const instance = new NodeInstance(graphNode, domNode);
680
825
  ctx.instances.set(graphNode.id, instance);
681
826
  wireEvents(dom, graph, graphNode, domNode, instance);
682
- wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
827
+ if (graphNode.stateRefs.length !== 0) {
828
+ wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
829
+ }
683
830
  return instance;
684
831
  }
685
832
  case "input": {
@@ -688,14 +835,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
688
835
  const value = graphNode.getProp("value");
689
836
  if (value !== void 0) dom.setProperty(domNode, "value", String(value));
690
837
  wireEvents(dom, graph, graphNode, domNode, instance);
691
- wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
838
+ if (graphNode.stateRefs.length !== 0) {
839
+ wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
840
+ }
692
841
  return instance;
693
842
  }
694
843
  case "button": {
695
844
  const instance = new NodeInstance(graphNode, domNode);
696
845
  ctx.instances.set(graphNode.id, instance);
697
846
  wireEvents(dom, graph, graphNode, domNode, instance);
698
- wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
847
+ if (graphNode.stateRefs.length !== 0) {
848
+ wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
849
+ }
699
850
  return instance;
700
851
  }
701
852
  case "image":
@@ -731,10 +882,11 @@ function hydrateChildren(ctx, parentGraphNode, parentInstance, parentDom, parent
731
882
  const expected = parentGraphNode.children;
732
883
  const actual = elementChildren(ctx, parentDom);
733
884
  let cursor = 0;
885
+ const diag = ctx.hydrationDiagnostics !== void 0;
734
886
  for (let i = 0; i < expected.length; i++) {
735
887
  const childNode = expected[i];
736
888
  const want = expectedTag(ctx, childNode);
737
- const childPath = `${parentPath} / ${childNode.type}[${i}]`;
889
+ const childPath = diag ? `${parentPath} / ${childNode.type}[${i}]` : parentPath;
738
890
  const actualEl = actual[cursor];
739
891
  if (actualEl !== void 0 && ctx.dom.isElement(actualEl) && ctx.dom.tagName(actualEl) === want) {
740
892
  const inst = hydrateNode(ctx, childNode, actualEl, childPath);
@@ -961,6 +1113,7 @@ function renderToString(compiled, options = {}) {
961
1113
  patchProp,
962
1114
  readState,
963
1115
  reconcileChildren,
1116
+ reconcileChildrenByPlan,
964
1117
  renderToString,
965
1118
  resolveTag,
966
1119
  serializeState,