@lytjs/devtools 6.0.0 → 6.5.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.mjs CHANGED
@@ -130,16 +130,14 @@ function setStoreState(storeId, path, value) {
130
130
  const store = storeRegistry.get(storeId);
131
131
  if (!store) return false;
132
132
  const keys = path.split(".");
133
- let current = store.$state || store;
133
+ let current = store;
134
134
  for (let i = 0; i < keys.length - 1; i++) {
135
135
  const key = keys[i];
136
- if (!isObject(current[key])) {
137
- return false;
138
- }
136
+ if (!isObject(current)) return false;
139
137
  current = current[key];
140
138
  }
141
139
  const lastKey = keys[keys.length - 1];
142
- if (lastKey) {
140
+ if (lastKey && isObject(current)) {
143
141
  current[lastKey] = value;
144
142
  }
145
143
  return true;
@@ -182,7 +180,7 @@ function subscribeStore(storeId) {
182
180
  unsubscribeStore(storeId);
183
181
  }
184
182
  if (isFunction(store.$subscribe)) {
185
- const unsubscribe = store.$subscribe((_mutation, state) => {
183
+ const unsubscribe = store.$subscribe?.((_mutation, state) => {
186
184
  const currentState = isObject(state) ? deepClone(state) : {};
187
185
  globalChangeCallbacks.forEach((cb) => cb(storeId, currentState));
188
186
  });
@@ -249,7 +247,7 @@ function watchRouteChanges() {
249
247
  if (afterEachHandler !== null) {
250
248
  unwatchRouteChanges();
251
249
  }
252
- routerInstance.afterEach((to) => {
250
+ routerInstance.afterEach?.((to) => {
253
251
  const routeInfo = {
254
252
  path: to.path || "/",
255
253
  name: to.name || null,
@@ -283,7 +281,8 @@ function navigateToName(name, params) {
283
281
  if (!routerInstance) {
284
282
  return Promise.reject(new Error("Router not registered"));
285
283
  }
286
- return routerInstance.push?.({ name, params }) || Promise.resolve();
284
+ const path = `/${name}${params ? "?" + new URLSearchParams(params).toString() : ""}`;
285
+ return routerInstance.push?.(path) || Promise.resolve();
287
286
  }
288
287
  function goBack() {
289
288
  if (!routerInstance) {
@@ -329,6 +328,14 @@ function clearRouteHistory() {
329
328
  }
330
329
 
331
330
  // src/signalsInspector.ts
331
+ var DEFAULT_LAYOUT_OPTIONS = {
332
+ nodeWidth: 120,
333
+ nodeHeight: 40,
334
+ horizontalSpacing: 60,
335
+ verticalSpacing: 80,
336
+ centerX: 300,
337
+ centerY: 200
338
+ };
332
339
  var signalRegistry = /* @__PURE__ */ new Map();
333
340
  var snapshotManager = {
334
341
  snapshots: [],
@@ -484,6 +491,9 @@ function createSnapshot(label) {
484
491
  function getSnapshots() {
485
492
  return snapshotManager.getAll();
486
493
  }
494
+ function getSnapshot(index) {
495
+ return snapshotManager.get(index);
496
+ }
487
497
  function getTimeTravelState() {
488
498
  const length = snapshotManager.getLength();
489
499
  return {
@@ -637,6 +647,352 @@ function serializePerformanceStats() {
637
647
  });
638
648
  return result;
639
649
  }
650
+ function calculateNodeLevels() {
651
+ const levels = /* @__PURE__ */ new Map();
652
+ const visited = /* @__PURE__ */ new Set();
653
+ function visitNode(nodeId, level) {
654
+ if (visited.has(nodeId)) {
655
+ const existingLevel = levels.get(nodeId);
656
+ if (existingLevel !== void 0 && level > existingLevel) {
657
+ levels.set(nodeId, level);
658
+ }
659
+ return;
660
+ }
661
+ visited.add(nodeId);
662
+ levels.set(nodeId, level);
663
+ const node = signalRegistry.get(nodeId);
664
+ if (node) {
665
+ node.dependents.forEach((depId) => {
666
+ visitNode(depId, level + 1);
667
+ });
668
+ }
669
+ }
670
+ signalRegistry.forEach((node, id) => {
671
+ if (node.dependencies.size === 0) {
672
+ visitNode(id, 0);
673
+ }
674
+ });
675
+ signalRegistry.forEach((_, id) => {
676
+ if (!visited.has(id)) {
677
+ visitNode(id, 0);
678
+ }
679
+ });
680
+ return levels;
681
+ }
682
+ function calculateNodeDegrees() {
683
+ const degrees = /* @__PURE__ */ new Map();
684
+ signalRegistry.forEach((node, id) => {
685
+ degrees.set(id, {
686
+ inDegree: node.dependencies.size,
687
+ outDegree: node.dependents.size
688
+ });
689
+ });
690
+ return degrees;
691
+ }
692
+ function getVisualLayoutGraph(options2) {
693
+ const opts = { ...DEFAULT_LAYOUT_OPTIONS, ...options2 };
694
+ if (signalRegistry.size === 0) {
695
+ return { nodes: [], edges: [], width: 0, height: 0 };
696
+ }
697
+ const levels = calculateNodeLevels();
698
+ const degrees = calculateNodeDegrees();
699
+ const levelNodes = /* @__PURE__ */ new Map();
700
+ levels.forEach((level, nodeId) => {
701
+ const nodes2 = levelNodes.get(level) || [];
702
+ nodes2.push(nodeId);
703
+ levelNodes.set(level, nodes2);
704
+ });
705
+ const maxLevel = Math.max(...Array.from(levels.values()), 0);
706
+ const maxNodesInLevel = Math.max(...Array.from(levelNodes.values()).map((n) => n.length), 1);
707
+ const graphWidth = (maxNodesInLevel + 1) * (opts.nodeWidth + opts.horizontalSpacing);
708
+ const graphHeight = (maxLevel + 2) * (opts.nodeHeight + opts.verticalSpacing);
709
+ const nodes = [];
710
+ const edges = [];
711
+ levelNodes.forEach((nodeIds, level) => {
712
+ const levelWidth = nodeIds.length * (opts.nodeWidth + opts.horizontalSpacing) - opts.horizontalSpacing;
713
+ const startX = (graphWidth - levelWidth) / 2;
714
+ nodeIds.forEach((nodeId, index) => {
715
+ const node = signalRegistry.get(nodeId);
716
+ if (!node) return;
717
+ const degree = degrees.get(nodeId) || { inDegree: 0, outDegree: 0 };
718
+ const x = startX + index * (opts.nodeWidth + opts.horizontalSpacing);
719
+ const y = opts.centerY - graphHeight / 2 + level * (opts.nodeHeight + opts.verticalSpacing) + opts.verticalSpacing / 2;
720
+ nodes.push({
721
+ id: nodeId,
722
+ name: node.name,
723
+ type: node.type,
724
+ x,
725
+ y,
726
+ level,
727
+ width: opts.nodeWidth,
728
+ height: opts.nodeHeight,
729
+ inDegree: degree.inDegree,
730
+ outDegree: degree.outDegree
731
+ });
732
+ node.dependencies.forEach((depId) => {
733
+ const depNode = nodes.find((n) => n.id === depId);
734
+ if (depNode) {
735
+ edges.push({
736
+ source: depId,
737
+ target: nodeId,
738
+ sourceX: depNode.x + opts.nodeWidth,
739
+ sourceY: depNode.y + opts.nodeHeight / 2,
740
+ targetX: x,
741
+ targetY: y + opts.nodeHeight / 2,
742
+ type: "dependency"
743
+ });
744
+ }
745
+ });
746
+ });
747
+ });
748
+ return { nodes, edges, width: graphWidth, height: graphHeight };
749
+ }
750
+ function getSubgraph(centerId, depth = 2) {
751
+ const includedNodes = /* @__PURE__ */ new Set([centerId]);
752
+ const queue = [{ id: centerId, currentDepth: 0 }];
753
+ while (queue.length > 0) {
754
+ const { id, currentDepth: currentDepth2 } = queue.shift();
755
+ if (currentDepth2 >= depth) continue;
756
+ const node = signalRegistry.get(id);
757
+ if (node) {
758
+ node.dependencies.forEach((depId) => {
759
+ if (!includedNodes.has(depId)) {
760
+ includedNodes.add(depId);
761
+ queue.push({ id: depId, currentDepth: currentDepth2 + 1 });
762
+ }
763
+ });
764
+ node.dependents.forEach((depId) => {
765
+ if (!includedNodes.has(depId)) {
766
+ includedNodes.add(depId);
767
+ queue.push({ id: depId, currentDepth: currentDepth2 + 1 });
768
+ }
769
+ });
770
+ }
771
+ }
772
+ const tempRegistry = new Map(signalRegistry);
773
+ signalRegistry.forEach((_, id) => {
774
+ if (!includedNodes.has(id)) {
775
+ signalRegistry.delete(id);
776
+ }
777
+ });
778
+ const subgraph = getVisualLayoutGraph();
779
+ signalRegistry.clear();
780
+ tempRegistry.forEach((value, key) => {
781
+ signalRegistry.set(key, value);
782
+ });
783
+ return subgraph;
784
+ }
785
+ function searchSignals(query) {
786
+ const lowerQuery = query.toLowerCase();
787
+ return getSignalNodes().filter(
788
+ (node) => node.name.toLowerCase().includes(lowerQuery) || node.id.toLowerCase().includes(lowerQuery) || node.type.toLowerCase().includes(lowerQuery)
789
+ );
790
+ }
791
+ function filterSignals(options2) {
792
+ return getSignalNodes().filter((node) => {
793
+ if (options2.types && !options2.types.includes(node.type)) {
794
+ return false;
795
+ }
796
+ if (options2.minUpdateCount !== void 0 && node.updateCount < options2.minUpdateCount) {
797
+ return false;
798
+ }
799
+ if (options2.hasDependencies && node.dependencies.length === 0) {
800
+ return false;
801
+ }
802
+ if (options2.hasDependents && node.dependents.length === 0) {
803
+ return false;
804
+ }
805
+ return true;
806
+ });
807
+ }
808
+ function compareSnapshots(snapshot1, snapshot2) {
809
+ const diff = {
810
+ added: [],
811
+ removed: [],
812
+ changed: []
813
+ };
814
+ const ids1 = new Set(Object.keys(snapshot1.signals));
815
+ const ids2 = new Set(Object.keys(snapshot2.signals));
816
+ ids2.forEach((id) => {
817
+ if (!ids1.has(id)) {
818
+ diff.added.push({
819
+ id,
820
+ value: snapshot2.signals[id].value
821
+ });
822
+ }
823
+ });
824
+ ids1.forEach((id) => {
825
+ if (!ids2.has(id)) {
826
+ diff.removed.push({
827
+ id,
828
+ value: snapshot1.signals[id].value
829
+ });
830
+ }
831
+ });
832
+ ids1.forEach((id) => {
833
+ if (ids2.has(id)) {
834
+ const oldValue = snapshot1.signals[id].value;
835
+ const newValue = snapshot2.signals[id].value;
836
+ if (!Object.is(oldValue, newValue)) {
837
+ diff.changed.push({ id, oldValue, newValue });
838
+ }
839
+ }
840
+ });
841
+ return diff;
842
+ }
843
+ function serializeSnapshotDiff(diff) {
844
+ let result = "";
845
+ if (diff.added.length > 0) {
846
+ result += `\u2795 \u65B0\u589E\u4FE1\u53F7 (${diff.added.length}):
847
+ `;
848
+ diff.added.forEach(({ id, value }) => {
849
+ result += ` ${id}: ${JSON.stringify(value)}
850
+ `;
851
+ });
852
+ result += "\n";
853
+ }
854
+ if (diff.removed.length > 0) {
855
+ result += `\u2796 \u79FB\u9664\u4FE1\u53F7 (${diff.removed.length}):
856
+ `;
857
+ diff.removed.forEach(({ id, value }) => {
858
+ result += ` ${id}: ${JSON.stringify(value)}
859
+ `;
860
+ });
861
+ result += "\n";
862
+ }
863
+ if (diff.changed.length > 0) {
864
+ result += `\u{1F504} \u53D8\u66F4\u4FE1\u53F7 (${diff.changed.length}):
865
+ `;
866
+ diff.changed.forEach(({ id, oldValue, newValue }) => {
867
+ result += ` ${id}:
868
+ `;
869
+ result += ` \u65E7\u503C: ${JSON.stringify(oldValue)}
870
+ `;
871
+ result += ` \u65B0\u503C: ${JSON.stringify(newValue)}
872
+ `;
873
+ });
874
+ result += "\n";
875
+ }
876
+ if (result === "") {
877
+ result = "\u2728 \u5FEB\u7167\u5B8C\u5168\u76F8\u540C";
878
+ }
879
+ return result;
880
+ }
881
+ function getDiffBetweenSnapshots(index1, index2) {
882
+ const snapshot1 = getSnapshot(index1);
883
+ const snapshot2 = getSnapshot(index2);
884
+ if (!snapshot1 || !snapshot2) {
885
+ return null;
886
+ }
887
+ return compareSnapshots(snapshot1, snapshot2);
888
+ }
889
+ function getTimeTravelNavigator(index) {
890
+ const snapshots = getSnapshots();
891
+ const currentIndex = index !== void 0 ? index : Math.max(0, snapshots.length - 1);
892
+ return {
893
+ currentIndex,
894
+ total: snapshots.length,
895
+ canGoBack: currentIndex > 0,
896
+ canGoForward: currentIndex < snapshots.length - 1,
897
+ currentSnapshot: snapshots[currentIndex] ?? null,
898
+ previousSnapshot: currentIndex > 0 ? snapshots[currentIndex - 1] ?? null : null,
899
+ nextSnapshot: snapshots[currentIndex + 1] ?? null,
900
+ diff: currentIndex > 0 ? compareSnapshots(snapshots[currentIndex - 1], snapshots[currentIndex]) : null
901
+ };
902
+ }
903
+ function timeTravelBack() {
904
+ const navigator = getTimeTravelNavigator();
905
+ if (!navigator.canGoBack) return null;
906
+ return restoreSnapshot(navigator.currentIndex - 1) ?? null;
907
+ }
908
+ function timeTravelForward() {
909
+ const navigator = getTimeTravelNavigator();
910
+ if (!navigator.canGoForward) return null;
911
+ return restoreSnapshot(navigator.currentIndex + 1) ?? null;
912
+ }
913
+
914
+ // src/vdomInspector.ts
915
+ var registry = {
916
+ roots: /* @__PURE__ */ new Map(),
917
+ nodes: /* @__PURE__ */ new Map()};
918
+ function getVDOMRoots() {
919
+ return Array.from(registry.roots.values());
920
+ }
921
+ function getVDOMTree() {
922
+ return getVDOMRoots();
923
+ }
924
+ function getVDOMStats() {
925
+ let maxDepth = 0;
926
+ let componentCount = 0;
927
+ let elementCount = 0;
928
+ let textCount = 0;
929
+ for (const node of registry.nodes.values()) {
930
+ if (node.depth > maxDepth) maxDepth = node.depth;
931
+ if (node.isComponent) componentCount++;
932
+ else if (node.type === "element") elementCount++;
933
+ else if (node.type === "text") textCount++;
934
+ }
935
+ return {
936
+ totalNodes: registry.nodes.size,
937
+ rootCount: registry.roots.size,
938
+ maxDepth,
939
+ componentCount,
940
+ elementCount,
941
+ textCount
942
+ };
943
+ }
944
+ function serializeVDOMNode(node, indent = 0) {
945
+ const spaces = " ".repeat(indent);
946
+ let result = "";
947
+ if (node.type === "text") {
948
+ result += `${spaces}\u{1F4DD} "${node.text || ""}"
949
+ `;
950
+ } else if (node.isComponent) {
951
+ result += `${spaces}\u{1F9E9} <${node.tagName || "Component"}`;
952
+ if (node.key !== void 0) result += ` key="${node.key}"`;
953
+ result += ">\n";
954
+ } else if (node.type === "element") {
955
+ result += `${spaces}\u{1F3F7}\uFE0F <${node.tagName || "unknown"}`;
956
+ if (node.key !== void 0) result += ` key="${node.key}"`;
957
+ if (node.props) {
958
+ const propStr = Object.entries(node.props).slice(0, 3).map(([k, v]) => `${k}="${v}"`).join(" ");
959
+ if (propStr) result += ` ${propStr}`;
960
+ if (Object.keys(node.props).length > 3) {
961
+ result += " ...";
962
+ }
963
+ }
964
+ result += ">\n";
965
+ }
966
+ for (const child of node.children) {
967
+ result += serializeVDOMNode(child, indent + 1);
968
+ }
969
+ if ((node.isComponent || node.type === "element") && node.children.length === 0) {
970
+ result += `${spaces} (empty)
971
+ `;
972
+ }
973
+ return result;
974
+ }
975
+ function serializeVDOMTree() {
976
+ const roots = getVDOMRoots();
977
+ if (roots.length === 0) {
978
+ return "No VDOM roots registered.";
979
+ }
980
+ let result = `\u{1F4E6} VDOM Tree (${roots.length} root(s))
981
+ `;
982
+ result += "\u2500".repeat(40) + "\n";
983
+ for (const root of roots) {
984
+ result += serializeVDOMNode(root);
985
+ result += "\n";
986
+ }
987
+ const stats = getVDOMStats();
988
+ result += "\u2500".repeat(40) + "\n";
989
+ result += `\u{1F4CA} Stats: ${stats.totalNodes} nodes, ${stats.componentCount} components, `;
990
+ result += `${stats.elementCount} elements, ${stats.textCount} text nodes
991
+ `;
992
+ result += `\u{1F4CF} Max depth: ${stats.maxDepth}
993
+ `;
994
+ return result;
995
+ }
640
996
 
641
997
  // src/performance.ts
642
998
  var DEFAULT_OPTIONS = {
@@ -881,7 +1237,7 @@ function outputAlert(alert) {
881
1237
  error: "color: #ff4d4f;",
882
1238
  critical: "color: #722ed1; background: #ff4d4f; padding: 2px 5px;"
883
1239
  };
884
- console.log(
1240
+ console.warn(
885
1241
  `%c[LytJS Alert] ${alert.ruleName}%c ${alert.message}`,
886
1242
  levelStyles[alert.level],
887
1243
  "color: inherit;"
@@ -968,6 +1324,144 @@ function startTimer(name, type = "custom") {
968
1324
  recordMetric({ name, type, duration });
969
1325
  };
970
1326
  }
1327
+ var timelineEventStack = [];
1328
+ var timelineEvents = [];
1329
+ var maxTimelineEvents = 1e3;
1330
+ var currentDepth = 0;
1331
+ function beginTimelineEvent(name, category = "custom", metadata) {
1332
+ const id = `timeline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1333
+ const event = {
1334
+ id,
1335
+ name,
1336
+ category,
1337
+ startTime: performance.now(),
1338
+ duration: 0,
1339
+ depth: currentDepth,
1340
+ metadata
1341
+ };
1342
+ timelineEventStack.push({
1343
+ event,
1344
+ startTime: performance.now()
1345
+ });
1346
+ currentDepth++;
1347
+ return id;
1348
+ }
1349
+ function endTimelineEvent(id) {
1350
+ const stackIndex = timelineEventStack.findIndex((item) => item.event.id === id);
1351
+ if (stackIndex === -1) {
1352
+ console.warn(`[DevTools] Timeline event ${id} not found in stack`);
1353
+ return null;
1354
+ }
1355
+ const { event } = timelineEventStack[stackIndex];
1356
+ const endTime = performance.now();
1357
+ event.duration = endTime - event.startTime;
1358
+ for (let i = stackIndex + 1; i < timelineEventStack.length; i++) {
1359
+ const nestedEvent = timelineEventStack[i];
1360
+ event.duration = Math.max(event.duration, nestedEvent.startTime - event.startTime + nestedEvent.event.duration);
1361
+ }
1362
+ timelineEventStack.splice(stackIndex);
1363
+ currentDepth = Math.max(0, currentDepth - 1);
1364
+ timelineEvents.push(event);
1365
+ if (timelineEvents.length > maxTimelineEvents) {
1366
+ timelineEvents.shift();
1367
+ }
1368
+ return event;
1369
+ }
1370
+ function getTimelineEvents() {
1371
+ return [...timelineEvents];
1372
+ }
1373
+ function getTimelineEventsInRange(startTime, endTime) {
1374
+ return timelineEvents.filter(
1375
+ (event) => event.startTime >= startTime && event.startTime <= endTime
1376
+ );
1377
+ }
1378
+ function getSlowOperations(limit = 10, threshold) {
1379
+ const sorted = [...timelineEvents].sort((a, b) => b.duration - a.duration);
1380
+ const result = threshold !== void 0 ? sorted.filter((e) => e.duration >= threshold) : sorted;
1381
+ return result.slice(0, limit);
1382
+ }
1383
+ function getFlameGraphData() {
1384
+ const root = {
1385
+ name: "root",
1386
+ value: 0,
1387
+ children: [],
1388
+ category: "custom"
1389
+ };
1390
+ const nodeMap = /* @__PURE__ */ new Map();
1391
+ nodeMap.set("root", root);
1392
+ timelineEvents.forEach((event) => {
1393
+ const node = {
1394
+ name: event.name,
1395
+ value: event.duration,
1396
+ category: event.category,
1397
+ children: []
1398
+ };
1399
+ const parentNode = event.depth === 0 ? root : findParentNode(root, event.depth);
1400
+ if (parentNode) {
1401
+ if (!parentNode.children) {
1402
+ parentNode.children = [];
1403
+ }
1404
+ parentNode.children.push(node);
1405
+ }
1406
+ nodeMap.set(event.id, node);
1407
+ });
1408
+ aggregateFlameGraphValues(root);
1409
+ return root;
1410
+ }
1411
+ function findParentNode(node, depth) {
1412
+ if (!node.children || node.children.length === 0) {
1413
+ return depth === 1 ? node : null;
1414
+ }
1415
+ let targetDepth = depth - 1;
1416
+ let currentNode = node;
1417
+ while (targetDepth > 0 && currentNode) {
1418
+ const children = currentNode.children || [];
1419
+ if (children.length === 0) {
1420
+ return currentNode;
1421
+ }
1422
+ const lastChild = children[children.length - 1];
1423
+ currentNode = lastChild;
1424
+ targetDepth--;
1425
+ }
1426
+ return currentNode;
1427
+ }
1428
+ function aggregateFlameGraphValues(node) {
1429
+ if (!node.children || node.children.length === 0) {
1430
+ return node.value;
1431
+ }
1432
+ let totalValue = node.value;
1433
+ node.children.forEach((child) => {
1434
+ totalValue += aggregateFlameGraphValues(child);
1435
+ });
1436
+ node.value = totalValue;
1437
+ return totalValue;
1438
+ }
1439
+ function clearTimelineEvents() {
1440
+ timelineEvents.length = 0;
1441
+ timelineEventStack.length = 0;
1442
+ currentDepth = 0;
1443
+ }
1444
+ function exportTimelineAsJSON() {
1445
+ return JSON.stringify({
1446
+ events: timelineEvents,
1447
+ exportedAt: Date.now()
1448
+ }, null, 2);
1449
+ }
1450
+ function serializeTimelineEvents() {
1451
+ if (timelineEvents.length === 0) {
1452
+ return "\u6682\u65E0\u65F6\u5E8F\u4E8B\u4EF6\u6570\u636E";
1453
+ }
1454
+ let result = `\u{1F4CA} \u65F6\u5E8F\u4E8B\u4EF6 (${timelineEvents.length} \u4E2A)
1455
+
1456
+ `;
1457
+ timelineEvents.slice(-20).forEach((event) => {
1458
+ const indent = " ".repeat(event.depth);
1459
+ const duration = event.duration.toFixed(2);
1460
+ result += `${indent}\u251C\u2500 ${event.name} [${event.category}] ${duration}ms
1461
+ `;
1462
+ });
1463
+ return result;
1464
+ }
971
1465
 
972
1466
  // src/benchmark.ts
973
1467
  var LARGE_SCALE_SCENARIOS = [
@@ -1208,7 +1702,7 @@ var DevTools = class {
1208
1702
  this.toggle();
1209
1703
  }
1210
1704
  });
1211
- console.log("[LytJS DevTools] Initialized. Press Ctrl+Shift+D to toggle.");
1705
+ console.warn("[LytJS DevTools] Initialized. Press Ctrl+Shift+D to toggle.");
1212
1706
  }
1213
1707
  /**
1214
1708
  * 创建 DevTools 面板
@@ -1296,6 +1790,15 @@ var DevTools = class {
1296
1790
  cursor: pointer;
1297
1791
  border-bottom: 2px solid transparent;
1298
1792
  ">Signals</button>
1793
+ <button class="lytjs-devtools-tab" data-tab="vdom" style="
1794
+ flex: 1;
1795
+ padding: 10px;
1796
+ background: transparent;
1797
+ border: none;
1798
+ color: #d4d4d4;
1799
+ cursor: pointer;
1800
+ border-bottom: 2px solid transparent;
1801
+ ">VDOM</button>
1299
1802
  <button class="lytjs-devtools-tab" data-tab="performance" style="
1300
1803
  flex: 1;
1301
1804
  padding: 10px;
@@ -1352,6 +1855,9 @@ var DevTools = class {
1352
1855
  case "signals":
1353
1856
  content.innerHTML = this.renderSignalsTab();
1354
1857
  break;
1858
+ case "vdom":
1859
+ content.innerHTML = this.renderVDOMTab();
1860
+ break;
1355
1861
  case "performance":
1356
1862
  content.innerHTML = this.renderPerformanceTab();
1357
1863
  break;
@@ -1386,7 +1892,7 @@ var DevTools = class {
1386
1892
  */
1387
1893
  renderSignalsTab() {
1388
1894
  const nodes = getSignalNodes();
1389
- let html = `
1895
+ const html = `
1390
1896
  <div style="margin-bottom: 15px;">
1391
1897
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
1392
1898
  <strong>\u{1F4CA} Signals (${nodes.length})</strong>
@@ -1491,6 +1997,97 @@ var DevTools = class {
1491
1997
  html += "</div>";
1492
1998
  return html;
1493
1999
  }
2000
+ /**
2001
+ * 渲染 VDOM 标签页
2002
+ */
2003
+ renderVDOMTab() {
2004
+ const roots = getVDOMTree();
2005
+ const stats = getVDOMStats();
2006
+ if (roots.length === 0) {
2007
+ return `
2008
+ <div style="color: #666; text-align: center; padding: 40px;">
2009
+ <div style="font-size: 48px; margin-bottom: 15px;">\u{1F333}</div>
2010
+ <div>No VDOM roots registered.</div>
2011
+ <div style="font-size: 11px; margin-top: 10px; color: #555;">
2012
+ Use registerVDOMRoot() to register your VDOM roots.
2013
+ </div>
2014
+ </div>
2015
+ `;
2016
+ }
2017
+ const html = `
2018
+ <div style="margin-bottom: 15px;">
2019
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
2020
+ <strong>\u{1F333} VDOM Tree</strong>
2021
+ <button id="lytjs-devtools-clear-vdom" style="
2022
+ background: #ff4d4f;
2023
+ border: none;
2024
+ color: white;
2025
+ padding: 5px 10px;
2026
+ border-radius: 4px;
2027
+ cursor: pointer;
2028
+ font-size: 11px;
2029
+ ">\u6E05\u7A7A</button>
2030
+ </div>
2031
+
2032
+ <div style="background: #252526; border-radius: 4px; padding: 15px; margin-bottom: 15px;">
2033
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: center;">
2034
+ <div>
2035
+ <div style="color: #888; font-size: 11px;">\u603B\u8282\u70B9\u6570</div>
2036
+ <div style="font-size: 20px; font-weight: bold; color: #4fc08d;">${stats.totalNodes}</div>
2037
+ </div>
2038
+ <div>
2039
+ <div style="color: #888; font-size: 11px;">\u7EC4\u4EF6\u6570</div>
2040
+ <div style="font-size: 20px; font-weight: bold; color: #1890ff;">${stats.componentCount}</div>
2041
+ </div>
2042
+ <div>
2043
+ <div style="color: #888; font-size: 11px;">\u5143\u7D20\u6570</div>
2044
+ <div style="font-size: 20px; font-weight: bold; color: #faad14;">${stats.elementCount}</div>
2045
+ </div>
2046
+ </div>
2047
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: center; margin-top: 10px;">
2048
+ <div>
2049
+ <div style="color: #888; font-size: 11px;">\u6587\u672C\u8282\u70B9</div>
2050
+ <div style="font-size: 16px; font-weight: bold; color: #888;">${stats.textCount}</div>
2051
+ </div>
2052
+ <div>
2053
+ <div style="color: #888; font-size: 11px;">\u6839\u8282\u70B9</div>
2054
+ <div style="font-size: 16px; font-weight: bold; color: #888;">${stats.rootCount}</div>
2055
+ </div>
2056
+ <div>
2057
+ <div style="color: #888; font-size: 11px;">\u6700\u5927\u6DF1\u5EA6</div>
2058
+ <div style="font-size: 16px; font-weight: bold; color: #888;">${stats.maxDepth}</div>
2059
+ </div>
2060
+ </div>
2061
+ </div>
2062
+
2063
+ <div style="margin-bottom: 10px;">
2064
+ <input id="lytjs-devtools-vdom-search" type="text" placeholder="\u641C\u7D22\u6807\u7B7E\u540D..." style="
2065
+ width: 100%;
2066
+ padding: 8px;
2067
+ background: #252526;
2068
+ border: 1px solid #3d3d3d;
2069
+ border-radius: 4px;
2070
+ color: #d4d4d4;
2071
+ font-size: 12px;
2072
+ " />
2073
+ </div>
2074
+
2075
+ <div id="lytjs-devtools-vdom-tree" style="
2076
+ background: #252526;
2077
+ border-radius: 4px;
2078
+ padding: 15px;
2079
+ max-height: 400px;
2080
+ overflow: auto;
2081
+ font-family: 'Consolas', 'Monaco', monospace;
2082
+ font-size: 11px;
2083
+ line-height: 1.6;
2084
+ ">
2085
+ <pre style="margin: 0; white-space: pre-wrap; word-break: break-all;">${serializeVDOMTree()}</pre>
2086
+ </div>
2087
+ </div>
2088
+ `;
2089
+ return html;
2090
+ }
1494
2091
  /**
1495
2092
  * 渲染 Performance 标签页
1496
2093
  */
@@ -1663,6 +2260,6 @@ function uninstallDevTools() {
1663
2260
  }
1664
2261
  }
1665
2262
 
1666
- export { LARGE_SCALE_SCENARIOS, acknowledgeAlert, acknowledgeAllAlerts, addObserver, clearAlerts, clearBenchmarkResults, clearMetrics, clearPerformanceRecords, clearRouteHistory, clearSignalRegistry, clearSnapshots, clearStoreRegistry, compareBenchmarkResults, createLargeScaleBenchmark, createRegressionDetector, createSnapshot, installDevTools as default, dispatchStoreAction, getAlertRules, getAlerts, getBenchmarkResults, getComponentTree, getCurrentRoute, getDependencyGraph, getDevTools, getLatestBenchmarkResult, getMemoryUsage, getMetrics, getPerformanceRecords, getPerformanceReport, getPerformanceStats, getRegisteredStoreIds, getRouteHistory, getRoutes, getSignalNode, getSignalNodes, getSnapshots, getStats, getStoreState, getStoreStates, getTimeTravelState, goBack, initPerformanceMonitor, installDevTools, isRouterRegistered, navigateTo, navigateToName, onStoreChange, recordDependency, recordMetric, recordSignalUpdate, registerAlertRule, registerRootComponent, registerRouter, registerSignal, registerStore, removeObserver, resetPerformanceMonitor, restoreSnapshot, runAsyncBenchmark, runBenchmark, serializeAllBenchmarkResults, serializeBenchmarkResult, serializeComponentTree, serializeDependencyGraph, serializeMemoryUsage, serializePerformanceReport, serializePerformanceStats, serializeRouteInfo, serializeSignalNode, serializeStoreStates, setAlertRuleEnabled, setStoreState, startTimer, subscribeStore, uninstallDevTools, unregisterAlertRule, unregisterRootComponent, unregisterRouter, unregisterSignal, unregisterStore, unsubscribeStore, unwatchRouteChanges, watchRouteChanges };
2263
+ export { LARGE_SCALE_SCENARIOS, acknowledgeAlert, acknowledgeAllAlerts, addObserver, beginTimelineEvent, clearAlerts, clearBenchmarkResults, clearMetrics, clearPerformanceRecords, clearRouteHistory, clearSignalRegistry, clearSnapshots, clearStoreRegistry, clearTimelineEvents, compareBenchmarkResults, compareSnapshots, createLargeScaleBenchmark, createRegressionDetector, createSnapshot, installDevTools as default, dispatchStoreAction, endTimelineEvent, exportTimelineAsJSON, filterSignals, getAlertRules, getAlerts, getBenchmarkResults, getComponentTree, getCurrentRoute, getDependencyGraph, getDevTools, getDiffBetweenSnapshots, getFlameGraphData, getLatestBenchmarkResult, getMemoryUsage, getMetrics, getPerformanceRecords, getPerformanceReport, getPerformanceStats, getRegisteredStoreIds, getRouteHistory, getRoutes, getSignalNode, getSignalNodes, getSlowOperations, getSnapshots, getStats, getStoreState, getStoreStates, getSubgraph, getTimeTravelNavigator, getTimeTravelState, getTimelineEvents, getTimelineEventsInRange, getVisualLayoutGraph, goBack, initPerformanceMonitor, installDevTools, isRouterRegistered, navigateTo, navigateToName, onStoreChange, recordDependency, recordMetric, recordSignalUpdate, registerAlertRule, registerRootComponent, registerRouter, registerSignal, registerStore, removeObserver, resetPerformanceMonitor, restoreSnapshot, runAsyncBenchmark, runBenchmark, searchSignals, serializeAllBenchmarkResults, serializeBenchmarkResult, serializeComponentTree, serializeDependencyGraph, serializeMemoryUsage, serializePerformanceReport, serializePerformanceStats, serializeRouteInfo, serializeSignalNode, serializeSnapshotDiff, serializeStoreStates, serializeTimelineEvents, setAlertRuleEnabled, setStoreState, startTimer, subscribeStore, timeTravelBack, timeTravelForward, uninstallDevTools, unregisterAlertRule, unregisterRootComponent, unregisterRouter, unregisterSignal, unregisterStore, unsubscribeStore, unwatchRouteChanges, watchRouteChanges };
1667
2264
  //# sourceMappingURL=index.mjs.map
1668
2265
  //# sourceMappingURL=index.mjs.map