streetui 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.
@@ -1,6 +1,7 @@
1
- import { S as StreetApp } from './compile-B0q07Hzq.cjs';
2
- export { V as VERSION } from './compile-B0q07Hzq.cjs';
3
- import { R as RenderHandle, H as HydrationDiagnostic } from './hydration-diagnostics-BE6xVWD1.cjs';
1
+ import { S as StreetApp } from './compile-DsNJm9IJ.cjs';
2
+ export { V as VERSION } from './compile-DsNJm9IJ.cjs';
3
+ import { R as RenderHandle, H as HydrationDiagnostic } from './hydration-diagnostics-OFIDPD15.cjs';
4
+ export * from 'streetui/diagnostics';
4
5
 
5
6
  /**
6
7
  * StreetUI Test Renderer.
package/dist/testing.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { S as StreetApp } from './compile-B0q07Hzq.js';
2
- export { V as VERSION } from './compile-B0q07Hzq.js';
3
- import { R as RenderHandle, H as HydrationDiagnostic } from './hydration-diagnostics-Bck5dMbz.js';
1
+ import { S as StreetApp } from './compile-DsNJm9IJ.js';
2
+ export { V as VERSION } from './compile-DsNJm9IJ.js';
3
+ import { R as RenderHandle, H as HydrationDiagnostic } from './hydration-diagnostics-BN1S-fbq.js';
4
+ export * from 'streetui/diagnostics';
4
5
 
5
6
  /**
6
7
  * StreetUI Test Renderer.
package/dist/testing.js CHANGED
@@ -692,6 +692,7 @@ function patchProp(dom, element, name, oldValue, newValue) {
692
692
 
693
693
  // ../renderer/src/events.ts
694
694
  function wireEvents(dom, graph, node, element, instance) {
695
+ if (node.events.length === 0) return;
695
696
  for (const eventDesc of node.events) {
696
697
  const handler = graph.getHandler(eventDesc.handlerKey);
697
698
  if (handler === void 0) continue;
@@ -821,6 +822,109 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
821
822
  reorderDom(ctx, parentDom, newInstances);
822
823
  return { instances: newInstances, removed };
823
824
  }
825
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
826
+ const oldByKey = /* @__PURE__ */ new Map();
827
+ for (const inst of oldInstances) {
828
+ oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
829
+ }
830
+ const newInstances = [];
831
+ const usedKeys = /* @__PURE__ */ new Set();
832
+ const built = [];
833
+ for (const entry of plan) {
834
+ const existing = oldByKey.get(entry.key);
835
+ if (existing !== void 0) {
836
+ usedKeys.add(entry.key);
837
+ const oldItem = existing.graphNode.getProp("_item");
838
+ if (!Object.is(oldItem, entry.item)) {
839
+ const newSig = entry.sig();
840
+ const oldSig = existing.graphNode.getProp("_sig");
841
+ if (!Object.is(oldSig, newSig)) {
842
+ const freshNode = entry.build();
843
+ built.push(freshNode);
844
+ patchExistingInstance(ctx, existing, freshNode);
845
+ reconcileItemChildren(ctx, existing, freshNode, mountFn);
846
+ existing.graphNode.setProp("_sig", newSig);
847
+ }
848
+ existing.graphNode.setProp("_item", entry.item);
849
+ }
850
+ newInstances.push(existing);
851
+ } else {
852
+ const freshNode = entry.build();
853
+ built.push(freshNode);
854
+ const inst = mountFn(freshNode, parentDom);
855
+ newInstances.push(inst);
856
+ }
857
+ }
858
+ const removed = [];
859
+ for (const inst of oldInstances) {
860
+ const key = inst.graphNode.key ?? inst.graphNode.id;
861
+ if (!usedKeys.has(key)) removed.push(inst);
862
+ }
863
+ for (const inst of removed) {
864
+ const parent = ctx.dom.parentNode(inst.domNode);
865
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
866
+ inst.dispose();
867
+ }
868
+ reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
869
+ return { instances: newInstances, removed, built };
870
+ }
871
+ function reorderDomMinimal(ctx, parentDom, oldInstances, newInstances) {
872
+ const n = newInstances.length;
873
+ if (n === 0) return;
874
+ const oldIndexOf = /* @__PURE__ */ new Map();
875
+ for (let i = 0; i < oldInstances.length; i++) oldIndexOf.set(oldInstances[i], i);
876
+ const source = new Array(n);
877
+ let moved = false;
878
+ let lastSeen = -1;
879
+ for (let i = 0; i < n; i++) {
880
+ const oi = oldIndexOf.get(newInstances[i]);
881
+ if (oi === void 0) {
882
+ source[i] = -1;
883
+ moved = true;
884
+ } else {
885
+ source[i] = oi;
886
+ if (oi < lastSeen) moved = true;
887
+ else lastSeen = oi;
888
+ }
889
+ }
890
+ if (!moved) return;
891
+ const keep = longestIncreasingSubsequence(source);
892
+ let refNode = null;
893
+ for (let i = n - 1; i >= 0; i--) {
894
+ const domNode = newInstances[i].domNode;
895
+ if (source[i] === -1 || !keep.has(i)) {
896
+ if (ctx.dom.nextSibling(domNode) !== refNode) {
897
+ ctx.dom.insertBefore(parentDom, domNode, refNode);
898
+ }
899
+ }
900
+ refNode = domNode;
901
+ }
902
+ }
903
+ function longestIncreasingSubsequence(source) {
904
+ const keep = /* @__PURE__ */ new Set();
905
+ const n = source.length;
906
+ const tails = [];
907
+ const prev = new Array(n).fill(-1);
908
+ for (let i = 0; i < n; i++) {
909
+ const v = source[i];
910
+ if (v < 0) continue;
911
+ let lo = 0;
912
+ let hi = tails.length;
913
+ while (lo < hi) {
914
+ const mid = lo + hi >> 1;
915
+ if (source[tails[mid]] < v) lo = mid + 1;
916
+ else hi = mid;
917
+ }
918
+ if (lo > 0) prev[i] = tails[lo - 1];
919
+ tails[lo] = i;
920
+ }
921
+ let idx = tails.length > 0 ? tails[tails.length - 1] : -1;
922
+ while (idx >= 0) {
923
+ keep.add(idx);
924
+ idx = prev[idx];
925
+ }
926
+ return keep;
927
+ }
824
928
  function reconcileItemChildren(ctx, itemInstance, newItemNode, mountFn) {
825
929
  const el = itemInstance.domNode;
826
930
  if (!ctx.dom.isElement(el)) return;
@@ -922,10 +1026,12 @@ function mountNode(ctx, graphNode, parentDom) {
922
1026
  const textNode = dom.createTextNode(text);
923
1027
  dom.appendChild(el2, textNode);
924
1028
  applyNodeProps(ctx, graphNode, el2);
925
- wireEvents(dom, graph, graphNode, el2, new NodeInstance(graphNode, el2));
926
1029
  const instance2 = new NodeInstance(graphNode, el2);
927
1030
  ctx.instances.set(graphNode.id, instance2);
928
- wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
1031
+ wireEvents(dom, graph, graphNode, el2, instance2);
1032
+ if (graphNode.stateRefs.length !== 0) {
1033
+ wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
1034
+ }
929
1035
  dom.appendChild(parentDom, el2);
930
1036
  return instance2;
931
1037
  }
@@ -939,7 +1045,9 @@ function mountNode(ctx, graphNode, parentDom) {
939
1045
  const instance2 = new NodeInstance(graphNode, el2);
940
1046
  ctx.instances.set(graphNode.id, instance2);
941
1047
  wireEvents(dom, graph, graphNode, el2, instance2);
942
- wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
1048
+ if (graphNode.stateRefs.length !== 0) {
1049
+ wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
1050
+ }
943
1051
  dom.appendChild(parentDom, el2);
944
1052
  return instance2;
945
1053
  }
@@ -955,7 +1063,9 @@ function mountNode(ctx, graphNode, parentDom) {
955
1063
  const instance2 = new NodeInstance(graphNode, el2);
956
1064
  ctx.instances.set(graphNode.id, instance2);
957
1065
  wireEvents(dom, graph, graphNode, el2, instance2);
958
- wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
1066
+ if (graphNode.stateRefs.length !== 0) {
1067
+ wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
1068
+ }
959
1069
  dom.appendChild(parentDom, el2);
960
1070
  return instance2;
961
1071
  }
@@ -1003,7 +1113,9 @@ function mountNode(ctx, graphNode, parentDom) {
1003
1113
  const instance2 = new NodeInstance(graphNode, el2);
1004
1114
  ctx.instances.set(graphNode.id, instance2);
1005
1115
  wireEvents(dom, graph, graphNode, el2, instance2);
1006
- wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
1116
+ if (graphNode.stateRefs.length !== 0) {
1117
+ wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
1118
+ }
1007
1119
  dom.appendChild(parentDom, el2);
1008
1120
  return instance2;
1009
1121
  }
@@ -1030,11 +1142,11 @@ function mountNode(ctx, graphNode, parentDom) {
1030
1142
  dom.setAttribute(el, "data-streetui-key", String(itemKey));
1031
1143
  }
1032
1144
  }
1033
- if (graphNode.type === "form") {
1034
- wireEvents(dom, graph, graphNode, el, new NodeInstance(graphNode, el));
1035
- }
1036
1145
  const instance = new NodeInstance(graphNode, el);
1037
1146
  ctx.instances.set(graphNode.id, instance);
1147
+ if (graphNode.type === "form") {
1148
+ wireEvents(dom, graph, graphNode, el, instance);
1149
+ }
1038
1150
  for (const child of graphNode.children) {
1039
1151
  const childInstance = mountNode(ctx, child, el);
1040
1152
  instance.addChild(childInstance);
@@ -1085,12 +1197,15 @@ function buttonUpdate(dom, el) {
1085
1197
  };
1086
1198
  }
1087
1199
  function applyNodeProps(ctx, graphNode, el) {
1088
- for (const [key, value] of Object.entries(graphNode.props)) {
1200
+ const props = graphNode.props;
1201
+ for (const key in props) {
1202
+ if (!Object.hasOwn(props, key)) continue;
1089
1203
  if (SKIP_PROP_KEYS.has(key)) continue;
1090
- applyProp(ctx.dom, el, key, value);
1204
+ applyProp(ctx.dom, el, key, props[key]);
1091
1205
  }
1092
1206
  }
1093
1207
  function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1208
+ if (graphNode.stateRefs.length === 0) return;
1094
1209
  for (const stateRef of graphNode.stateRefs) {
1095
1210
  const signalKey = `__signal__${stateRef.signalId}`;
1096
1211
  const maybeSig = ctx.graph.getHandler(signalKey);
@@ -1102,18 +1217,45 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1102
1217
  }
1103
1218
  }
1104
1219
  function wireReactiveList(ctx, graphNode, instance, el) {
1220
+ const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
1105
1221
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
1106
- if (build === void 0) return;
1222
+ if (plan === void 0 && build === void 0) return;
1107
1223
  for (const stateRef of graphNode.stateRefs) {
1108
1224
  if (stateRef.propKey !== "items") continue;
1109
1225
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
1110
1226
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
1111
1227
  const unsub = sig.subscribe((value) => {
1112
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1228
+ if (plan !== void 0) {
1229
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
1230
+ } else {
1231
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1232
+ }
1113
1233
  });
1114
1234
  instance.trackCleanup(unsub);
1115
1235
  }
1116
1236
  }
1237
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
1238
+ const oldInstances = [...listInstance.children];
1239
+ const result = reconcileChildrenByPlan(
1240
+ ctx,
1241
+ listEl,
1242
+ oldInstances,
1243
+ plan,
1244
+ (node, parent) => mountNode(ctx, node, parent)
1245
+ );
1246
+ listInstance.children.length = 0;
1247
+ for (const inst of result.instances) listInstance.children.push(inst);
1248
+ for (const removed of result.removed) {
1249
+ forgetInstance(ctx, removed);
1250
+ ctx.graph.detachNode(removed.graphNode);
1251
+ }
1252
+ const adopted = new Set(result.instances.map((i) => i.graphNode));
1253
+ for (const node of result.built ?? []) {
1254
+ if (!adopted.has(node)) ctx.graph.detachNode(node);
1255
+ }
1256
+ for (const child of [...listNode.children]) listNode.removeChild(child);
1257
+ for (const inst of result.instances) listNode.appendChild(inst.graphNode);
1258
+ }
1117
1259
  function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
1118
1260
  const oldInstances = [...listInstance.children];
1119
1261
  const result = reconcileChildren(
@@ -1186,14 +1328,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
1186
1328
  const instance = new NodeInstance(graphNode, domNode);
1187
1329
  ctx.instances.set(graphNode.id, instance);
1188
1330
  wireEvents(dom, graph, graphNode, domNode, instance);
1189
- wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
1331
+ if (graphNode.stateRefs.length !== 0) {
1332
+ wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
1333
+ }
1190
1334
  return instance;
1191
1335
  }
1192
1336
  case "heading": {
1193
1337
  const instance = new NodeInstance(graphNode, domNode);
1194
1338
  ctx.instances.set(graphNode.id, instance);
1195
1339
  wireEvents(dom, graph, graphNode, domNode, instance);
1196
- wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
1340
+ if (graphNode.stateRefs.length !== 0) {
1341
+ wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
1342
+ }
1197
1343
  return instance;
1198
1344
  }
1199
1345
  case "input": {
@@ -1202,14 +1348,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
1202
1348
  const value = graphNode.getProp("value");
1203
1349
  if (value !== void 0) dom.setProperty(domNode, "value", String(value));
1204
1350
  wireEvents(dom, graph, graphNode, domNode, instance);
1205
- wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
1351
+ if (graphNode.stateRefs.length !== 0) {
1352
+ wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
1353
+ }
1206
1354
  return instance;
1207
1355
  }
1208
1356
  case "button": {
1209
1357
  const instance = new NodeInstance(graphNode, domNode);
1210
1358
  ctx.instances.set(graphNode.id, instance);
1211
1359
  wireEvents(dom, graph, graphNode, domNode, instance);
1212
- wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
1360
+ if (graphNode.stateRefs.length !== 0) {
1361
+ wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
1362
+ }
1213
1363
  return instance;
1214
1364
  }
1215
1365
  case "image":
@@ -1245,10 +1395,11 @@ function hydrateChildren(ctx, parentGraphNode, parentInstance, parentDom, parent
1245
1395
  const expected = parentGraphNode.children;
1246
1396
  const actual = elementChildren(ctx, parentDom);
1247
1397
  let cursor = 0;
1398
+ const diag = ctx.hydrationDiagnostics !== void 0;
1248
1399
  for (let i = 0; i < expected.length; i++) {
1249
1400
  const childNode = expected[i];
1250
1401
  const want = expectedTag(ctx, childNode);
1251
- const childPath = `${parentPath} / ${childNode.type}[${i}]`;
1402
+ const childPath = diag ? `${parentPath} / ${childNode.type}[${i}]` : parentPath;
1252
1403
  const actualEl = actual[cursor];
1253
1404
  if (actualEl !== void 0 && ctx.dom.isElement(actualEl) && ctx.dom.tagName(actualEl) === want) {
1254
1405
  const inst = hydrateNode(ctx, childNode, actualEl, childPath);
@@ -1703,14 +1854,123 @@ function renderServerThenHydrate(build, options = {}) {
1703
1854
  };
1704
1855
  }
1705
1856
 
1857
+ // ../compiler/dist/diagnostics.js
1858
+ var TEXT_PROP_KEYS = /* @__PURE__ */ new Set(["text", "label", "value"]);
1859
+ function analyzeGraph(graph) {
1860
+ const nodes = /* @__PURE__ */ new Map();
1861
+ const summary = {
1862
+ totalNodes: 0,
1863
+ staticNodes: 0,
1864
+ staticSubtrees: 0,
1865
+ dynamicTextNodes: 0,
1866
+ dynamicAttrNodes: 0,
1867
+ eventNodes: 0,
1868
+ lists: 0,
1869
+ conditionals: 0
1870
+ };
1871
+ const visit = (node) => {
1872
+ let allChildrenStatic = true;
1873
+ for (const child of node.children) {
1874
+ const childSubtreeStatic = visit(child);
1875
+ if (!childSubtreeStatic) allChildrenStatic = false;
1876
+ }
1877
+ let hasDynamicText = false;
1878
+ let hasDynamicAttr = false;
1879
+ for (const ref of node.stateRefs) {
1880
+ if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;
1881
+ else hasDynamicAttr = true;
1882
+ }
1883
+ const hasEvents = node.events.length > 0;
1884
+ const isList = node.type === "reactive-list";
1885
+ const isConditional = node.type === "conditional";
1886
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;
1887
+ const isStaticSubtree = isStatic && allChildrenStatic;
1888
+ nodes.set(node.id, {
1889
+ isStatic,
1890
+ isStaticSubtree,
1891
+ hasDynamicText,
1892
+ hasDynamicAttr,
1893
+ hasEvents,
1894
+ isList,
1895
+ isConditional
1896
+ });
1897
+ summary.totalNodes += 1;
1898
+ if (isStatic) summary.staticNodes += 1;
1899
+ if (isStaticSubtree) summary.staticSubtrees += 1;
1900
+ if (hasDynamicText) summary.dynamicTextNodes += 1;
1901
+ if (hasDynamicAttr) summary.dynamicAttrNodes += 1;
1902
+ if (hasEvents) summary.eventNodes += 1;
1903
+ if (isList) summary.lists += 1;
1904
+ if (isConditional) summary.conditionals += 1;
1905
+ return isStaticSubtree;
1906
+ };
1907
+ visit(graph.root);
1908
+ return { nodes, summary };
1909
+ }
1910
+ function inspectCompilation(graph) {
1911
+ const analysis = analyzeGraph(graph);
1912
+ const nodes = [];
1913
+ const walk = (node, depth) => {
1914
+ const a = analysis.nodes.get(node.id);
1915
+ if (a !== void 0) {
1916
+ const classification = a.isStaticSubtree ? "static-subtree-root" : a.isStatic ? "static" : "dynamic";
1917
+ nodes.push({
1918
+ id: node.id,
1919
+ type: node.type,
1920
+ depth,
1921
+ classification,
1922
+ dynamicText: a.hasDynamicText,
1923
+ dynamicAttrs: a.hasDynamicAttr,
1924
+ events: node.events.map((e) => e.type),
1925
+ boundProps: node.stateRefs.map((r) => r.propKey),
1926
+ isList: a.isList,
1927
+ isConditional: a.isConditional,
1928
+ hydration: a.isStaticSubtree ? "adopt-static" : "verify-dynamic"
1929
+ });
1930
+ }
1931
+ for (const child of node.children) walk(child, depth + 1);
1932
+ };
1933
+ walk(graph.root, 0);
1934
+ const staticRatio = analysis.summary.totalNodes === 0 ? 0 : analysis.summary.staticNodes / analysis.summary.totalNodes;
1935
+ return {
1936
+ name: graph.name,
1937
+ version: graph.version,
1938
+ summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },
1939
+ nodes
1940
+ };
1941
+ }
1942
+ function formatInspection(inspection) {
1943
+ const s = inspection.summary;
1944
+ const lines = [];
1945
+ lines.push(`StreetUI compiler inspection \u2014 ${inspection.name} v${inspection.version}`);
1946
+ lines.push(
1947
+ ` nodes=${s.totalNodes} static=${s.staticNodes} staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} lists=${s.lists} conditionals=${s.conditionals} staticRatio=${(s.staticRatio * 100).toFixed(1)}%`
1948
+ );
1949
+ for (const n of inspection.nodes) {
1950
+ const flags = [];
1951
+ if (n.dynamicText) flags.push("text");
1952
+ if (n.dynamicAttrs) flags.push("attr:" + n.boundProps.join(","));
1953
+ if (n.events.length > 0) flags.push("on:" + n.events.join(","));
1954
+ if (n.isList) flags.push("list");
1955
+ if (n.isConditional) flags.push("cond");
1956
+ lines.push(
1957
+ ` ${" ".repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` + (flags.length > 0 ? ` {${flags.join(" ")}}` : "")
1958
+ );
1959
+ }
1960
+ return lines.join("\n");
1961
+ }
1962
+
1706
1963
  // src/version.ts
1707
- var VERSION = "1.0.0";
1964
+ var VERSION = "1.2.0";
1708
1965
  export {
1709
1966
  VERSION,
1967
+ analyzeGraph,
1710
1968
  findAllByRole,
1711
1969
  findByRole,
1712
1970
  findByText,
1713
1971
  flushUpdates,
1972
+ formatInspection,
1973
+ inspectCompilation,
1714
1974
  render,
1715
1975
  renderOnce,
1716
1976
  renderServerThenHydrate,