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.
package/dist/testing.cjs CHANGED
@@ -21,10 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var testing_exports = {};
22
22
  __export(testing_exports, {
23
23
  VERSION: () => VERSION,
24
+ analyzeGraph: () => analyzeGraph,
24
25
  findAllByRole: () => findAllByRole,
25
26
  findByRole: () => findByRole,
26
27
  findByText: () => findByText,
27
28
  flushUpdates: () => flushUpdates,
29
+ formatInspection: () => formatInspection,
30
+ inspectCompilation: () => inspectCompilation,
28
31
  render: () => render,
29
32
  renderOnce: () => renderOnce,
30
33
  renderServerThenHydrate: () => renderServerThenHydrate,
@@ -726,6 +729,7 @@ function patchProp(dom, element, name, oldValue, newValue) {
726
729
 
727
730
  // ../renderer/src/events.ts
728
731
  function wireEvents(dom, graph, node, element, instance) {
732
+ if (node.events.length === 0) return;
729
733
  for (const eventDesc of node.events) {
730
734
  const handler = graph.getHandler(eventDesc.handlerKey);
731
735
  if (handler === void 0) continue;
@@ -855,6 +859,109 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
855
859
  reorderDom(ctx, parentDom, newInstances);
856
860
  return { instances: newInstances, removed };
857
861
  }
862
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
863
+ const oldByKey = /* @__PURE__ */ new Map();
864
+ for (const inst of oldInstances) {
865
+ oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
866
+ }
867
+ const newInstances = [];
868
+ const usedKeys = /* @__PURE__ */ new Set();
869
+ const built = [];
870
+ for (const entry of plan) {
871
+ const existing = oldByKey.get(entry.key);
872
+ if (existing !== void 0) {
873
+ usedKeys.add(entry.key);
874
+ const oldItem = existing.graphNode.getProp("_item");
875
+ if (!Object.is(oldItem, entry.item)) {
876
+ const newSig = entry.sig();
877
+ const oldSig = existing.graphNode.getProp("_sig");
878
+ if (!Object.is(oldSig, newSig)) {
879
+ const freshNode = entry.build();
880
+ built.push(freshNode);
881
+ patchExistingInstance(ctx, existing, freshNode);
882
+ reconcileItemChildren(ctx, existing, freshNode, mountFn);
883
+ existing.graphNode.setProp("_sig", newSig);
884
+ }
885
+ existing.graphNode.setProp("_item", entry.item);
886
+ }
887
+ newInstances.push(existing);
888
+ } else {
889
+ const freshNode = entry.build();
890
+ built.push(freshNode);
891
+ const inst = mountFn(freshNode, parentDom);
892
+ newInstances.push(inst);
893
+ }
894
+ }
895
+ const removed = [];
896
+ for (const inst of oldInstances) {
897
+ const key = inst.graphNode.key ?? inst.graphNode.id;
898
+ if (!usedKeys.has(key)) removed.push(inst);
899
+ }
900
+ for (const inst of removed) {
901
+ const parent = ctx.dom.parentNode(inst.domNode);
902
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
903
+ inst.dispose();
904
+ }
905
+ reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
906
+ return { instances: newInstances, removed, built };
907
+ }
908
+ function reorderDomMinimal(ctx, parentDom, oldInstances, newInstances) {
909
+ const n = newInstances.length;
910
+ if (n === 0) return;
911
+ const oldIndexOf = /* @__PURE__ */ new Map();
912
+ for (let i = 0; i < oldInstances.length; i++) oldIndexOf.set(oldInstances[i], i);
913
+ const source = new Array(n);
914
+ let moved = false;
915
+ let lastSeen = -1;
916
+ for (let i = 0; i < n; i++) {
917
+ const oi = oldIndexOf.get(newInstances[i]);
918
+ if (oi === void 0) {
919
+ source[i] = -1;
920
+ moved = true;
921
+ } else {
922
+ source[i] = oi;
923
+ if (oi < lastSeen) moved = true;
924
+ else lastSeen = oi;
925
+ }
926
+ }
927
+ if (!moved) return;
928
+ const keep = longestIncreasingSubsequence(source);
929
+ let refNode = null;
930
+ for (let i = n - 1; i >= 0; i--) {
931
+ const domNode = newInstances[i].domNode;
932
+ if (source[i] === -1 || !keep.has(i)) {
933
+ if (ctx.dom.nextSibling(domNode) !== refNode) {
934
+ ctx.dom.insertBefore(parentDom, domNode, refNode);
935
+ }
936
+ }
937
+ refNode = domNode;
938
+ }
939
+ }
940
+ function longestIncreasingSubsequence(source) {
941
+ const keep = /* @__PURE__ */ new Set();
942
+ const n = source.length;
943
+ const tails = [];
944
+ const prev = new Array(n).fill(-1);
945
+ for (let i = 0; i < n; i++) {
946
+ const v = source[i];
947
+ if (v < 0) continue;
948
+ let lo = 0;
949
+ let hi = tails.length;
950
+ while (lo < hi) {
951
+ const mid = lo + hi >> 1;
952
+ if (source[tails[mid]] < v) lo = mid + 1;
953
+ else hi = mid;
954
+ }
955
+ if (lo > 0) prev[i] = tails[lo - 1];
956
+ tails[lo] = i;
957
+ }
958
+ let idx = tails.length > 0 ? tails[tails.length - 1] : -1;
959
+ while (idx >= 0) {
960
+ keep.add(idx);
961
+ idx = prev[idx];
962
+ }
963
+ return keep;
964
+ }
858
965
  function reconcileItemChildren(ctx, itemInstance, newItemNode, mountFn) {
859
966
  const el = itemInstance.domNode;
860
967
  if (!ctx.dom.isElement(el)) return;
@@ -956,10 +1063,12 @@ function mountNode(ctx, graphNode, parentDom) {
956
1063
  const textNode = dom.createTextNode(text);
957
1064
  dom.appendChild(el2, textNode);
958
1065
  applyNodeProps(ctx, graphNode, el2);
959
- wireEvents(dom, graph, graphNode, el2, new NodeInstance(graphNode, el2));
960
1066
  const instance2 = new NodeInstance(graphNode, el2);
961
1067
  ctx.instances.set(graphNode.id, instance2);
962
- wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
1068
+ wireEvents(dom, graph, graphNode, el2, instance2);
1069
+ if (graphNode.stateRefs.length !== 0) {
1070
+ wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
1071
+ }
963
1072
  dom.appendChild(parentDom, el2);
964
1073
  return instance2;
965
1074
  }
@@ -973,7 +1082,9 @@ function mountNode(ctx, graphNode, parentDom) {
973
1082
  const instance2 = new NodeInstance(graphNode, el2);
974
1083
  ctx.instances.set(graphNode.id, instance2);
975
1084
  wireEvents(dom, graph, graphNode, el2, instance2);
976
- wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
1085
+ if (graphNode.stateRefs.length !== 0) {
1086
+ wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
1087
+ }
977
1088
  dom.appendChild(parentDom, el2);
978
1089
  return instance2;
979
1090
  }
@@ -989,7 +1100,9 @@ function mountNode(ctx, graphNode, parentDom) {
989
1100
  const instance2 = new NodeInstance(graphNode, el2);
990
1101
  ctx.instances.set(graphNode.id, instance2);
991
1102
  wireEvents(dom, graph, graphNode, el2, instance2);
992
- wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
1103
+ if (graphNode.stateRefs.length !== 0) {
1104
+ wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
1105
+ }
993
1106
  dom.appendChild(parentDom, el2);
994
1107
  return instance2;
995
1108
  }
@@ -1037,7 +1150,9 @@ function mountNode(ctx, graphNode, parentDom) {
1037
1150
  const instance2 = new NodeInstance(graphNode, el2);
1038
1151
  ctx.instances.set(graphNode.id, instance2);
1039
1152
  wireEvents(dom, graph, graphNode, el2, instance2);
1040
- wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
1153
+ if (graphNode.stateRefs.length !== 0) {
1154
+ wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
1155
+ }
1041
1156
  dom.appendChild(parentDom, el2);
1042
1157
  return instance2;
1043
1158
  }
@@ -1064,11 +1179,11 @@ function mountNode(ctx, graphNode, parentDom) {
1064
1179
  dom.setAttribute(el, "data-streetui-key", String(itemKey));
1065
1180
  }
1066
1181
  }
1067
- if (graphNode.type === "form") {
1068
- wireEvents(dom, graph, graphNode, el, new NodeInstance(graphNode, el));
1069
- }
1070
1182
  const instance = new NodeInstance(graphNode, el);
1071
1183
  ctx.instances.set(graphNode.id, instance);
1184
+ if (graphNode.type === "form") {
1185
+ wireEvents(dom, graph, graphNode, el, instance);
1186
+ }
1072
1187
  for (const child of graphNode.children) {
1073
1188
  const childInstance = mountNode(ctx, child, el);
1074
1189
  instance.addChild(childInstance);
@@ -1119,12 +1234,15 @@ function buttonUpdate(dom, el) {
1119
1234
  };
1120
1235
  }
1121
1236
  function applyNodeProps(ctx, graphNode, el) {
1122
- for (const [key, value] of Object.entries(graphNode.props)) {
1237
+ const props = graphNode.props;
1238
+ for (const key in props) {
1239
+ if (!Object.hasOwn(props, key)) continue;
1123
1240
  if (SKIP_PROP_KEYS.has(key)) continue;
1124
- applyProp(ctx.dom, el, key, value);
1241
+ applyProp(ctx.dom, el, key, props[key]);
1125
1242
  }
1126
1243
  }
1127
1244
  function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1245
+ if (graphNode.stateRefs.length === 0) return;
1128
1246
  for (const stateRef of graphNode.stateRefs) {
1129
1247
  const signalKey = `__signal__${stateRef.signalId}`;
1130
1248
  const maybeSig = ctx.graph.getHandler(signalKey);
@@ -1136,18 +1254,45 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1136
1254
  }
1137
1255
  }
1138
1256
  function wireReactiveList(ctx, graphNode, instance, el) {
1257
+ const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
1139
1258
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
1140
- if (build === void 0) return;
1259
+ if (plan === void 0 && build === void 0) return;
1141
1260
  for (const stateRef of graphNode.stateRefs) {
1142
1261
  if (stateRef.propKey !== "items") continue;
1143
1262
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
1144
1263
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
1145
1264
  const unsub = sig.subscribe((value) => {
1146
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1265
+ if (plan !== void 0) {
1266
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
1267
+ } else {
1268
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1269
+ }
1147
1270
  });
1148
1271
  instance.trackCleanup(unsub);
1149
1272
  }
1150
1273
  }
1274
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
1275
+ const oldInstances = [...listInstance.children];
1276
+ const result = reconcileChildrenByPlan(
1277
+ ctx,
1278
+ listEl,
1279
+ oldInstances,
1280
+ plan,
1281
+ (node, parent) => mountNode(ctx, node, parent)
1282
+ );
1283
+ listInstance.children.length = 0;
1284
+ for (const inst of result.instances) listInstance.children.push(inst);
1285
+ for (const removed of result.removed) {
1286
+ forgetInstance(ctx, removed);
1287
+ ctx.graph.detachNode(removed.graphNode);
1288
+ }
1289
+ const adopted = new Set(result.instances.map((i) => i.graphNode));
1290
+ for (const node of result.built ?? []) {
1291
+ if (!adopted.has(node)) ctx.graph.detachNode(node);
1292
+ }
1293
+ for (const child of [...listNode.children]) listNode.removeChild(child);
1294
+ for (const inst of result.instances) listNode.appendChild(inst.graphNode);
1295
+ }
1151
1296
  function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
1152
1297
  const oldInstances = [...listInstance.children];
1153
1298
  const result = reconcileChildren(
@@ -1220,14 +1365,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
1220
1365
  const instance = new NodeInstance(graphNode, domNode);
1221
1366
  ctx.instances.set(graphNode.id, instance);
1222
1367
  wireEvents(dom, graph, graphNode, domNode, instance);
1223
- wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
1368
+ if (graphNode.stateRefs.length !== 0) {
1369
+ wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
1370
+ }
1224
1371
  return instance;
1225
1372
  }
1226
1373
  case "heading": {
1227
1374
  const instance = new NodeInstance(graphNode, domNode);
1228
1375
  ctx.instances.set(graphNode.id, instance);
1229
1376
  wireEvents(dom, graph, graphNode, domNode, instance);
1230
- wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
1377
+ if (graphNode.stateRefs.length !== 0) {
1378
+ wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
1379
+ }
1231
1380
  return instance;
1232
1381
  }
1233
1382
  case "input": {
@@ -1236,14 +1385,18 @@ function hydrateNode(ctx, graphNode, domNode, path) {
1236
1385
  const value = graphNode.getProp("value");
1237
1386
  if (value !== void 0) dom.setProperty(domNode, "value", String(value));
1238
1387
  wireEvents(dom, graph, graphNode, domNode, instance);
1239
- wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
1388
+ if (graphNode.stateRefs.length !== 0) {
1389
+ wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
1390
+ }
1240
1391
  return instance;
1241
1392
  }
1242
1393
  case "button": {
1243
1394
  const instance = new NodeInstance(graphNode, domNode);
1244
1395
  ctx.instances.set(graphNode.id, instance);
1245
1396
  wireEvents(dom, graph, graphNode, domNode, instance);
1246
- wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
1397
+ if (graphNode.stateRefs.length !== 0) {
1398
+ wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
1399
+ }
1247
1400
  return instance;
1248
1401
  }
1249
1402
  case "image":
@@ -1279,10 +1432,11 @@ function hydrateChildren(ctx, parentGraphNode, parentInstance, parentDom, parent
1279
1432
  const expected = parentGraphNode.children;
1280
1433
  const actual = elementChildren(ctx, parentDom);
1281
1434
  let cursor = 0;
1435
+ const diag = ctx.hydrationDiagnostics !== void 0;
1282
1436
  for (let i = 0; i < expected.length; i++) {
1283
1437
  const childNode = expected[i];
1284
1438
  const want = expectedTag(ctx, childNode);
1285
- const childPath = `${parentPath} / ${childNode.type}[${i}]`;
1439
+ const childPath = diag ? `${parentPath} / ${childNode.type}[${i}]` : parentPath;
1286
1440
  const actualEl = actual[cursor];
1287
1441
  if (actualEl !== void 0 && ctx.dom.isElement(actualEl) && ctx.dom.tagName(actualEl) === want) {
1288
1442
  const inst = hydrateNode(ctx, childNode, actualEl, childPath);
@@ -1737,15 +1891,124 @@ function renderServerThenHydrate(build, options = {}) {
1737
1891
  };
1738
1892
  }
1739
1893
 
1894
+ // ../compiler/dist/diagnostics.js
1895
+ var TEXT_PROP_KEYS = /* @__PURE__ */ new Set(["text", "label", "value"]);
1896
+ function analyzeGraph(graph) {
1897
+ const nodes = /* @__PURE__ */ new Map();
1898
+ const summary = {
1899
+ totalNodes: 0,
1900
+ staticNodes: 0,
1901
+ staticSubtrees: 0,
1902
+ dynamicTextNodes: 0,
1903
+ dynamicAttrNodes: 0,
1904
+ eventNodes: 0,
1905
+ lists: 0,
1906
+ conditionals: 0
1907
+ };
1908
+ const visit = (node) => {
1909
+ let allChildrenStatic = true;
1910
+ for (const child of node.children) {
1911
+ const childSubtreeStatic = visit(child);
1912
+ if (!childSubtreeStatic) allChildrenStatic = false;
1913
+ }
1914
+ let hasDynamicText = false;
1915
+ let hasDynamicAttr = false;
1916
+ for (const ref of node.stateRefs) {
1917
+ if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;
1918
+ else hasDynamicAttr = true;
1919
+ }
1920
+ const hasEvents = node.events.length > 0;
1921
+ const isList = node.type === "reactive-list";
1922
+ const isConditional = node.type === "conditional";
1923
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;
1924
+ const isStaticSubtree = isStatic && allChildrenStatic;
1925
+ nodes.set(node.id, {
1926
+ isStatic,
1927
+ isStaticSubtree,
1928
+ hasDynamicText,
1929
+ hasDynamicAttr,
1930
+ hasEvents,
1931
+ isList,
1932
+ isConditional
1933
+ });
1934
+ summary.totalNodes += 1;
1935
+ if (isStatic) summary.staticNodes += 1;
1936
+ if (isStaticSubtree) summary.staticSubtrees += 1;
1937
+ if (hasDynamicText) summary.dynamicTextNodes += 1;
1938
+ if (hasDynamicAttr) summary.dynamicAttrNodes += 1;
1939
+ if (hasEvents) summary.eventNodes += 1;
1940
+ if (isList) summary.lists += 1;
1941
+ if (isConditional) summary.conditionals += 1;
1942
+ return isStaticSubtree;
1943
+ };
1944
+ visit(graph.root);
1945
+ return { nodes, summary };
1946
+ }
1947
+ function inspectCompilation(graph) {
1948
+ const analysis = analyzeGraph(graph);
1949
+ const nodes = [];
1950
+ const walk = (node, depth) => {
1951
+ const a = analysis.nodes.get(node.id);
1952
+ if (a !== void 0) {
1953
+ const classification = a.isStaticSubtree ? "static-subtree-root" : a.isStatic ? "static" : "dynamic";
1954
+ nodes.push({
1955
+ id: node.id,
1956
+ type: node.type,
1957
+ depth,
1958
+ classification,
1959
+ dynamicText: a.hasDynamicText,
1960
+ dynamicAttrs: a.hasDynamicAttr,
1961
+ events: node.events.map((e) => e.type),
1962
+ boundProps: node.stateRefs.map((r) => r.propKey),
1963
+ isList: a.isList,
1964
+ isConditional: a.isConditional,
1965
+ hydration: a.isStaticSubtree ? "adopt-static" : "verify-dynamic"
1966
+ });
1967
+ }
1968
+ for (const child of node.children) walk(child, depth + 1);
1969
+ };
1970
+ walk(graph.root, 0);
1971
+ const staticRatio = analysis.summary.totalNodes === 0 ? 0 : analysis.summary.staticNodes / analysis.summary.totalNodes;
1972
+ return {
1973
+ name: graph.name,
1974
+ version: graph.version,
1975
+ summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },
1976
+ nodes
1977
+ };
1978
+ }
1979
+ function formatInspection(inspection) {
1980
+ const s = inspection.summary;
1981
+ const lines = [];
1982
+ lines.push(`StreetUI compiler inspection \u2014 ${inspection.name} v${inspection.version}`);
1983
+ lines.push(
1984
+ ` 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)}%`
1985
+ );
1986
+ for (const n of inspection.nodes) {
1987
+ const flags = [];
1988
+ if (n.dynamicText) flags.push("text");
1989
+ if (n.dynamicAttrs) flags.push("attr:" + n.boundProps.join(","));
1990
+ if (n.events.length > 0) flags.push("on:" + n.events.join(","));
1991
+ if (n.isList) flags.push("list");
1992
+ if (n.isConditional) flags.push("cond");
1993
+ lines.push(
1994
+ ` ${" ".repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` + (flags.length > 0 ? ` {${flags.join(" ")}}` : "")
1995
+ );
1996
+ }
1997
+ return lines.join("\n");
1998
+ }
1999
+
1740
2000
  // src/version.ts
1741
- var VERSION = "1.0.0";
2001
+ var VERSION = "1.2.0";
1742
2002
  // Annotate the CommonJS export names for ESM import in node:
1743
2003
  0 && (module.exports = {
1744
2004
  VERSION,
2005
+ analyzeGraph,
1745
2006
  findAllByRole,
1746
2007
  findByRole,
1747
2008
  findByText,
1748
2009
  flushUpdates,
2010
+ formatInspection,
2011
+ inspectCompilation,
1749
2012
  render,
1750
2013
  renderOnce,
1751
2014
  renderServerThenHydrate,