@three-flatland/devtools 0.1.0-alpha.3

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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/buffers-modal.cjs +491 -0
  4. package/dist/buffers-modal.cjs.map +1 -0
  5. package/dist/buffers-modal.d.cts +62 -0
  6. package/dist/buffers-modal.d.ts +62 -0
  7. package/dist/buffers-modal.js +467 -0
  8. package/dist/buffers-modal.js.map +1 -0
  9. package/dist/buffers-view.cjs +269 -0
  10. package/dist/buffers-view.cjs.map +1 -0
  11. package/dist/buffers-view.d.cts +39 -0
  12. package/dist/buffers-view.d.ts +39 -0
  13. package/dist/buffers-view.js +245 -0
  14. package/dist/buffers-view.js.map +1 -0
  15. package/dist/create-pane.cjs +203 -0
  16. package/dist/create-pane.cjs.map +1 -0
  17. package/dist/create-pane.d.cts +66 -0
  18. package/dist/create-pane.d.ts +66 -0
  19. package/dist/create-pane.js +178 -0
  20. package/dist/create-pane.js.map +1 -0
  21. package/dist/dashboard/app.tsx +42 -0
  22. package/dist/dashboard/client.ts +21 -0
  23. package/dist/dashboard/export.ts +84 -0
  24. package/dist/dashboard/hooks.ts +75 -0
  25. package/dist/dashboard/index.html +1149 -0
  26. package/dist/dashboard/index.tsx +18 -0
  27. package/dist/dashboard/panels/batches.tsx +282 -0
  28. package/dist/dashboard/panels/buffers.tsx +425 -0
  29. package/dist/dashboard/panels/env.tsx +85 -0
  30. package/dist/dashboard/panels/header-stats.tsx +30 -0
  31. package/dist/dashboard/panels/producer-select.tsx +81 -0
  32. package/dist/dashboard/panels/protocol-log.tsx +743 -0
  33. package/dist/dashboard/panels/registry.tsx +642 -0
  34. package/dist/dashboard/panels/stats.tsx +431 -0
  35. package/dist/dashboard/protocol-store.ts +312 -0
  36. package/dist/dashboard/tsconfig.json +19 -0
  37. package/dist/dashboard/vendor/hooks.module.js +1 -0
  38. package/dist/dashboard/vendor/jsx-runtime.js +22 -0
  39. package/dist/dashboard/vendor/preact.module.js +12 -0
  40. package/dist/devtools-client.cjs +870 -0
  41. package/dist/devtools-client.cjs.map +1 -0
  42. package/dist/devtools-client.d.cts +362 -0
  43. package/dist/devtools-client.d.ts +362 -0
  44. package/dist/devtools-client.js +852 -0
  45. package/dist/devtools-client.js.map +1 -0
  46. package/dist/index.cjs +42 -0
  47. package/dist/index.cjs.map +1 -0
  48. package/dist/index.d.cts +8 -0
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.js +13 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/perf-trace.cjs +103 -0
  53. package/dist/perf-trace.cjs.map +1 -0
  54. package/dist/perf-trace.d.cts +27 -0
  55. package/dist/perf-trace.d.ts +27 -0
  56. package/dist/perf-trace.js +79 -0
  57. package/dist/perf-trace.js.map +1 -0
  58. package/dist/plugins.cjs +44 -0
  59. package/dist/plugins.cjs.map +1 -0
  60. package/dist/plugins.d.cts +7 -0
  61. package/dist/plugins.d.ts +7 -0
  62. package/dist/plugins.js +9 -0
  63. package/dist/plugins.js.map +1 -0
  64. package/dist/provider-switcher.cjs +95 -0
  65. package/dist/provider-switcher.cjs.map +1 -0
  66. package/dist/provider-switcher.d.cts +18 -0
  67. package/dist/provider-switcher.d.ts +18 -0
  68. package/dist/provider-switcher.js +71 -0
  69. package/dist/provider-switcher.js.map +1 -0
  70. package/dist/react/devtools-provider.cjs +59 -0
  71. package/dist/react/devtools-provider.cjs.map +1 -0
  72. package/dist/react/devtools-provider.d.cts +39 -0
  73. package/dist/react/devtools-provider.d.ts +39 -0
  74. package/dist/react/devtools-provider.js +38 -0
  75. package/dist/react/devtools-provider.js.map +1 -0
  76. package/dist/react/use-fps-graph.cjs +49 -0
  77. package/dist/react/use-fps-graph.cjs.map +1 -0
  78. package/dist/react/use-fps-graph.d.cts +15 -0
  79. package/dist/react/use-fps-graph.d.ts +15 -0
  80. package/dist/react/use-fps-graph.js +25 -0
  81. package/dist/react/use-fps-graph.js.map +1 -0
  82. package/dist/react/use-pane-button.cjs +58 -0
  83. package/dist/react/use-pane-button.cjs.map +1 -0
  84. package/dist/react/use-pane-button.d.cts +10 -0
  85. package/dist/react/use-pane-button.d.ts +10 -0
  86. package/dist/react/use-pane-button.js +34 -0
  87. package/dist/react/use-pane-button.js.map +1 -0
  88. package/dist/react/use-pane-folder.cjs +48 -0
  89. package/dist/react/use-pane-folder.cjs.map +1 -0
  90. package/dist/react/use-pane-folder.d.cts +15 -0
  91. package/dist/react/use-pane-folder.d.ts +15 -0
  92. package/dist/react/use-pane-folder.js +24 -0
  93. package/dist/react/use-pane-folder.js.map +1 -0
  94. package/dist/react/use-pane-input.cjs +70 -0
  95. package/dist/react/use-pane-input.cjs.map +1 -0
  96. package/dist/react/use-pane-input.d.cts +44 -0
  97. package/dist/react/use-pane-input.d.ts +44 -0
  98. package/dist/react/use-pane-input.js +46 -0
  99. package/dist/react/use-pane-input.js.map +1 -0
  100. package/dist/react/use-pane-radio-grid.cjs +78 -0
  101. package/dist/react/use-pane-radio-grid.cjs.map +1 -0
  102. package/dist/react/use-pane-radio-grid.d.cts +35 -0
  103. package/dist/react/use-pane-radio-grid.d.ts +35 -0
  104. package/dist/react/use-pane-radio-grid.js +54 -0
  105. package/dist/react/use-pane-radio-grid.js.map +1 -0
  106. package/dist/react/use-pane.cjs +45 -0
  107. package/dist/react/use-pane.cjs.map +1 -0
  108. package/dist/react/use-pane.d.cts +40 -0
  109. package/dist/react/use-pane.d.ts +40 -0
  110. package/dist/react/use-pane.js +21 -0
  111. package/dist/react/use-pane.js.map +1 -0
  112. package/dist/react.cjs +50 -0
  113. package/dist/react.cjs.map +1 -0
  114. package/dist/react.d.cts +12 -0
  115. package/dist/react.d.ts +12 -0
  116. package/dist/react.js +19 -0
  117. package/dist/react.js.map +1 -0
  118. package/dist/registry-view.cjs +371 -0
  119. package/dist/registry-view.cjs.map +1 -0
  120. package/dist/registry-view.d.cts +24 -0
  121. package/dist/registry-view.d.ts +24 -0
  122. package/dist/registry-view.js +347 -0
  123. package/dist/registry-view.js.map +1 -0
  124. package/dist/stats-graph.cjs +297 -0
  125. package/dist/stats-graph.cjs.map +1 -0
  126. package/dist/stats-graph.d.cts +40 -0
  127. package/dist/stats-graph.d.ts +40 -0
  128. package/dist/stats-graph.js +273 -0
  129. package/dist/stats-graph.js.map +1 -0
  130. package/dist/stats-row.cjs +153 -0
  131. package/dist/stats-row.cjs.map +1 -0
  132. package/dist/stats-row.d.cts +24 -0
  133. package/dist/stats-row.d.ts +24 -0
  134. package/dist/stats-row.js +129 -0
  135. package/dist/stats-row.js.map +1 -0
  136. package/dist/theme.cjs +260 -0
  137. package/dist/theme.cjs.map +1 -0
  138. package/dist/theme.d.cts +10 -0
  139. package/dist/theme.d.ts +10 -0
  140. package/dist/theme.js +235 -0
  141. package/dist/theme.js.map +1 -0
  142. package/dist/vite-plugin.cjs +113 -0
  143. package/dist/vite-plugin.cjs.map +1 -0
  144. package/dist/vite-plugin.d.cts +9 -0
  145. package/dist/vite-plugin.d.ts +9 -0
  146. package/dist/vite-plugin.js +88 -0
  147. package/dist/vite-plugin.js.map +1 -0
  148. package/package.json +124 -0
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var provider_switcher_exports = {};
20
+ __export(provider_switcher_exports, {
21
+ addProviderSwitcher: () => addProviderSwitcher
22
+ });
23
+ module.exports = __toCommonJS(provider_switcher_exports);
24
+ function addProviderSwitcher(parent, client) {
25
+ const blade = parent.addBlade({ view: "separator" });
26
+ const bladeEl = blade.element;
27
+ bladeEl.innerHTML = "";
28
+ bladeEl.className = "tp-cntv";
29
+ bladeEl.style.cssText = "display:none";
30
+ const row = document.createElement("div");
31
+ row.style.cssText = [
32
+ "display:grid",
33
+ "grid-template-columns:auto 1fr auto",
34
+ "align-items:center",
35
+ "gap:6px",
36
+ "height:calc(var(--cnt-usz, 20px) * 1.1)",
37
+ "padding:0 6px",
38
+ "font-size:12px",
39
+ "line-height:1",
40
+ "color:var(--tp-label-foreground-color)",
41
+ "font-variant-numeric:tabular-nums",
42
+ "user-select:none",
43
+ "-webkit-user-select:none"
44
+ ].join(";");
45
+ const arrowStyle = "cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;";
46
+ const prevBtn = document.createElement("span");
47
+ prevBtn.textContent = "\u25C0";
48
+ prevBtn.style.cssText = arrowStyle;
49
+ prevBtn.setAttribute("role", "button");
50
+ prevBtn.setAttribute("aria-label", "Previous provider");
51
+ const label = document.createElement("span");
52
+ label.style.cssText = "text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
53
+ const nextBtn = document.createElement("span");
54
+ nextBtn.textContent = "\u25B6";
55
+ nextBtn.style.cssText = arrowStyle;
56
+ nextBtn.setAttribute("role", "button");
57
+ nextBtn.setAttribute("aria-label", "Next provider");
58
+ row.appendChild(prevBtn);
59
+ row.appendChild(label);
60
+ row.appendChild(nextBtn);
61
+ bladeEl.appendChild(row);
62
+ function step(delta) {
63
+ const providers = client.state.providers;
64
+ if (providers.length < 2) return;
65
+ const selected = client.state.selectedProviderId;
66
+ const idx = Math.max(0, providers.findIndex((p) => p.id === selected));
67
+ const next = providers[(idx + delta + providers.length) % providers.length];
68
+ client.selectProvider(next.id);
69
+ }
70
+ prevBtn.addEventListener("click", () => step(-1));
71
+ nextBtn.addEventListener("click", () => step(1));
72
+ const unsubscribe = client.addListener((s) => {
73
+ bladeEl.style.display = s.providers.length >= 2 ? "" : "none";
74
+ const current = s.providers.find((p) => p.id === s.selectedProviderId);
75
+ if (current) {
76
+ label.textContent = `${current.kind}:${current.name}`;
77
+ label.title = `${current.kind}:${current.name} (${current.id.slice(0, 8)})`;
78
+ } else {
79
+ label.textContent = "\u2014";
80
+ label.title = "";
81
+ }
82
+ });
83
+ return {
84
+ element: bladeEl,
85
+ dispose() {
86
+ unsubscribe();
87
+ blade.dispose();
88
+ }
89
+ };
90
+ }
91
+ // Annotate the CommonJS export names for ESM import in node:
92
+ 0 && (module.exports = {
93
+ addProviderSwitcher
94
+ });
95
+ //# sourceMappingURL=provider-switcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider-switcher.ts"],"sourcesContent":["/**\n * Provider switcher — a single-row blade with ◀ / ▶ arrows + the\n * selected provider's name. Hidden while only one provider is known\n * (the common case); shown automatically whenever two or more appear\n * on the discovery bus.\n */\n\nimport type { FolderApi, Pane } from 'tweakpane'\n\nimport type { DevtoolsClient } from './devtools-client.js'\n\nexport interface ProviderSwitcherHandle {\n readonly element: HTMLElement\n dispose(): void\n}\n\nexport function addProviderSwitcher(\n parent: Pane | FolderApi,\n client: DevtoolsClient,\n): ProviderSwitcherHandle {\n // Separator-blade shim so the rack ordering stays correct; we swap\n // its innards for our own layout (same pattern as stats-graph / row).\n const blade = parent.addBlade({ view: 'separator' }) as unknown as {\n element: HTMLElement\n dispose(): void\n }\n const bladeEl = blade.element\n bladeEl.innerHTML = ''\n bladeEl.className = 'tp-cntv'\n bladeEl.style.cssText = 'display:none' // Hidden until >= 2 providers\n\n const row = document.createElement('div')\n row.style.cssText = [\n 'display:grid',\n 'grid-template-columns:auto 1fr auto',\n 'align-items:center',\n 'gap:6px',\n 'height:calc(var(--cnt-usz, 20px) * 1.1)',\n 'padding:0 6px',\n 'font-size:12px',\n 'line-height:1',\n 'color:var(--tp-label-foreground-color)',\n 'font-variant-numeric:tabular-nums',\n 'user-select:none',\n '-webkit-user-select:none',\n ].join(';')\n\n const arrowStyle =\n 'cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;'\n const prevBtn = document.createElement('span')\n prevBtn.textContent = '◀'\n prevBtn.style.cssText = arrowStyle\n prevBtn.setAttribute('role', 'button')\n prevBtn.setAttribute('aria-label', 'Previous provider')\n\n const label = document.createElement('span')\n label.style.cssText = 'text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'\n\n const nextBtn = document.createElement('span')\n nextBtn.textContent = '▶'\n nextBtn.style.cssText = arrowStyle\n nextBtn.setAttribute('role', 'button')\n nextBtn.setAttribute('aria-label', 'Next provider')\n\n row.appendChild(prevBtn)\n row.appendChild(label)\n row.appendChild(nextBtn)\n bladeEl.appendChild(row)\n\n function step(delta: number): void {\n const providers = client.state.providers\n if (providers.length < 2) return\n const selected = client.state.selectedProviderId\n const idx = Math.max(0, providers.findIndex((p) => p.id === selected))\n const next = providers[(idx + delta + providers.length) % providers.length]!\n client.selectProvider(next.id)\n }\n\n prevBtn.addEventListener('click', () => step(-1))\n nextBtn.addEventListener('click', () => step(1))\n\n const unsubscribe = client.addListener((s) => {\n // Visibility: only when multi-provider.\n bladeEl.style.display = s.providers.length >= 2 ? '' : 'none'\n const current = s.providers.find((p) => p.id === s.selectedProviderId)\n if (current) {\n // Prefix with kind so the user can tell flatland's system provider\n // apart from a user provider with the same display name.\n label.textContent = `${current.kind}:${current.name}`\n label.title = `${current.kind}:${current.name} (${current.id.slice(0, 8)})`\n } else {\n label.textContent = '—'\n label.title = ''\n }\n })\n\n return {\n element: bladeEl,\n dispose() {\n unsubscribe()\n blade.dispose()\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBO,SAAS,oBACd,QACA,QACwB;AAGxB,QAAM,QAAQ,OAAO,SAAS,EAAE,MAAM,YAAY,CAAC;AAInD,QAAM,UAAU,MAAM;AACtB,UAAQ,YAAY;AACpB,UAAQ,YAAY;AACpB,UAAQ,MAAM,UAAU;AAExB,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,MAAM,UAAU;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,QAAM,aACJ;AACF,QAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,UAAQ,cAAc;AACtB,UAAQ,MAAM,UAAU;AACxB,UAAQ,aAAa,QAAQ,QAAQ;AACrC,UAAQ,aAAa,cAAc,mBAAmB;AAEtD,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,MAAM,UAAU;AAEtB,QAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,UAAQ,cAAc;AACtB,UAAQ,MAAM,UAAU;AACxB,UAAQ,aAAa,QAAQ,QAAQ;AACrC,UAAQ,aAAa,cAAc,eAAe;AAElD,MAAI,YAAY,OAAO;AACvB,MAAI,YAAY,KAAK;AACrB,MAAI,YAAY,OAAO;AACvB,UAAQ,YAAY,GAAG;AAEvB,WAAS,KAAK,OAAqB;AACjC,UAAM,YAAY,OAAO,MAAM;AAC/B,QAAI,UAAU,SAAS,EAAG;AAC1B,UAAM,WAAW,OAAO,MAAM;AAC9B,UAAM,MAAM,KAAK,IAAI,GAAG,UAAU,UAAU,CAAC,MAAM,EAAE,OAAO,QAAQ,CAAC;AACrE,UAAM,OAAO,WAAW,MAAM,QAAQ,UAAU,UAAU,UAAU,MAAM;AAC1E,WAAO,eAAe,KAAK,EAAE;AAAA,EAC/B;AAEA,UAAQ,iBAAiB,SAAS,MAAM,KAAK,EAAE,CAAC;AAChD,UAAQ,iBAAiB,SAAS,MAAM,KAAK,CAAC,CAAC;AAE/C,QAAM,cAAc,OAAO,YAAY,CAAC,MAAM;AAE5C,YAAQ,MAAM,UAAU,EAAE,UAAU,UAAU,IAAI,KAAK;AACvD,UAAM,UAAU,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,kBAAkB;AACrE,QAAI,SAAS;AAGX,YAAM,cAAc,GAAG,QAAQ,IAAI,IAAI,QAAQ,IAAI;AACnD,YAAM,QAAQ,GAAG,QAAQ,IAAI,IAAI,QAAQ,IAAI,KAAK,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC;AAAA,IAC1E,OAAO;AACL,YAAM,cAAc;AACpB,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AACR,kBAAY;AACZ,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,18 @@
1
+ import { Pane, FolderApi } from 'tweakpane';
2
+ import { DevtoolsClient } from './devtools-client.cjs';
3
+ import 'three-flatland/debug-protocol';
4
+
5
+ /**
6
+ * Provider switcher — a single-row blade with ◀ / ▶ arrows + the
7
+ * selected provider's name. Hidden while only one provider is known
8
+ * (the common case); shown automatically whenever two or more appear
9
+ * on the discovery bus.
10
+ */
11
+
12
+ interface ProviderSwitcherHandle {
13
+ readonly element: HTMLElement;
14
+ dispose(): void;
15
+ }
16
+ declare function addProviderSwitcher(parent: Pane | FolderApi, client: DevtoolsClient): ProviderSwitcherHandle;
17
+
18
+ export { type ProviderSwitcherHandle, addProviderSwitcher };
@@ -0,0 +1,18 @@
1
+ import { Pane, FolderApi } from 'tweakpane';
2
+ import { DevtoolsClient } from './devtools-client.js';
3
+ import 'three-flatland/debug-protocol';
4
+
5
+ /**
6
+ * Provider switcher — a single-row blade with ◀ / ▶ arrows + the
7
+ * selected provider's name. Hidden while only one provider is known
8
+ * (the common case); shown automatically whenever two or more appear
9
+ * on the discovery bus.
10
+ */
11
+
12
+ interface ProviderSwitcherHandle {
13
+ readonly element: HTMLElement;
14
+ dispose(): void;
15
+ }
16
+ declare function addProviderSwitcher(parent: Pane | FolderApi, client: DevtoolsClient): ProviderSwitcherHandle;
17
+
18
+ export { type ProviderSwitcherHandle, addProviderSwitcher };
@@ -0,0 +1,71 @@
1
+ function addProviderSwitcher(parent, client) {
2
+ const blade = parent.addBlade({ view: "separator" });
3
+ const bladeEl = blade.element;
4
+ bladeEl.innerHTML = "";
5
+ bladeEl.className = "tp-cntv";
6
+ bladeEl.style.cssText = "display:none";
7
+ const row = document.createElement("div");
8
+ row.style.cssText = [
9
+ "display:grid",
10
+ "grid-template-columns:auto 1fr auto",
11
+ "align-items:center",
12
+ "gap:6px",
13
+ "height:calc(var(--cnt-usz, 20px) * 1.1)",
14
+ "padding:0 6px",
15
+ "font-size:12px",
16
+ "line-height:1",
17
+ "color:var(--tp-label-foreground-color)",
18
+ "font-variant-numeric:tabular-nums",
19
+ "user-select:none",
20
+ "-webkit-user-select:none"
21
+ ].join(";");
22
+ const arrowStyle = "cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;";
23
+ const prevBtn = document.createElement("span");
24
+ prevBtn.textContent = "\u25C0";
25
+ prevBtn.style.cssText = arrowStyle;
26
+ prevBtn.setAttribute("role", "button");
27
+ prevBtn.setAttribute("aria-label", "Previous provider");
28
+ const label = document.createElement("span");
29
+ label.style.cssText = "text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
30
+ const nextBtn = document.createElement("span");
31
+ nextBtn.textContent = "\u25B6";
32
+ nextBtn.style.cssText = arrowStyle;
33
+ nextBtn.setAttribute("role", "button");
34
+ nextBtn.setAttribute("aria-label", "Next provider");
35
+ row.appendChild(prevBtn);
36
+ row.appendChild(label);
37
+ row.appendChild(nextBtn);
38
+ bladeEl.appendChild(row);
39
+ function step(delta) {
40
+ const providers = client.state.providers;
41
+ if (providers.length < 2) return;
42
+ const selected = client.state.selectedProviderId;
43
+ const idx = Math.max(0, providers.findIndex((p) => p.id === selected));
44
+ const next = providers[(idx + delta + providers.length) % providers.length];
45
+ client.selectProvider(next.id);
46
+ }
47
+ prevBtn.addEventListener("click", () => step(-1));
48
+ nextBtn.addEventListener("click", () => step(1));
49
+ const unsubscribe = client.addListener((s) => {
50
+ bladeEl.style.display = s.providers.length >= 2 ? "" : "none";
51
+ const current = s.providers.find((p) => p.id === s.selectedProviderId);
52
+ if (current) {
53
+ label.textContent = `${current.kind}:${current.name}`;
54
+ label.title = `${current.kind}:${current.name} (${current.id.slice(0, 8)})`;
55
+ } else {
56
+ label.textContent = "\u2014";
57
+ label.title = "";
58
+ }
59
+ });
60
+ return {
61
+ element: bladeEl,
62
+ dispose() {
63
+ unsubscribe();
64
+ blade.dispose();
65
+ }
66
+ };
67
+ }
68
+ export {
69
+ addProviderSwitcher
70
+ };
71
+ //# sourceMappingURL=provider-switcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider-switcher.ts"],"sourcesContent":["/**\n * Provider switcher — a single-row blade with ◀ / ▶ arrows + the\n * selected provider's name. Hidden while only one provider is known\n * (the common case); shown automatically whenever two or more appear\n * on the discovery bus.\n */\n\nimport type { FolderApi, Pane } from 'tweakpane'\n\nimport type { DevtoolsClient } from './devtools-client.js'\n\nexport interface ProviderSwitcherHandle {\n readonly element: HTMLElement\n dispose(): void\n}\n\nexport function addProviderSwitcher(\n parent: Pane | FolderApi,\n client: DevtoolsClient,\n): ProviderSwitcherHandle {\n // Separator-blade shim so the rack ordering stays correct; we swap\n // its innards for our own layout (same pattern as stats-graph / row).\n const blade = parent.addBlade({ view: 'separator' }) as unknown as {\n element: HTMLElement\n dispose(): void\n }\n const bladeEl = blade.element\n bladeEl.innerHTML = ''\n bladeEl.className = 'tp-cntv'\n bladeEl.style.cssText = 'display:none' // Hidden until >= 2 providers\n\n const row = document.createElement('div')\n row.style.cssText = [\n 'display:grid',\n 'grid-template-columns:auto 1fr auto',\n 'align-items:center',\n 'gap:6px',\n 'height:calc(var(--cnt-usz, 20px) * 1.1)',\n 'padding:0 6px',\n 'font-size:12px',\n 'line-height:1',\n 'color:var(--tp-label-foreground-color)',\n 'font-variant-numeric:tabular-nums',\n 'user-select:none',\n '-webkit-user-select:none',\n ].join(';')\n\n const arrowStyle =\n 'cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;'\n const prevBtn = document.createElement('span')\n prevBtn.textContent = '◀'\n prevBtn.style.cssText = arrowStyle\n prevBtn.setAttribute('role', 'button')\n prevBtn.setAttribute('aria-label', 'Previous provider')\n\n const label = document.createElement('span')\n label.style.cssText = 'text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'\n\n const nextBtn = document.createElement('span')\n nextBtn.textContent = '▶'\n nextBtn.style.cssText = arrowStyle\n nextBtn.setAttribute('role', 'button')\n nextBtn.setAttribute('aria-label', 'Next provider')\n\n row.appendChild(prevBtn)\n row.appendChild(label)\n row.appendChild(nextBtn)\n bladeEl.appendChild(row)\n\n function step(delta: number): void {\n const providers = client.state.providers\n if (providers.length < 2) return\n const selected = client.state.selectedProviderId\n const idx = Math.max(0, providers.findIndex((p) => p.id === selected))\n const next = providers[(idx + delta + providers.length) % providers.length]!\n client.selectProvider(next.id)\n }\n\n prevBtn.addEventListener('click', () => step(-1))\n nextBtn.addEventListener('click', () => step(1))\n\n const unsubscribe = client.addListener((s) => {\n // Visibility: only when multi-provider.\n bladeEl.style.display = s.providers.length >= 2 ? '' : 'none'\n const current = s.providers.find((p) => p.id === s.selectedProviderId)\n if (current) {\n // Prefix with kind so the user can tell flatland's system provider\n // apart from a user provider with the same display name.\n label.textContent = `${current.kind}:${current.name}`\n label.title = `${current.kind}:${current.name} (${current.id.slice(0, 8)})`\n } else {\n label.textContent = '—'\n label.title = ''\n }\n })\n\n return {\n element: bladeEl,\n dispose() {\n unsubscribe()\n blade.dispose()\n },\n }\n}\n"],"mappings":"AAgBO,SAAS,oBACd,QACA,QACwB;AAGxB,QAAM,QAAQ,OAAO,SAAS,EAAE,MAAM,YAAY,CAAC;AAInD,QAAM,UAAU,MAAM;AACtB,UAAQ,YAAY;AACpB,UAAQ,YAAY;AACpB,UAAQ,MAAM,UAAU;AAExB,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,MAAM,UAAU;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,QAAM,aACJ;AACF,QAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,UAAQ,cAAc;AACtB,UAAQ,MAAM,UAAU;AACxB,UAAQ,aAAa,QAAQ,QAAQ;AACrC,UAAQ,aAAa,cAAc,mBAAmB;AAEtD,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,MAAM,UAAU;AAEtB,QAAM,UAAU,SAAS,cAAc,MAAM;AAC7C,UAAQ,cAAc;AACtB,UAAQ,MAAM,UAAU;AACxB,UAAQ,aAAa,QAAQ,QAAQ;AACrC,UAAQ,aAAa,cAAc,eAAe;AAElD,MAAI,YAAY,OAAO;AACvB,MAAI,YAAY,KAAK;AACrB,MAAI,YAAY,OAAO;AACvB,UAAQ,YAAY,GAAG;AAEvB,WAAS,KAAK,OAAqB;AACjC,UAAM,YAAY,OAAO,MAAM;AAC/B,QAAI,UAAU,SAAS,EAAG;AAC1B,UAAM,WAAW,OAAO,MAAM;AAC9B,UAAM,MAAM,KAAK,IAAI,GAAG,UAAU,UAAU,CAAC,MAAM,EAAE,OAAO,QAAQ,CAAC;AACrE,UAAM,OAAO,WAAW,MAAM,QAAQ,UAAU,UAAU,UAAU,MAAM;AAC1E,WAAO,eAAe,KAAK,EAAE;AAAA,EAC/B;AAEA,UAAQ,iBAAiB,SAAS,MAAM,KAAK,EAAE,CAAC;AAChD,UAAQ,iBAAiB,SAAS,MAAM,KAAK,CAAC,CAAC;AAE/C,QAAM,cAAc,OAAO,YAAY,CAAC,MAAM;AAE5C,YAAQ,MAAM,UAAU,EAAE,UAAU,UAAU,IAAI,KAAK;AACvD,UAAM,UAAU,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,kBAAkB;AACrE,QAAI,SAAS;AAGX,YAAM,cAAc,GAAG,QAAQ,IAAI,IAAI,QAAQ,IAAI;AACnD,YAAM,QAAQ,GAAG,QAAQ,IAAI,IAAI,QAAQ,IAAI,KAAK,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC;AAAA,IAC1E,OAAO;AACL,YAAM,cAAc;AACpB,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AACR,kBAAY;AACZ,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var devtools_provider_exports = {};
20
+ __export(devtools_provider_exports, {
21
+ DevtoolsProvider: () => DevtoolsProvider
22
+ });
23
+ module.exports = __toCommonJS(devtools_provider_exports);
24
+ var import_jsx_runtime = require("react/jsx-runtime");
25
+ var import_react = require("react");
26
+ var import_fiber = require("@react-three/fiber");
27
+ var import_three_flatland = require("three-flatland");
28
+ function DevtoolsProvider(props) {
29
+ if (process.env.NODE_ENV === "production" && process.env.FL_DEVTOOLS !== "true") return null;
30
+ if (!(0, import_three_flatland.isDevtoolsActive)()) return null;
31
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DevtoolsProviderActive, { ...props });
32
+ }
33
+ function DevtoolsProviderActive({
34
+ name,
35
+ id,
36
+ discoveryChannelName
37
+ }) {
38
+ const handleRef = (0, import_react.useRef)(null);
39
+ (0, import_fiber.useFrame)((state) => {
40
+ const h = handleRef.current;
41
+ if (!h || h.disposed) return;
42
+ h.endFrame(state.gl);
43
+ h.beginFrame(performance.now(), state.gl);
44
+ });
45
+ (0, import_react.useEffect)(() => {
46
+ const handle = (0, import_three_flatland.createDevtoolsProvider)({ name, id, discoveryChannelName });
47
+ handleRef.current = handle;
48
+ return () => {
49
+ handle.dispose();
50
+ if (handleRef.current === handle) handleRef.current = null;
51
+ };
52
+ }, [name, id, discoveryChannelName]);
53
+ return null;
54
+ }
55
+ // Annotate the CommonJS export names for ESM import in node:
56
+ 0 && (module.exports = {
57
+ DevtoolsProvider
58
+ });
59
+ //# sourceMappingURL=devtools-provider.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/devtools-provider.tsx"],"sourcesContent":["import { useEffect, useRef, type ReactElement } from 'react'\nimport { useFrame } from '@react-three/fiber'\nimport {\n createDevtoolsProvider,\n isDevtoolsActive,\n type DevtoolsProviderHandle,\n} from 'three-flatland'\n\n// Types the build-time `process.env` reads without requiring @types/node (shadows the global where present; erased at compile).\ndeclare const process: { env: { NODE_ENV?: string; FL_DEVTOOLS?: string } }\n\nexport interface DevtoolsProviderProps {\n /** Display name shown in the consumer's provider switcher. */\n name?: string\n /** Optional explicit provider id (defaults to a fresh uuid). */\n id?: string\n /** Override the discovery channel name (advanced). */\n discoveryChannelName?: string\n}\n\n/**\n * Mount inside `<Canvas>` to publish stats / registry / buffer feeds.\n * Drop it next to your scene — no children, no wrapping:\n *\n * ```tsx\n * <Canvas>\n * <DevtoolsProvider name=\"basic-sprite\" />\n * <Scene />\n * </Canvas>\n * ```\n *\n * Passive sampler — does NOT take over rendering. Registers a\n * default-phase `useFrame` and snaps `endFrame → beginFrame` once per\n * tick, leaving R3F's auto-render (and any explicit render useFrame\n * like Flatland's `flatland.render(gl)`) completely alone. The\n * `cpuMs` value reported is the inter-tick interval — equivalent to\n * frame time at 60fps, slightly looser than Flatland's internal\n * tight-bracket measurement when stressed (Flatland's number isolates\n * just its `render()` work; ours covers the whole rAF interval).\n *\n * **Safe to leave in production.** The outer component short-circuits\n * when the devtools build gate is false (build-time, terser folds the\n * branch away) or when `isDevtoolsActive()` is false (runtime toggle). In\n * those cases the inner component never mounts, no `useFrame` is\n * registered, and R3F's normal auto-render path is untouched.\n */\nexport function DevtoolsProvider(\n props: DevtoolsProviderProps,\n): ReactElement | null {\n if (process.env.NODE_ENV === 'production' && process.env.FL_DEVTOOLS !== 'true') return null\n if (!isDevtoolsActive()) return null\n return <DevtoolsProviderActive {...props} />\n}\n\nfunction DevtoolsProviderActive({\n name,\n id,\n discoveryChannelName,\n}: DevtoolsProviderProps): null {\n // Provider creation lives in `useEffect` only — never in render. R3F's\n // concurrent-rendering + StrictMode double-render can throw away renders\n // before commit, and any side effect we performed in render (like\n // opening BroadcastChannels) would leak as an orphan. Keeping the only\n // construction site inside the committed effect guarantees one live\n // provider per mounted component instance.\n const handleRef = useRef<DevtoolsProviderHandle | null>(null)\n\n useFrame((state) => {\n const h = handleRef.current\n if (!h || h.disposed) return\n // Default-phase `useFrame` runs every rAF tick BEFORE R3F's\n // auto-render (or whatever render useFrame the example registered).\n // We close the previous tick's window and open the next one, so\n // `cpuMs` measures the full inter-tick interval. We deliberately\n // do NOT use `phase: 'render'` — that would steal R3F's render\n // slot and prevent users from driving their own render path\n // (Flatland's `flatland.render(gl)`, custom passes, etc.).\n h.endFrame(state.gl as never)\n h.beginFrame(performance.now(), state.gl as never)\n })\n\n useEffect(() => {\n const handle = createDevtoolsProvider({ name, id, discoveryChannelName })\n handleRef.current = handle\n return () => {\n handle.dispose()\n if (handleRef.current === handle) handleRef.current = null\n }\n }, [name, id, discoveryChannelName])\n\n return null\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmDS;AAnDT,mBAAqD;AACrD,mBAAyB;AACzB,4BAIO;AAwCA,SAAS,iBACd,OACqB;AACrB,MAAI,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,gBAAgB,OAAQ,QAAO;AACxF,MAAI,KAAC,wCAAiB,EAAG,QAAO;AAChC,SAAO,4CAAC,0BAAwB,GAAG,OAAO;AAC5C;AAEA,SAAS,uBAAuB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAAgC;AAO9B,QAAM,gBAAY,qBAAsC,IAAI;AAE5D,6BAAS,CAAC,UAAU;AAClB,UAAM,IAAI,UAAU;AACpB,QAAI,CAAC,KAAK,EAAE,SAAU;AAQtB,MAAE,SAAS,MAAM,EAAW;AAC5B,MAAE,WAAW,YAAY,IAAI,GAAG,MAAM,EAAW;AAAA,EACnD,CAAC;AAED,8BAAU,MAAM;AACd,UAAM,aAAS,8CAAuB,EAAE,MAAM,IAAI,qBAAqB,CAAC;AACxE,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,UAAI,UAAU,YAAY,OAAQ,WAAU,UAAU;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,MAAM,IAAI,oBAAoB,CAAC;AAEnC,SAAO;AACT;","names":[]}
@@ -0,0 +1,39 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ interface DevtoolsProviderProps {
4
+ /** Display name shown in the consumer's provider switcher. */
5
+ name?: string;
6
+ /** Optional explicit provider id (defaults to a fresh uuid). */
7
+ id?: string;
8
+ /** Override the discovery channel name (advanced). */
9
+ discoveryChannelName?: string;
10
+ }
11
+ /**
12
+ * Mount inside `<Canvas>` to publish stats / registry / buffer feeds.
13
+ * Drop it next to your scene — no children, no wrapping:
14
+ *
15
+ * ```tsx
16
+ * <Canvas>
17
+ * <DevtoolsProvider name="basic-sprite" />
18
+ * <Scene />
19
+ * </Canvas>
20
+ * ```
21
+ *
22
+ * Passive sampler — does NOT take over rendering. Registers a
23
+ * default-phase `useFrame` and snaps `endFrame → beginFrame` once per
24
+ * tick, leaving R3F's auto-render (and any explicit render useFrame
25
+ * like Flatland's `flatland.render(gl)`) completely alone. The
26
+ * `cpuMs` value reported is the inter-tick interval — equivalent to
27
+ * frame time at 60fps, slightly looser than Flatland's internal
28
+ * tight-bracket measurement when stressed (Flatland's number isolates
29
+ * just its `render()` work; ours covers the whole rAF interval).
30
+ *
31
+ * **Safe to leave in production.** The outer component short-circuits
32
+ * when the devtools build gate is false (build-time, terser folds the
33
+ * branch away) or when `isDevtoolsActive()` is false (runtime toggle). In
34
+ * those cases the inner component never mounts, no `useFrame` is
35
+ * registered, and R3F's normal auto-render path is untouched.
36
+ */
37
+ declare function DevtoolsProvider(props: DevtoolsProviderProps): ReactElement | null;
38
+
39
+ export { DevtoolsProvider, type DevtoolsProviderProps };
@@ -0,0 +1,39 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ interface DevtoolsProviderProps {
4
+ /** Display name shown in the consumer's provider switcher. */
5
+ name?: string;
6
+ /** Optional explicit provider id (defaults to a fresh uuid). */
7
+ id?: string;
8
+ /** Override the discovery channel name (advanced). */
9
+ discoveryChannelName?: string;
10
+ }
11
+ /**
12
+ * Mount inside `<Canvas>` to publish stats / registry / buffer feeds.
13
+ * Drop it next to your scene — no children, no wrapping:
14
+ *
15
+ * ```tsx
16
+ * <Canvas>
17
+ * <DevtoolsProvider name="basic-sprite" />
18
+ * <Scene />
19
+ * </Canvas>
20
+ * ```
21
+ *
22
+ * Passive sampler — does NOT take over rendering. Registers a
23
+ * default-phase `useFrame` and snaps `endFrame → beginFrame` once per
24
+ * tick, leaving R3F's auto-render (and any explicit render useFrame
25
+ * like Flatland's `flatland.render(gl)`) completely alone. The
26
+ * `cpuMs` value reported is the inter-tick interval — equivalent to
27
+ * frame time at 60fps, slightly looser than Flatland's internal
28
+ * tight-bracket measurement when stressed (Flatland's number isolates
29
+ * just its `render()` work; ours covers the whole rAF interval).
30
+ *
31
+ * **Safe to leave in production.** The outer component short-circuits
32
+ * when the devtools build gate is false (build-time, terser folds the
33
+ * branch away) or when `isDevtoolsActive()` is false (runtime toggle). In
34
+ * those cases the inner component never mounts, no `useFrame` is
35
+ * registered, and R3F's normal auto-render path is untouched.
36
+ */
37
+ declare function DevtoolsProvider(props: DevtoolsProviderProps): ReactElement | null;
38
+
39
+ export { DevtoolsProvider, type DevtoolsProviderProps };
@@ -0,0 +1,38 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { useFrame } from "@react-three/fiber";
4
+ import {
5
+ createDevtoolsProvider,
6
+ isDevtoolsActive
7
+ } from "three-flatland";
8
+ function DevtoolsProvider(props) {
9
+ if (process.env.NODE_ENV === "production" && process.env.FL_DEVTOOLS !== "true") return null;
10
+ if (!isDevtoolsActive()) return null;
11
+ return /* @__PURE__ */ jsx(DevtoolsProviderActive, { ...props });
12
+ }
13
+ function DevtoolsProviderActive({
14
+ name,
15
+ id,
16
+ discoveryChannelName
17
+ }) {
18
+ const handleRef = useRef(null);
19
+ useFrame((state) => {
20
+ const h = handleRef.current;
21
+ if (!h || h.disposed) return;
22
+ h.endFrame(state.gl);
23
+ h.beginFrame(performance.now(), state.gl);
24
+ });
25
+ useEffect(() => {
26
+ const handle = createDevtoolsProvider({ name, id, discoveryChannelName });
27
+ handleRef.current = handle;
28
+ return () => {
29
+ handle.dispose();
30
+ if (handleRef.current === handle) handleRef.current = null;
31
+ };
32
+ }, [name, id, discoveryChannelName]);
33
+ return null;
34
+ }
35
+ export {
36
+ DevtoolsProvider
37
+ };
38
+ //# sourceMappingURL=devtools-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/devtools-provider.tsx"],"sourcesContent":["import { useEffect, useRef, type ReactElement } from 'react'\nimport { useFrame } from '@react-three/fiber'\nimport {\n createDevtoolsProvider,\n isDevtoolsActive,\n type DevtoolsProviderHandle,\n} from 'three-flatland'\n\n// Types the build-time `process.env` reads without requiring @types/node (shadows the global where present; erased at compile).\ndeclare const process: { env: { NODE_ENV?: string; FL_DEVTOOLS?: string } }\n\nexport interface DevtoolsProviderProps {\n /** Display name shown in the consumer's provider switcher. */\n name?: string\n /** Optional explicit provider id (defaults to a fresh uuid). */\n id?: string\n /** Override the discovery channel name (advanced). */\n discoveryChannelName?: string\n}\n\n/**\n * Mount inside `<Canvas>` to publish stats / registry / buffer feeds.\n * Drop it next to your scene — no children, no wrapping:\n *\n * ```tsx\n * <Canvas>\n * <DevtoolsProvider name=\"basic-sprite\" />\n * <Scene />\n * </Canvas>\n * ```\n *\n * Passive sampler — does NOT take over rendering. Registers a\n * default-phase `useFrame` and snaps `endFrame → beginFrame` once per\n * tick, leaving R3F's auto-render (and any explicit render useFrame\n * like Flatland's `flatland.render(gl)`) completely alone. The\n * `cpuMs` value reported is the inter-tick interval — equivalent to\n * frame time at 60fps, slightly looser than Flatland's internal\n * tight-bracket measurement when stressed (Flatland's number isolates\n * just its `render()` work; ours covers the whole rAF interval).\n *\n * **Safe to leave in production.** The outer component short-circuits\n * when the devtools build gate is false (build-time, terser folds the\n * branch away) or when `isDevtoolsActive()` is false (runtime toggle). In\n * those cases the inner component never mounts, no `useFrame` is\n * registered, and R3F's normal auto-render path is untouched.\n */\nexport function DevtoolsProvider(\n props: DevtoolsProviderProps,\n): ReactElement | null {\n if (process.env.NODE_ENV === 'production' && process.env.FL_DEVTOOLS !== 'true') return null\n if (!isDevtoolsActive()) return null\n return <DevtoolsProviderActive {...props} />\n}\n\nfunction DevtoolsProviderActive({\n name,\n id,\n discoveryChannelName,\n}: DevtoolsProviderProps): null {\n // Provider creation lives in `useEffect` only — never in render. R3F's\n // concurrent-rendering + StrictMode double-render can throw away renders\n // before commit, and any side effect we performed in render (like\n // opening BroadcastChannels) would leak as an orphan. Keeping the only\n // construction site inside the committed effect guarantees one live\n // provider per mounted component instance.\n const handleRef = useRef<DevtoolsProviderHandle | null>(null)\n\n useFrame((state) => {\n const h = handleRef.current\n if (!h || h.disposed) return\n // Default-phase `useFrame` runs every rAF tick BEFORE R3F's\n // auto-render (or whatever render useFrame the example registered).\n // We close the previous tick's window and open the next one, so\n // `cpuMs` measures the full inter-tick interval. We deliberately\n // do NOT use `phase: 'render'` — that would steal R3F's render\n // slot and prevent users from driving their own render path\n // (Flatland's `flatland.render(gl)`, custom passes, etc.).\n h.endFrame(state.gl as never)\n h.beginFrame(performance.now(), state.gl as never)\n })\n\n useEffect(() => {\n const handle = createDevtoolsProvider({ name, id, discoveryChannelName })\n handleRef.current = handle\n return () => {\n handle.dispose()\n if (handleRef.current === handle) handleRef.current = null\n }\n }, [name, id, discoveryChannelName])\n\n return null\n}\n"],"mappings":"AAmDS;AAnDT,SAAS,WAAW,cAAiC;AACrD,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAwCA,SAAS,iBACd,OACqB;AACrB,MAAI,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,gBAAgB,OAAQ,QAAO;AACxF,MAAI,CAAC,iBAAiB,EAAG,QAAO;AAChC,SAAO,oBAAC,0BAAwB,GAAG,OAAO;AAC5C;AAEA,SAAS,uBAAuB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAAgC;AAO9B,QAAM,YAAY,OAAsC,IAAI;AAE5D,WAAS,CAAC,UAAU;AAClB,UAAM,IAAI,UAAU;AACpB,QAAI,CAAC,KAAK,EAAE,SAAU;AAQtB,MAAE,SAAS,MAAM,EAAW;AAC5B,MAAE,WAAW,YAAY,IAAI,GAAG,MAAM,EAAW;AAAA,EACnD,CAAC;AAED,YAAU,MAAM;AACd,UAAM,SAAS,uBAAuB,EAAE,MAAM,IAAI,qBAAqB,CAAC;AACxE,cAAU,UAAU;AACpB,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,UAAI,UAAU,YAAY,OAAQ,WAAU,UAAU;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,MAAM,IAAI,oBAAoB,CAAC;AAEnC,SAAO;AACT;","names":[]}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var use_fps_graph_exports = {};
20
+ __export(use_fps_graph_exports, {
21
+ useFpsGraph: () => useFpsGraph
22
+ });
23
+ module.exports = __toCommonJS(use_fps_graph_exports);
24
+ var import_react = require("react");
25
+ function useFpsGraph(parent) {
26
+ const graphRef = (0, import_react.useRef)(null);
27
+ (0, import_react.useEffect)(() => {
28
+ if (!parent) return;
29
+ const graph = parent.addBlade({
30
+ view: "fpsgraph",
31
+ label: "fps",
32
+ rows: 2
33
+ });
34
+ graphRef.current = graph;
35
+ return () => {
36
+ graph.dispose();
37
+ graphRef.current = null;
38
+ };
39
+ }, [parent]);
40
+ return {
41
+ begin: () => graphRef.current?.begin(),
42
+ end: () => graphRef.current?.end()
43
+ };
44
+ }
45
+ // Annotate the CommonJS export names for ESM import in node:
46
+ 0 && (module.exports = {
47
+ useFpsGraph
48
+ });
49
+ //# sourceMappingURL=use-fps-graph.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/use-fps-graph.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { FpsGraphBladeApi } from '@tweakpane/plugin-essentials'\nimport type { PaneParent } from './use-pane-input.js'\n\nexport interface FpsGraphHandle {\n begin: () => void\n end: () => void\n}\n\n/**\n * Add an FPS graph blade. Returns { begin, end } for use inside useFrame.\n * If the pane was created with `fps: true` (default), you can skip this hook\n * and use paneBundle.fpsGraph directly.\n */\nexport function useFpsGraph(parent: PaneParent | null): FpsGraphHandle {\n const graphRef = useRef<FpsGraphBladeApi | null>(null)\n\n useEffect(() => {\n if (!parent) return\n\n const graph = parent.addBlade({\n view: 'fpsgraph',\n label: 'fps',\n rows: 2,\n }) as FpsGraphBladeApi\n\n graphRef.current = graph\n\n return () => {\n graph.dispose()\n graphRef.current = null\n }\n }, [parent])\n\n return {\n begin: () => graphRef.current?.begin(),\n end: () => graphRef.current?.end(),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAkC;AAc3B,SAAS,YAAY,QAA2C;AACrE,QAAM,eAAW,qBAAgC,IAAI;AAErD,8BAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AAEb,UAAM,QAAQ,OAAO,SAAS;AAAA,MAC5B,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,IACR,CAAC;AAED,aAAS,UAAU;AAEnB,WAAO,MAAM;AACX,YAAM,QAAQ;AACd,eAAS,UAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,SAAO;AAAA,IACL,OAAO,MAAM,SAAS,SAAS,MAAM;AAAA,IACrC,KAAK,MAAM,SAAS,SAAS,IAAI;AAAA,EACnC;AACF;","names":[]}
@@ -0,0 +1,15 @@
1
+ import { PaneParent } from './use-pane-input.cjs';
2
+ import 'tweakpane';
3
+
4
+ interface FpsGraphHandle {
5
+ begin: () => void;
6
+ end: () => void;
7
+ }
8
+ /**
9
+ * Add an FPS graph blade. Returns { begin, end } for use inside useFrame.
10
+ * If the pane was created with `fps: true` (default), you can skip this hook
11
+ * and use paneBundle.fpsGraph directly.
12
+ */
13
+ declare function useFpsGraph(parent: PaneParent | null): FpsGraphHandle;
14
+
15
+ export { type FpsGraphHandle, useFpsGraph };
@@ -0,0 +1,15 @@
1
+ import { PaneParent } from './use-pane-input.js';
2
+ import 'tweakpane';
3
+
4
+ interface FpsGraphHandle {
5
+ begin: () => void;
6
+ end: () => void;
7
+ }
8
+ /**
9
+ * Add an FPS graph blade. Returns { begin, end } for use inside useFrame.
10
+ * If the pane was created with `fps: true` (default), you can skip this hook
11
+ * and use paneBundle.fpsGraph directly.
12
+ */
13
+ declare function useFpsGraph(parent: PaneParent | null): FpsGraphHandle;
14
+
15
+ export { type FpsGraphHandle, useFpsGraph };
@@ -0,0 +1,25 @@
1
+ import { useEffect, useRef } from "react";
2
+ function useFpsGraph(parent) {
3
+ const graphRef = useRef(null);
4
+ useEffect(() => {
5
+ if (!parent) return;
6
+ const graph = parent.addBlade({
7
+ view: "fpsgraph",
8
+ label: "fps",
9
+ rows: 2
10
+ });
11
+ graphRef.current = graph;
12
+ return () => {
13
+ graph.dispose();
14
+ graphRef.current = null;
15
+ };
16
+ }, [parent]);
17
+ return {
18
+ begin: () => graphRef.current?.begin(),
19
+ end: () => graphRef.current?.end()
20
+ };
21
+ }
22
+ export {
23
+ useFpsGraph
24
+ };
25
+ //# sourceMappingURL=use-fps-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/use-fps-graph.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { FpsGraphBladeApi } from '@tweakpane/plugin-essentials'\nimport type { PaneParent } from './use-pane-input.js'\n\nexport interface FpsGraphHandle {\n begin: () => void\n end: () => void\n}\n\n/**\n * Add an FPS graph blade. Returns { begin, end } for use inside useFrame.\n * If the pane was created with `fps: true` (default), you can skip this hook\n * and use paneBundle.fpsGraph directly.\n */\nexport function useFpsGraph(parent: PaneParent | null): FpsGraphHandle {\n const graphRef = useRef<FpsGraphBladeApi | null>(null)\n\n useEffect(() => {\n if (!parent) return\n\n const graph = parent.addBlade({\n view: 'fpsgraph',\n label: 'fps',\n rows: 2,\n }) as FpsGraphBladeApi\n\n graphRef.current = graph\n\n return () => {\n graph.dispose()\n graphRef.current = null\n }\n }, [parent])\n\n return {\n begin: () => graphRef.current?.begin(),\n end: () => graphRef.current?.end(),\n }\n}\n"],"mappings":"AAAA,SAAS,WAAW,cAAc;AAc3B,SAAS,YAAY,QAA2C;AACrE,QAAM,WAAW,OAAgC,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AAEb,UAAM,QAAQ,OAAO,SAAS;AAAA,MAC5B,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,IACR,CAAC;AAED,aAAS,UAAU;AAEnB,WAAO,MAAM;AACX,YAAM,QAAQ;AACd,eAAS,UAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,SAAO;AAAA,IACL,OAAO,MAAM,SAAS,SAAS,MAAM;AAAA,IACrC,KAAK,MAAM,SAAS,SAAS,IAAI;AAAA,EACnC;AACF;","names":[]}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var use_pane_button_exports = {};
20
+ __export(use_pane_button_exports, {
21
+ usePaneButton: () => usePaneButton
22
+ });
23
+ module.exports = __toCommonJS(use_pane_button_exports);
24
+ var import_react = require("react");
25
+ function usePaneButton(parent, title, onClick) {
26
+ const callbackRef = (0, import_react.useRef)(onClick);
27
+ callbackRef.current = onClick;
28
+ const buttonRef = (0, import_react.useRef)(null);
29
+ const mountedRef = (0, import_react.useRef)(false);
30
+ if (parent && buttonRef.current === null) {
31
+ const button = parent.addButton({ title });
32
+ button.on("click", () => callbackRef.current());
33
+ buttonRef.current = button;
34
+ }
35
+ (0, import_react.useEffect)(() => {
36
+ mountedRef.current = true;
37
+ if (parent && buttonRef.current === null) {
38
+ const button = parent.addButton({ title });
39
+ button.on("click", () => callbackRef.current());
40
+ buttonRef.current = button;
41
+ }
42
+ return () => {
43
+ const button = buttonRef.current;
44
+ setTimeout(() => {
45
+ if (!mountedRef.current && button) {
46
+ button.dispose();
47
+ if (buttonRef.current === button) buttonRef.current = null;
48
+ }
49
+ }, 0);
50
+ mountedRef.current = false;
51
+ };
52
+ }, [parent]);
53
+ }
54
+ // Annotate the CommonJS export names for ESM import in node:
55
+ 0 && (module.exports = {
56
+ usePaneButton
57
+ });
58
+ //# sourceMappingURL=use-pane-button.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/use-pane-button.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { PaneParent } from './use-pane-input.js'\n\n/**\n * Add a button to a Tweakpane parent. Uses a ref for the callback to avoid stale closures.\n * Uses deferred disposal to survive React strict mode.\n */\nexport function usePaneButton(\n parent: PaneParent | null,\n title: string,\n onClick: () => void,\n): void {\n const callbackRef = useRef(onClick)\n callbackRef.current = onClick\n const buttonRef = useRef<{ dispose(): void } | null>(null)\n const mountedRef = useRef(false)\n\n if (parent && buttonRef.current === null) {\n const button = parent.addButton({ title })\n button.on('click', () => callbackRef.current())\n buttonRef.current = button\n }\n\n useEffect(() => {\n mountedRef.current = true\n\n if (parent && buttonRef.current === null) {\n const button = parent.addButton({ title })\n button.on('click', () => callbackRef.current())\n buttonRef.current = button\n }\n\n return () => {\n const button = buttonRef.current\n setTimeout(() => {\n if (!mountedRef.current && button) {\n button.dispose()\n if (buttonRef.current === button) buttonRef.current = null\n }\n }, 0)\n mountedRef.current = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parent])\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAkC;AAO3B,SAAS,cACd,QACA,OACA,SACM;AACN,QAAM,kBAAc,qBAAO,OAAO;AAClC,cAAY,UAAU;AACtB,QAAM,gBAAY,qBAAmC,IAAI;AACzD,QAAM,iBAAa,qBAAO,KAAK;AAE/B,MAAI,UAAU,UAAU,YAAY,MAAM;AACxC,UAAM,SAAS,OAAO,UAAU,EAAE,MAAM,CAAC;AACzC,WAAO,GAAG,SAAS,MAAM,YAAY,QAAQ,CAAC;AAC9C,cAAU,UAAU;AAAA,EACtB;AAEA,8BAAU,MAAM;AACd,eAAW,UAAU;AAErB,QAAI,UAAU,UAAU,YAAY,MAAM;AACxC,YAAM,SAAS,OAAO,UAAU,EAAE,MAAM,CAAC;AACzC,aAAO,GAAG,SAAS,MAAM,YAAY,QAAQ,CAAC;AAC9C,gBAAU,UAAU;AAAA,IACtB;AAEA,WAAO,MAAM;AACX,YAAM,SAAS,UAAU;AACzB,iBAAW,MAAM;AACf,YAAI,CAAC,WAAW,WAAW,QAAQ;AACjC,iBAAO,QAAQ;AACf,cAAI,UAAU,YAAY,OAAQ,WAAU,UAAU;AAAA,QACxD;AAAA,MACF,GAAG,CAAC;AACJ,iBAAW,UAAU;AAAA,IACvB;AAAA,EAEF,GAAG,CAAC,MAAM,CAAC;AACb;","names":[]}
@@ -0,0 +1,10 @@
1
+ import { PaneParent } from './use-pane-input.cjs';
2
+ import 'tweakpane';
3
+
4
+ /**
5
+ * Add a button to a Tweakpane parent. Uses a ref for the callback to avoid stale closures.
6
+ * Uses deferred disposal to survive React strict mode.
7
+ */
8
+ declare function usePaneButton(parent: PaneParent | null, title: string, onClick: () => void): void;
9
+
10
+ export { usePaneButton };
@@ -0,0 +1,10 @@
1
+ import { PaneParent } from './use-pane-input.js';
2
+ import 'tweakpane';
3
+
4
+ /**
5
+ * Add a button to a Tweakpane parent. Uses a ref for the callback to avoid stale closures.
6
+ * Uses deferred disposal to survive React strict mode.
7
+ */
8
+ declare function usePaneButton(parent: PaneParent | null, title: string, onClick: () => void): void;
9
+
10
+ export { usePaneButton };