@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
package/dist/theme.js ADDED
@@ -0,0 +1,235 @@
1
+ const FLATLAND_THEME = {
2
+ // Backgrounds — retro darks
3
+ "--tp-base-background-color": "#00021c",
4
+ // retro-midnight
5
+ "--tp-container-background-color": "rgba(28, 40, 77, 0.4)",
6
+ // retro-deep-purple @ 40%
7
+ "--tp-input-background-color": "rgba(0, 2, 28, 0.6)",
8
+ // retro-black @ 60%
9
+ "--tp-monitor-background-color": "rgba(0, 2, 28, 0.4)",
10
+ // retro-black @ 40%
11
+ // Text — retro whites and greys
12
+ "--tp-container-foreground-color": "#f0edd8",
13
+ // retro-white
14
+ "--tp-button-foreground-color": "#f0edd8",
15
+ // retro-white
16
+ "--tp-input-foreground-color": "#c8c5b4",
17
+ // muted retro-white
18
+ "--tp-label-foreground-color": "rgba(240, 237, 216, 0.55)",
19
+ // retro-white @ 55%
20
+ "--tp-monitor-foreground-color": "#c8c5b4",
21
+ // muted retro-white
22
+ // Buttons — retro-deep-purple
23
+ "--tp-button-background-color": "#1c284d",
24
+ // retro-deep-purple
25
+ // Accent — slider grooves & separators
26
+ "--tp-groove-foreground-color": "rgba(240, 237, 216, 0.12)",
27
+ // muted separator/groove
28
+ // Typography
29
+ "--tp-base-font-family": "monospace"
30
+ };
31
+ const ACCENT_CSS = `
32
+ /* Slider thumb */
33
+ .tp-sldv_k::after { background-color: #d94c87 !important; }
34
+ .tp-sldv_t:hover .tp-sldv_k::after { background-color: #e0609a !important; }
35
+ .tp-sldv_t:active .tp-sldv_k::after { background-color: #a6216e !important; }
36
+ /* Slider track (filled portion) */
37
+ .tp-sldv_k::before { background-color: rgba(240, 237, 216, 0.25) !important; }
38
+ /* Number spinner drag handle */
39
+ .tp-txtv_k::before { background-color: #d94c87 !important; }
40
+ /* Select/dropdown */
41
+ .tp-lstv_s { background-color: rgba(28, 40, 77, 0.6) !important; color: rgba(240, 237, 216, 0.5) !important; }
42
+ .tp-lstv_s:hover { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }
43
+ .tp-lstv_s:focus { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }
44
+ .tp-lstv_s:active { background-color: #343473 !important; color: #d94c87 !important; }
45
+ .tp-lstv_m { color: rgba(240, 237, 216, 0.5) !important; }
46
+ /* Buttons */
47
+ .tp-btnv_b { background-color: rgba(28, 40, 77, 0.6) !important; color: rgba(240, 237, 216, 0.5) !important; }
48
+ .tp-btnv_b:hover { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }
49
+ .tp-btnv_b:active { background-color: #343473 !important; color: #d94c87 !important; }
50
+ /* Radio grid buttons */
51
+ .tp-radv_b { background-color: rgba(28, 40, 77, 0.6) !important; color: rgba(240, 237, 216, 0.5) !important; }
52
+ .tp-radv_i:hover + .tp-radv_b { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }
53
+ .tp-radv_i:checked + .tp-radv_b { background-color: #1c284d !important; color: #d94c87 !important; }
54
+ .tp-radv_i:active + .tp-radv_b { background-color: #343473 !important; color: #f0edd8 !important; }
55
+ /* Checkbox \u2014 default background blends with the container, so bump to
56
+ the same surface as the select/button controls for a visible hit
57
+ target, and accent the check stroke on :checked. Markup:
58
+ <input.tp-ckbv_i> + <div.tp-ckbv_w><svg><path/></svg></div>
59
+
60
+ Tweakpane's hidden input ships at browser-default size (~13\xD713) at
61
+ top-left of the label, relying on <label>\u2192<input> click forwarding
62
+ to toggle from clicks on the visible box. That forwarding is flaky
63
+ under some pointer-events + z-index combinations (reports: clicks
64
+ sometimes register, sometimes don't). Stretching the input to cover
65
+ the entire visible box puts the hit target directly on the pointer,
66
+ no label forwarding required. */
67
+ .tp-ckbv_i {
68
+ /* Absolute positioning + zero opacity is Tweakpane's default; we
69
+ just add explicit size + cursor so the click area matches the
70
+ visible affordance exactly. */
71
+ width: var(--cnt-usz) !important;
72
+ height: var(--cnt-usz) !important;
73
+ margin: 0 !important;
74
+ cursor: pointer !important;
75
+ }
76
+ .tp-ckbv_w { background-color: rgba(28, 40, 77, 0.6) !important; }
77
+ .tp-ckbv_i:hover + .tp-ckbv_w { background-color: rgba(28, 40, 77, 0.9) !important; }
78
+ .tp-ckbv_i:focus + .tp-ckbv_w { background-color: rgba(28, 40, 77, 0.9) !important; }
79
+ .tp-ckbv_i:active + .tp-ckbv_w { background-color: #343473 !important; }
80
+ .tp-ckbv_i:checked + .tp-ckbv_w svg path { stroke: #d94c87 !important; }
81
+
82
+ /* Dimmable pane \u2014 slightly translucent when idle, opaque on hover or when pinned.
83
+ Hover-promotion is gated on devices that actually have a pointer (desktop),
84
+ so on touch devices the pin toggle is the only way to flip opacity. */
85
+ .tp-flatland-dimmable { opacity: 0.8; transition: opacity 750ms ease; }
86
+ .tp-flatland-dimmable.tp-flatland-pinned { opacity: 1; }
87
+ @media (hover: hover) {
88
+ .tp-flatland-dimmable:hover { opacity: 1; }
89
+ }
90
+
91
+ /* Pin in the pane header (left side, mirrors the collapse caret on the right) */
92
+ .tp-flatland-pin {
93
+ position: absolute;
94
+ left: var(--cnt-hp, 4px);
95
+ top: 0;
96
+ bottom: 0;
97
+ margin: auto;
98
+ width: var(--cnt-usz, 20px);
99
+ height: var(--cnt-usz, 20px);
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ color: rgba(240, 237, 216, 0.45);
104
+ cursor: pointer;
105
+ border-radius: 2px;
106
+ transition: color 150ms, background-color 150ms;
107
+ }
108
+ .tp-flatland-pin:hover { color: #d94c87; background: transparent; }
109
+ .tp-flatland-pin svg { display: block; }
110
+ .tp-flatland-pin svg circle { fill: none; stroke: currentColor; stroke-width: 1.6; }
111
+ .tp-flatland-pinned .tp-flatland-pin svg circle { fill: currentColor; }
112
+
113
+ /*
114
+ * Three-mode cycle toggle in the header (replaces Tweakpane's built-in
115
+ * fold caret). We hide the native caret via the .tp-rotv_m marker
116
+ * class and render our own button that cycles full - minimal -
117
+ * collapsed - full. The data-mode attribute drives icon styling.
118
+ */
119
+ .tp-rotv_m { display: none !important; }
120
+ .tp-flatland-mode {
121
+ position: absolute;
122
+ right: var(--cnt-hp, 4px);
123
+ top: 0;
124
+ bottom: 0;
125
+ margin: auto;
126
+ min-width: var(--cnt-usz, 20px);
127
+ height: var(--cnt-usz, 20px);
128
+ padding: 0 4px;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ /* Match the original tweakpane fold caret and folder-header glyphs \u2014
133
+ * the muted foreground token pane headers use throughout. */
134
+ color: var(--tp-label-foreground-color);
135
+ cursor: pointer;
136
+ font-size: 11px;
137
+ line-height: 1;
138
+ user-select: none;
139
+ }
140
+ /*
141
+ * Glyph exactly replicates tweakpane's native fold marker \u2014 a 6\xD76
142
+ * square with a horizontal linear-gradient that draws two vertical
143
+ * stripes (screw-head look). Rotating gives the three states:
144
+ * full \u2192 0\xB0 (vertical stripes, ||) \u2014 matches tweakpane's expanded
145
+ * minimal \u2192 45\xB0 (diagonal, like a flat-head screw)
146
+ * collapsed \u2192 90\xB0 (horizontal stripes, =) \u2014 matches tweakpane's collapsed
147
+ */
148
+ .tp-flatland-mode-glyph {
149
+ display: block;
150
+ width: 6px;
151
+ height: 6px;
152
+ border-radius: 2px;
153
+ background: linear-gradient(
154
+ to left,
155
+ var(--cnt-fg, currentColor),
156
+ var(--cnt-fg, currentColor) 2px,
157
+ transparent 2px,
158
+ transparent 4px,
159
+ var(--cnt-fg, currentColor) 4px
160
+ );
161
+ opacity: 0.5;
162
+ /* 0.2s ease-in-out \u2014 the exact transition tweakpane applies to its
163
+ * own fold marker. */
164
+ transition: transform 0.2s ease-in-out;
165
+ }
166
+ .tp-flatland-mode[data-mode="full"] .tp-flatland-mode-glyph { transform: rotate(0deg); }
167
+ .tp-flatland-mode[data-mode="minimal"] .tp-flatland-mode-glyph { transform: rotate(45deg); }
168
+ .tp-flatland-mode[data-mode="collapsed"] .tp-flatland-mode-glyph { transform: rotate(90deg); }
169
+
170
+ /*
171
+ * Minimal mode: keep only blades tagged with .tp-flatland-minimal-keep
172
+ * (stats graph + stats row). Non-keep blades animate max-height to 0
173
+ * and fade out so the whole pane visibly shrinks, matching tweakpane
174
+ * native fold feel. Values and bindings stay intact \u2014 DOM is just
175
+ * clipped.
176
+ *
177
+ * max-height uses a generous ceiling instead of a measured value so we
178
+ * can stay declarative; the transition duration matches the native
179
+ * pane fold for continuity.
180
+ */
181
+ /*
182
+ * Animation timings + easings mirror tweakpane's own fold exactly:
183
+ * .tp-rotv_c / .tp-fldv_c use
184
+ * height .2s ease-in-out, opacity .2s linear, padding .2s ease-in-out
185
+ * and on expand, opacity has a 0.2s delay so height finishes first
186
+ * then opacity fades in. The collapse direction has no delay \u2014 both
187
+ * animate together.
188
+ *
189
+ * We use max-height in place of height because content height isn't
190
+ * known at author time; with a generous ceiling the visual is
191
+ * indistinguishable from native height tweens.
192
+ */
193
+ .tp-rotv_c > *:not(.tp-flatland-minimal-keep) {
194
+ max-height: 1200px;
195
+ opacity: 1;
196
+ overflow: hidden;
197
+ transition:
198
+ max-height 0.2s ease-in-out,
199
+ opacity 0.2s linear 0.2s,
200
+ margin 0.2s ease-in-out,
201
+ padding 0.2s ease-in-out;
202
+ }
203
+ .tp-flatland-minimal .tp-rotv_c > *:not(.tp-flatland-minimal-keep) {
204
+ max-height: 0;
205
+ opacity: 0;
206
+ margin-top: 0 !important;
207
+ margin-bottom: 0 !important;
208
+ padding-top: 0 !important;
209
+ padding-bottom: 0 !important;
210
+ pointer-events: none;
211
+ /* On collapse: no delay on opacity \u2014 both animate together,
212
+ * matching tweakpane's non-expanded transition shape. */
213
+ transition:
214
+ max-height 0.2s ease-in-out,
215
+ opacity 0.2s linear,
216
+ margin 0.2s ease-in-out,
217
+ padding 0.2s ease-in-out;
218
+ }
219
+ `;
220
+ function applyTheme(element, theme = FLATLAND_THEME) {
221
+ for (const [key, value] of Object.entries(theme)) {
222
+ element.style.setProperty(key, value);
223
+ }
224
+ if (!element.querySelector("style[data-flatland]")) {
225
+ const style = document.createElement("style");
226
+ style.setAttribute("data-flatland", "");
227
+ style.textContent = ACCENT_CSS;
228
+ element.appendChild(style);
229
+ }
230
+ }
231
+ export {
232
+ FLATLAND_THEME,
233
+ applyTheme
234
+ };
235
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme.ts"],"sourcesContent":["/**\n * Flatland theme — based on the Jehkoba32 retro palette.\n *\n * Uses project dark/neutral colors for backgrounds, retro-white/grey for text,\n * and accent color only on control thumbs and slider grooves.\n */\nexport const FLATLAND_THEME: Record<string, string> = {\n // Backgrounds — retro darks\n '--tp-base-background-color': '#00021c', // retro-midnight\n '--tp-container-background-color': 'rgba(28, 40, 77, 0.4)', // retro-deep-purple @ 40%\n '--tp-input-background-color': 'rgba(0, 2, 28, 0.6)', // retro-black @ 60%\n '--tp-monitor-background-color': 'rgba(0, 2, 28, 0.4)', // retro-black @ 40%\n\n // Text — retro whites and greys\n '--tp-container-foreground-color': '#f0edd8', // retro-white\n '--tp-button-foreground-color': '#f0edd8', // retro-white\n '--tp-input-foreground-color': '#c8c5b4', // muted retro-white\n '--tp-label-foreground-color': 'rgba(240, 237, 216, 0.55)', // retro-white @ 55%\n '--tp-monitor-foreground-color': '#c8c5b4', // muted retro-white\n\n // Buttons — retro-deep-purple\n '--tp-button-background-color': '#1c284d', // retro-deep-purple\n\n // Accent — slider grooves & separators\n '--tp-groove-foreground-color': 'rgba(240, 237, 216, 0.12)', // muted separator/groove\n\n // Typography\n '--tp-base-font-family': 'monospace',\n}\n\n/** Accent CSS — colorizes slider thumb and number spinner drag handle */\nconst ACCENT_CSS = `\n/* Slider thumb */\n.tp-sldv_k::after { background-color: #d94c87 !important; }\n.tp-sldv_t:hover .tp-sldv_k::after { background-color: #e0609a !important; }\n.tp-sldv_t:active .tp-sldv_k::after { background-color: #a6216e !important; }\n/* Slider track (filled portion) */\n.tp-sldv_k::before { background-color: rgba(240, 237, 216, 0.25) !important; }\n/* Number spinner drag handle */\n.tp-txtv_k::before { background-color: #d94c87 !important; }\n/* Select/dropdown */\n.tp-lstv_s { background-color: rgba(28, 40, 77, 0.6) !important; color: rgba(240, 237, 216, 0.5) !important; }\n.tp-lstv_s:hover { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }\n.tp-lstv_s:focus { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }\n.tp-lstv_s:active { background-color: #343473 !important; color: #d94c87 !important; }\n.tp-lstv_m { color: rgba(240, 237, 216, 0.5) !important; }\n/* Buttons */\n.tp-btnv_b { background-color: rgba(28, 40, 77, 0.6) !important; color: rgba(240, 237, 216, 0.5) !important; }\n.tp-btnv_b:hover { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }\n.tp-btnv_b:active { background-color: #343473 !important; color: #d94c87 !important; }\n/* Radio grid buttons */\n.tp-radv_b { background-color: rgba(28, 40, 77, 0.6) !important; color: rgba(240, 237, 216, 0.5) !important; }\n.tp-radv_i:hover + .tp-radv_b { background-color: rgba(28, 40, 77, 0.9) !important; color: #f0edd8 !important; }\n.tp-radv_i:checked + .tp-radv_b { background-color: #1c284d !important; color: #d94c87 !important; }\n.tp-radv_i:active + .tp-radv_b { background-color: #343473 !important; color: #f0edd8 !important; }\n/* Checkbox — default background blends with the container, so bump to\n the same surface as the select/button controls for a visible hit\n target, and accent the check stroke on :checked. Markup:\n <input.tp-ckbv_i> + <div.tp-ckbv_w><svg><path/></svg></div>\n\n Tweakpane's hidden input ships at browser-default size (~13×13) at\n top-left of the label, relying on <label>→<input> click forwarding\n to toggle from clicks on the visible box. That forwarding is flaky\n under some pointer-events + z-index combinations (reports: clicks\n sometimes register, sometimes don't). Stretching the input to cover\n the entire visible box puts the hit target directly on the pointer,\n no label forwarding required. */\n.tp-ckbv_i {\n /* Absolute positioning + zero opacity is Tweakpane's default; we\n just add explicit size + cursor so the click area matches the\n visible affordance exactly. */\n width: var(--cnt-usz) !important;\n height: var(--cnt-usz) !important;\n margin: 0 !important;\n cursor: pointer !important;\n}\n.tp-ckbv_w { background-color: rgba(28, 40, 77, 0.6) !important; }\n.tp-ckbv_i:hover + .tp-ckbv_w { background-color: rgba(28, 40, 77, 0.9) !important; }\n.tp-ckbv_i:focus + .tp-ckbv_w { background-color: rgba(28, 40, 77, 0.9) !important; }\n.tp-ckbv_i:active + .tp-ckbv_w { background-color: #343473 !important; }\n.tp-ckbv_i:checked + .tp-ckbv_w svg path { stroke: #d94c87 !important; }\n\n/* Dimmable pane — slightly translucent when idle, opaque on hover or when pinned.\n Hover-promotion is gated on devices that actually have a pointer (desktop),\n so on touch devices the pin toggle is the only way to flip opacity. */\n.tp-flatland-dimmable { opacity: 0.8; transition: opacity 750ms ease; }\n.tp-flatland-dimmable.tp-flatland-pinned { opacity: 1; }\n@media (hover: hover) {\n .tp-flatland-dimmable:hover { opacity: 1; }\n}\n\n/* Pin in the pane header (left side, mirrors the collapse caret on the right) */\n.tp-flatland-pin {\n position: absolute;\n left: var(--cnt-hp, 4px);\n top: 0;\n bottom: 0;\n margin: auto;\n width: var(--cnt-usz, 20px);\n height: var(--cnt-usz, 20px);\n display: flex;\n align-items: center;\n justify-content: center;\n color: rgba(240, 237, 216, 0.45);\n cursor: pointer;\n border-radius: 2px;\n transition: color 150ms, background-color 150ms;\n}\n.tp-flatland-pin:hover { color: #d94c87; background: transparent; }\n.tp-flatland-pin svg { display: block; }\n.tp-flatland-pin svg circle { fill: none; stroke: currentColor; stroke-width: 1.6; }\n.tp-flatland-pinned .tp-flatland-pin svg circle { fill: currentColor; }\n\n/*\n * Three-mode cycle toggle in the header (replaces Tweakpane's built-in\n * fold caret). We hide the native caret via the .tp-rotv_m marker\n * class and render our own button that cycles full - minimal -\n * collapsed - full. The data-mode attribute drives icon styling.\n */\n.tp-rotv_m { display: none !important; }\n.tp-flatland-mode {\n position: absolute;\n right: var(--cnt-hp, 4px);\n top: 0;\n bottom: 0;\n margin: auto;\n min-width: var(--cnt-usz, 20px);\n height: var(--cnt-usz, 20px);\n padding: 0 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n /* Match the original tweakpane fold caret and folder-header glyphs —\n * the muted foreground token pane headers use throughout. */\n color: var(--tp-label-foreground-color);\n cursor: pointer;\n font-size: 11px;\n line-height: 1;\n user-select: none;\n}\n/*\n * Glyph exactly replicates tweakpane's native fold marker — a 6×6\n * square with a horizontal linear-gradient that draws two vertical\n * stripes (screw-head look). Rotating gives the three states:\n * full → 0° (vertical stripes, ||) — matches tweakpane's expanded\n * minimal → 45° (diagonal, like a flat-head screw)\n * collapsed → 90° (horizontal stripes, =) — matches tweakpane's collapsed\n */\n.tp-flatland-mode-glyph {\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 2px;\n background: linear-gradient(\n to left,\n var(--cnt-fg, currentColor),\n var(--cnt-fg, currentColor) 2px,\n transparent 2px,\n transparent 4px,\n var(--cnt-fg, currentColor) 4px\n );\n opacity: 0.5;\n /* 0.2s ease-in-out — the exact transition tweakpane applies to its\n * own fold marker. */\n transition: transform 0.2s ease-in-out;\n}\n.tp-flatland-mode[data-mode=\"full\"] .tp-flatland-mode-glyph { transform: rotate(0deg); }\n.tp-flatland-mode[data-mode=\"minimal\"] .tp-flatland-mode-glyph { transform: rotate(45deg); }\n.tp-flatland-mode[data-mode=\"collapsed\"] .tp-flatland-mode-glyph { transform: rotate(90deg); }\n\n/*\n * Minimal mode: keep only blades tagged with .tp-flatland-minimal-keep\n * (stats graph + stats row). Non-keep blades animate max-height to 0\n * and fade out so the whole pane visibly shrinks, matching tweakpane\n * native fold feel. Values and bindings stay intact — DOM is just\n * clipped.\n *\n * max-height uses a generous ceiling instead of a measured value so we\n * can stay declarative; the transition duration matches the native\n * pane fold for continuity.\n */\n/*\n * Animation timings + easings mirror tweakpane's own fold exactly:\n * .tp-rotv_c / .tp-fldv_c use\n * height .2s ease-in-out, opacity .2s linear, padding .2s ease-in-out\n * and on expand, opacity has a 0.2s delay so height finishes first\n * then opacity fades in. The collapse direction has no delay — both\n * animate together.\n *\n * We use max-height in place of height because content height isn't\n * known at author time; with a generous ceiling the visual is\n * indistinguishable from native height tweens.\n */\n.tp-rotv_c > *:not(.tp-flatland-minimal-keep) {\n max-height: 1200px;\n opacity: 1;\n overflow: hidden;\n transition:\n max-height 0.2s ease-in-out,\n opacity 0.2s linear 0.2s,\n margin 0.2s ease-in-out,\n padding 0.2s ease-in-out;\n}\n.tp-flatland-minimal .tp-rotv_c > *:not(.tp-flatland-minimal-keep) {\n max-height: 0;\n opacity: 0;\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n pointer-events: none;\n /* On collapse: no delay on opacity — both animate together,\n * matching tweakpane's non-expanded transition shape. */\n transition:\n max-height 0.2s ease-in-out,\n opacity 0.2s linear,\n margin 0.2s ease-in-out,\n padding 0.2s ease-in-out;\n}\n`\n\nexport function applyTheme(\n element: HTMLElement,\n theme: Record<string, string> = FLATLAND_THEME,\n): void {\n for (const [key, value] of Object.entries(theme)) {\n element.style.setProperty(key, value)\n }\n\n // Inject accent styles once\n if (!element.querySelector('style[data-flatland]')) {\n const style = document.createElement('style')\n style.setAttribute('data-flatland', '')\n style.textContent = ACCENT_CSS\n element.appendChild(style)\n }\n}\n"],"mappings":"AAMO,MAAM,iBAAyC;AAAA;AAAA,EAEpD,8BAA8B;AAAA;AAAA,EAC9B,mCAAmC;AAAA;AAAA,EACnC,+BAA+B;AAAA;AAAA,EAC/B,iCAAiC;AAAA;AAAA;AAAA,EAGjC,mCAAmC;AAAA;AAAA,EACnC,gCAAgC;AAAA;AAAA,EAChC,+BAA+B;AAAA;AAAA,EAC/B,+BAA+B;AAAA;AAAA,EAC/B,iCAAiC;AAAA;AAAA;AAAA,EAGjC,gCAAgC;AAAA;AAAA;AAAA,EAGhC,gCAAgC;AAAA;AAAA;AAAA,EAGhC,yBAAyB;AAC3B;AAGA,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8LZ,SAAS,WACd,SACA,QAAgC,gBAC1B;AACN,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,YAAQ,MAAM,YAAY,KAAK,KAAK;AAAA,EACtC;AAGA,MAAI,CAAC,QAAQ,cAAc,sBAAsB,GAAG;AAClD,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,aAAa,iBAAiB,EAAE;AACtC,UAAM,cAAc;AACpB,YAAQ,YAAY,KAAK;AAAA,EAC3B;AACF;","names":[]}
@@ -0,0 +1,113 @@
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 vite_plugin_exports = {};
20
+ __export(vite_plugin_exports, {
21
+ default: () => vite_plugin_default,
22
+ threeFlatlandDevtools: () => threeFlatlandDevtools
23
+ });
24
+ module.exports = __toCommonJS(vite_plugin_exports);
25
+ var import_node_fs = require("node:fs");
26
+ var import_promises = require("node:fs/promises");
27
+ var import_node_url = require("node:url");
28
+ var import_node_path = require("node:path");
29
+ const import_meta = {};
30
+ const DEFAULT_PATH = "/.devtools";
31
+ function resolveDashboardRoot() {
32
+ const here = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
33
+ const siblingSrc = (0, import_node_path.resolve)(here, "../src/dashboard");
34
+ if ((0, import_node_fs.existsSync)(siblingSrc)) return siblingSrc;
35
+ return (0, import_node_path.resolve)(here, "dashboard");
36
+ }
37
+ function threeFlatlandDevtools(options = {}) {
38
+ const mountPath = options.path ?? DEFAULT_PATH;
39
+ const dashboardRoot = resolveDashboardRoot();
40
+ const vendorRoot = (0, import_node_path.resolve)(dashboardRoot, "vendor");
41
+ return {
42
+ name: "three-flatland-devtools",
43
+ apply: "serve",
44
+ config() {
45
+ return {
46
+ resolve: {
47
+ // Order matters — Vite/rollup-plugin-alias matches left-to-right
48
+ // by string prefix, so the broad `preact` entry must come last
49
+ // or it swallows `preact/jsx-*` and `preact/hooks`.
50
+ alias: [
51
+ {
52
+ find: "preact/jsx-runtime",
53
+ replacement: (0, import_node_path.resolve)(vendorRoot, "jsx-runtime.js")
54
+ },
55
+ {
56
+ // esbuild dev mode emits `import { jsxDEV } from 'preact/jsx-dev-runtime'`;
57
+ // the same shim exports `jsxDEV`.
58
+ find: "preact/jsx-dev-runtime",
59
+ replacement: (0, import_node_path.resolve)(vendorRoot, "jsx-runtime.js")
60
+ },
61
+ {
62
+ find: "preact/hooks",
63
+ replacement: (0, import_node_path.resolve)(vendorRoot, "hooks.module.js")
64
+ },
65
+ {
66
+ find: /^preact$/,
67
+ replacement: (0, import_node_path.resolve)(vendorRoot, "preact.module.js")
68
+ }
69
+ ]
70
+ }
71
+ };
72
+ },
73
+ configResolved(config) {
74
+ if (!config.server.fs.allow.includes(dashboardRoot)) {
75
+ config.server.fs.allow.push(dashboardRoot);
76
+ }
77
+ },
78
+ configureServer(server) {
79
+ server.middlewares.use((req, res, next) => {
80
+ void (async () => {
81
+ if (req.url === void 0) return next();
82
+ const url = req.url.split("?")[0] ?? "";
83
+ if (url !== mountPath && url !== `${mountPath}/`) return next();
84
+ try {
85
+ const indexPath = (0, import_node_path.resolve)(dashboardRoot, "index.html");
86
+ let html = await (0, import_promises.readFile)(indexPath, "utf-8");
87
+ const entryAbs = (0, import_node_path.resolve)(dashboardRoot, "index.tsx");
88
+ html = html.replace("./index.tsx", `/@fs${entryAbs}`);
89
+ html = await server.transformIndexHtml(req.url, html, req.originalUrl);
90
+ res.statusCode = 200;
91
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
92
+ res.end(html);
93
+ } catch (err) {
94
+ next(err);
95
+ }
96
+ })();
97
+ });
98
+ const logger = server.config.logger;
99
+ const port = server.config.server.port ?? 5173;
100
+ logger.info(
101
+ `
102
+ \x1B[32m\u279C\x1B[0m three-flatland devtools: \x1B[36mhttp://localhost:${port}${mountPath}\x1B[0m
103
+ `
104
+ );
105
+ }
106
+ };
107
+ }
108
+ var vite_plugin_default = threeFlatlandDevtools;
109
+ // Annotate the CommonJS export names for ESM import in node:
110
+ 0 && (module.exports = {
111
+ threeFlatlandDevtools
112
+ });
113
+ //# sourceMappingURL=vite-plugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["/**\n * Vite plugin that serves the standalone devtools dashboard alongside the\n * user's app. Mounts a dev-only middleware at a configurable path (default\n * `/.devtools`).\n *\n * The dashboard source lives inside this package\n * (`packages/devtools/src/dashboard/`), not inside the consumer's project.\n * The plugin:\n * 1. Whitelists the dashboard directory via `server.fs.allow` so Vite\n * is willing to read its files across the workspace boundary.\n * 2. Serves `index.html` at the mount path, rewritten so the entry\n * script loads via `/@fs/<abs>` — Vite then transforms it with full\n * HMR.\n * 3. Aliases the vendored Preact files so `import from 'preact'`\n * resolves locally — no runtime dep on Preact leaks into the\n * consumer's `node_modules`.\n *\n * Access the dashboard at the Vite dev server's origin directly, e.g.\n * `http://localhost:5174/.devtools`. If the host project sits behind a\n * microfrontend proxy, point the browser at the Vite port — the\n * dashboard's transitive requests (`/@fs/`, `/@vite/client`, pre-bundled\n * `node_modules/.vite/`) are not namespaced and expect to land on the\n * same origin that served the HTML.\n *\n * Dev-only; the plugin is inert in production builds.\n */\nimport { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nexport interface ThreeFlatlandDevtoolsOptions {\n /** URL path the dashboard is served under. Default: `/.devtools`. */\n path?: string\n}\n\nconst DEFAULT_PATH = '/.devtools'\n\nfunction resolveDashboardRoot(): string {\n // Three possible layouts for this file at runtime:\n // 1. `packages/devtools/src/vite-plugin.ts` (source condition)\n // 2. `packages/devtools/dist/vite-plugin.js` (workspace, Node import condition)\n // 3. `node_modules/@three-flatland/devtools/dist/vite-plugin.js` (published)\n //\n // For (2) — the dev workflow where Node loads the built plugin but we\n // want HMR over the unbuilt sources — prefer the sibling `../src/dashboard/`\n // if it exists. Otherwise resolve `./dashboard` relative to this file.\n const here = dirname(fileURLToPath(import.meta.url))\n const siblingSrc = resolve(here, '../src/dashboard')\n if (existsSync(siblingSrc)) return siblingSrc\n return resolve(here, 'dashboard')\n}\n\nexport function threeFlatlandDevtools(\n options: ThreeFlatlandDevtoolsOptions = {},\n): Plugin {\n const mountPath = options.path ?? DEFAULT_PATH\n const dashboardRoot = resolveDashboardRoot()\n const vendorRoot = resolve(dashboardRoot, 'vendor')\n\n return {\n name: 'three-flatland-devtools',\n apply: 'serve',\n\n config() {\n return {\n resolve: {\n // Order matters — Vite/rollup-plugin-alias matches left-to-right\n // by string prefix, so the broad `preact` entry must come last\n // or it swallows `preact/jsx-*` and `preact/hooks`.\n alias: [\n {\n find: 'preact/jsx-runtime',\n replacement: resolve(vendorRoot, 'jsx-runtime.js'),\n },\n {\n // esbuild dev mode emits `import { jsxDEV } from 'preact/jsx-dev-runtime'`;\n // the same shim exports `jsxDEV`.\n find: 'preact/jsx-dev-runtime',\n replacement: resolve(vendorRoot, 'jsx-runtime.js'),\n },\n {\n find: 'preact/hooks',\n replacement: resolve(vendorRoot, 'hooks.module.js'),\n },\n {\n find: /^preact$/,\n replacement: resolve(vendorRoot, 'preact.module.js'),\n },\n ],\n },\n }\n },\n\n configResolved(config) {\n // Append, don't replace. Returning an array from `config` would\n // overwrite Vite's default allow-list (project + workspace root),\n // locking the host app out of its own files.\n if (!config.server.fs.allow.includes(dashboardRoot)) {\n config.server.fs.allow.push(dashboardRoot)\n }\n },\n\n configureServer(server: ViteDevServer) {\n server.middlewares.use((req, res, next) => {\n void (async () => {\n if (req.url === undefined) return next()\n const url = req.url.split('?')[0] ?? ''\n if (url !== mountPath && url !== `${mountPath}/`) return next()\n\n try {\n const indexPath = resolve(dashboardRoot, 'index.html')\n let html = await readFile(indexPath, 'utf-8')\n const entryAbs = resolve(dashboardRoot, 'index.tsx')\n html = html.replace('./index.tsx', `/@fs${entryAbs}`)\n html = await server.transformIndexHtml(req.url, html, req.originalUrl)\n res.statusCode = 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(html)\n } catch (err) {\n next(err)\n }\n })()\n })\n\n const logger = server.config.logger\n const port = server.config.server.port ?? 5173\n logger.info(\n `\\n \\u001b[32m\\u279c\\u001b[0m three-flatland devtools: \\u001b[36mhttp://localhost:${port}${mountPath}\\u001b[0m\\n`,\n )\n },\n }\n}\n\nexport default threeFlatlandDevtools\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BA,qBAA2B;AAC3B,sBAAyB;AACzB,sBAA8B;AAC9B,uBAAiC;AA7BjC;AAsCA,MAAM,eAAe;AAErB,SAAS,uBAA+B;AAStC,QAAM,WAAO,8BAAQ,+BAAc,YAAY,GAAG,CAAC;AACnD,QAAM,iBAAa,0BAAQ,MAAM,kBAAkB;AACnD,UAAI,2BAAW,UAAU,EAAG,QAAO;AACnC,aAAO,0BAAQ,MAAM,WAAW;AAClC;AAEO,SAAS,sBACd,UAAwC,CAAC,GACjC;AACR,QAAM,YAAY,QAAQ,QAAQ;AAClC,QAAM,gBAAgB,qBAAqB;AAC3C,QAAM,iBAAa,0BAAQ,eAAe,QAAQ;AAElD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,SAAS;AACP,aAAO;AAAA,QACL,SAAS;AAAA;AAAA;AAAA;AAAA,UAIP,OAAO;AAAA,YACL;AAAA,cACE,MAAM;AAAA,cACN,iBAAa,0BAAQ,YAAY,gBAAgB;AAAA,YACnD;AAAA,YACA;AAAA;AAAA;AAAA,cAGE,MAAM;AAAA,cACN,iBAAa,0BAAQ,YAAY,gBAAgB;AAAA,YACnD;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,iBAAa,0BAAQ,YAAY,iBAAiB;AAAA,YACpD;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,iBAAa,0BAAQ,YAAY,kBAAkB;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,eAAe,QAAQ;AAIrB,UAAI,CAAC,OAAO,OAAO,GAAG,MAAM,SAAS,aAAa,GAAG;AACnD,eAAO,OAAO,GAAG,MAAM,KAAK,aAAa;AAAA,MAC3C;AAAA,IACF;AAAA,IAEA,gBAAgB,QAAuB;AACrC,aAAO,YAAY,IAAI,CAAC,KAAK,KAAK,SAAS;AACzC,cAAM,YAAY;AAChB,cAAI,IAAI,QAAQ,OAAW,QAAO,KAAK;AACvC,gBAAM,MAAM,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,KAAK;AACrC,cAAI,QAAQ,aAAa,QAAQ,GAAG,SAAS,IAAK,QAAO,KAAK;AAE9D,cAAI;AACF,kBAAM,gBAAY,0BAAQ,eAAe,YAAY;AACrD,gBAAI,OAAO,UAAM,0BAAS,WAAW,OAAO;AAC5C,kBAAM,eAAW,0BAAQ,eAAe,WAAW;AACnD,mBAAO,KAAK,QAAQ,eAAe,OAAO,QAAQ,EAAE;AACpD,mBAAO,MAAM,OAAO,mBAAmB,IAAI,KAAK,MAAM,IAAI,WAAW;AACrE,gBAAI,aAAa;AACjB,gBAAI,UAAU,gBAAgB,0BAA0B;AACxD,gBAAI,IAAI,IAAI;AAAA,UACd,SAAS,KAAK;AACZ,iBAAK,GAAG;AAAA,UACV;AAAA,QACF,GAAG;AAAA,MACL,CAAC;AAED,YAAM,SAAS,OAAO,OAAO;AAC7B,YAAM,OAAO,OAAO,OAAO,OAAO,QAAQ;AAC1C,aAAO;AAAA,QACL;AAAA,6EAAsF,IAAI,GAAG,SAAS;AAAA;AAAA,MACxG;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,sBAAQ;","names":[]}
@@ -0,0 +1,9 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ThreeFlatlandDevtoolsOptions {
4
+ /** URL path the dashboard is served under. Default: `/.devtools`. */
5
+ path?: string;
6
+ }
7
+ declare function threeFlatlandDevtools(options?: ThreeFlatlandDevtoolsOptions): Plugin;
8
+
9
+ export { type ThreeFlatlandDevtoolsOptions, threeFlatlandDevtools as default, threeFlatlandDevtools };
@@ -0,0 +1,9 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ThreeFlatlandDevtoolsOptions {
4
+ /** URL path the dashboard is served under. Default: `/.devtools`. */
5
+ path?: string;
6
+ }
7
+ declare function threeFlatlandDevtools(options?: ThreeFlatlandDevtoolsOptions): Plugin;
8
+
9
+ export { type ThreeFlatlandDevtoolsOptions, threeFlatlandDevtools as default, threeFlatlandDevtools };
@@ -0,0 +1,88 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, resolve } from "node:path";
5
+ const DEFAULT_PATH = "/.devtools";
6
+ function resolveDashboardRoot() {
7
+ const here = dirname(fileURLToPath(import.meta.url));
8
+ const siblingSrc = resolve(here, "../src/dashboard");
9
+ if (existsSync(siblingSrc)) return siblingSrc;
10
+ return resolve(here, "dashboard");
11
+ }
12
+ function threeFlatlandDevtools(options = {}) {
13
+ const mountPath = options.path ?? DEFAULT_PATH;
14
+ const dashboardRoot = resolveDashboardRoot();
15
+ const vendorRoot = resolve(dashboardRoot, "vendor");
16
+ return {
17
+ name: "three-flatland-devtools",
18
+ apply: "serve",
19
+ config() {
20
+ return {
21
+ resolve: {
22
+ // Order matters — Vite/rollup-plugin-alias matches left-to-right
23
+ // by string prefix, so the broad `preact` entry must come last
24
+ // or it swallows `preact/jsx-*` and `preact/hooks`.
25
+ alias: [
26
+ {
27
+ find: "preact/jsx-runtime",
28
+ replacement: resolve(vendorRoot, "jsx-runtime.js")
29
+ },
30
+ {
31
+ // esbuild dev mode emits `import { jsxDEV } from 'preact/jsx-dev-runtime'`;
32
+ // the same shim exports `jsxDEV`.
33
+ find: "preact/jsx-dev-runtime",
34
+ replacement: resolve(vendorRoot, "jsx-runtime.js")
35
+ },
36
+ {
37
+ find: "preact/hooks",
38
+ replacement: resolve(vendorRoot, "hooks.module.js")
39
+ },
40
+ {
41
+ find: /^preact$/,
42
+ replacement: resolve(vendorRoot, "preact.module.js")
43
+ }
44
+ ]
45
+ }
46
+ };
47
+ },
48
+ configResolved(config) {
49
+ if (!config.server.fs.allow.includes(dashboardRoot)) {
50
+ config.server.fs.allow.push(dashboardRoot);
51
+ }
52
+ },
53
+ configureServer(server) {
54
+ server.middlewares.use((req, res, next) => {
55
+ void (async () => {
56
+ if (req.url === void 0) return next();
57
+ const url = req.url.split("?")[0] ?? "";
58
+ if (url !== mountPath && url !== `${mountPath}/`) return next();
59
+ try {
60
+ const indexPath = resolve(dashboardRoot, "index.html");
61
+ let html = await readFile(indexPath, "utf-8");
62
+ const entryAbs = resolve(dashboardRoot, "index.tsx");
63
+ html = html.replace("./index.tsx", `/@fs${entryAbs}`);
64
+ html = await server.transformIndexHtml(req.url, html, req.originalUrl);
65
+ res.statusCode = 200;
66
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
67
+ res.end(html);
68
+ } catch (err) {
69
+ next(err);
70
+ }
71
+ })();
72
+ });
73
+ const logger = server.config.logger;
74
+ const port = server.config.server.port ?? 5173;
75
+ logger.info(
76
+ `
77
+ \x1B[32m\u279C\x1B[0m three-flatland devtools: \x1B[36mhttp://localhost:${port}${mountPath}\x1B[0m
78
+ `
79
+ );
80
+ }
81
+ };
82
+ }
83
+ var vite_plugin_default = threeFlatlandDevtools;
84
+ export {
85
+ vite_plugin_default as default,
86
+ threeFlatlandDevtools
87
+ };
88
+ //# sourceMappingURL=vite-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["/**\n * Vite plugin that serves the standalone devtools dashboard alongside the\n * user's app. Mounts a dev-only middleware at a configurable path (default\n * `/.devtools`).\n *\n * The dashboard source lives inside this package\n * (`packages/devtools/src/dashboard/`), not inside the consumer's project.\n * The plugin:\n * 1. Whitelists the dashboard directory via `server.fs.allow` so Vite\n * is willing to read its files across the workspace boundary.\n * 2. Serves `index.html` at the mount path, rewritten so the entry\n * script loads via `/@fs/<abs>` — Vite then transforms it with full\n * HMR.\n * 3. Aliases the vendored Preact files so `import from 'preact'`\n * resolves locally — no runtime dep on Preact leaks into the\n * consumer's `node_modules`.\n *\n * Access the dashboard at the Vite dev server's origin directly, e.g.\n * `http://localhost:5174/.devtools`. If the host project sits behind a\n * microfrontend proxy, point the browser at the Vite port — the\n * dashboard's transitive requests (`/@fs/`, `/@vite/client`, pre-bundled\n * `node_modules/.vite/`) are not namespaced and expect to land on the\n * same origin that served the HTML.\n *\n * Dev-only; the plugin is inert in production builds.\n */\nimport { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nexport interface ThreeFlatlandDevtoolsOptions {\n /** URL path the dashboard is served under. Default: `/.devtools`. */\n path?: string\n}\n\nconst DEFAULT_PATH = '/.devtools'\n\nfunction resolveDashboardRoot(): string {\n // Three possible layouts for this file at runtime:\n // 1. `packages/devtools/src/vite-plugin.ts` (source condition)\n // 2. `packages/devtools/dist/vite-plugin.js` (workspace, Node import condition)\n // 3. `node_modules/@three-flatland/devtools/dist/vite-plugin.js` (published)\n //\n // For (2) — the dev workflow where Node loads the built plugin but we\n // want HMR over the unbuilt sources — prefer the sibling `../src/dashboard/`\n // if it exists. Otherwise resolve `./dashboard` relative to this file.\n const here = dirname(fileURLToPath(import.meta.url))\n const siblingSrc = resolve(here, '../src/dashboard')\n if (existsSync(siblingSrc)) return siblingSrc\n return resolve(here, 'dashboard')\n}\n\nexport function threeFlatlandDevtools(\n options: ThreeFlatlandDevtoolsOptions = {},\n): Plugin {\n const mountPath = options.path ?? DEFAULT_PATH\n const dashboardRoot = resolveDashboardRoot()\n const vendorRoot = resolve(dashboardRoot, 'vendor')\n\n return {\n name: 'three-flatland-devtools',\n apply: 'serve',\n\n config() {\n return {\n resolve: {\n // Order matters — Vite/rollup-plugin-alias matches left-to-right\n // by string prefix, so the broad `preact` entry must come last\n // or it swallows `preact/jsx-*` and `preact/hooks`.\n alias: [\n {\n find: 'preact/jsx-runtime',\n replacement: resolve(vendorRoot, 'jsx-runtime.js'),\n },\n {\n // esbuild dev mode emits `import { jsxDEV } from 'preact/jsx-dev-runtime'`;\n // the same shim exports `jsxDEV`.\n find: 'preact/jsx-dev-runtime',\n replacement: resolve(vendorRoot, 'jsx-runtime.js'),\n },\n {\n find: 'preact/hooks',\n replacement: resolve(vendorRoot, 'hooks.module.js'),\n },\n {\n find: /^preact$/,\n replacement: resolve(vendorRoot, 'preact.module.js'),\n },\n ],\n },\n }\n },\n\n configResolved(config) {\n // Append, don't replace. Returning an array from `config` would\n // overwrite Vite's default allow-list (project + workspace root),\n // locking the host app out of its own files.\n if (!config.server.fs.allow.includes(dashboardRoot)) {\n config.server.fs.allow.push(dashboardRoot)\n }\n },\n\n configureServer(server: ViteDevServer) {\n server.middlewares.use((req, res, next) => {\n void (async () => {\n if (req.url === undefined) return next()\n const url = req.url.split('?')[0] ?? ''\n if (url !== mountPath && url !== `${mountPath}/`) return next()\n\n try {\n const indexPath = resolve(dashboardRoot, 'index.html')\n let html = await readFile(indexPath, 'utf-8')\n const entryAbs = resolve(dashboardRoot, 'index.tsx')\n html = html.replace('./index.tsx', `/@fs${entryAbs}`)\n html = await server.transformIndexHtml(req.url, html, req.originalUrl)\n res.statusCode = 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(html)\n } catch (err) {\n next(err)\n }\n })()\n })\n\n const logger = server.config.logger\n const port = server.config.server.port ?? 5173\n logger.info(\n `\\n \\u001b[32m\\u279c\\u001b[0m three-flatland devtools: \\u001b[36mhttp://localhost:${port}${mountPath}\\u001b[0m\\n`,\n )\n },\n }\n}\n\nexport default threeFlatlandDevtools\n"],"mappings":"AA0BA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,SAAS,eAAe;AASjC,MAAM,eAAe;AAErB,SAAS,uBAA+B;AAStC,QAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,QAAM,aAAa,QAAQ,MAAM,kBAAkB;AACnD,MAAI,WAAW,UAAU,EAAG,QAAO;AACnC,SAAO,QAAQ,MAAM,WAAW;AAClC;AAEO,SAAS,sBACd,UAAwC,CAAC,GACjC;AACR,QAAM,YAAY,QAAQ,QAAQ;AAClC,QAAM,gBAAgB,qBAAqB;AAC3C,QAAM,aAAa,QAAQ,eAAe,QAAQ;AAElD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,SAAS;AACP,aAAO;AAAA,QACL,SAAS;AAAA;AAAA;AAAA;AAAA,UAIP,OAAO;AAAA,YACL;AAAA,cACE,MAAM;AAAA,cACN,aAAa,QAAQ,YAAY,gBAAgB;AAAA,YACnD;AAAA,YACA;AAAA;AAAA;AAAA,cAGE,MAAM;AAAA,cACN,aAAa,QAAQ,YAAY,gBAAgB;AAAA,YACnD;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,aAAa,QAAQ,YAAY,iBAAiB;AAAA,YACpD;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,aAAa,QAAQ,YAAY,kBAAkB;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,eAAe,QAAQ;AAIrB,UAAI,CAAC,OAAO,OAAO,GAAG,MAAM,SAAS,aAAa,GAAG;AACnD,eAAO,OAAO,GAAG,MAAM,KAAK,aAAa;AAAA,MAC3C;AAAA,IACF;AAAA,IAEA,gBAAgB,QAAuB;AACrC,aAAO,YAAY,IAAI,CAAC,KAAK,KAAK,SAAS;AACzC,cAAM,YAAY;AAChB,cAAI,IAAI,QAAQ,OAAW,QAAO,KAAK;AACvC,gBAAM,MAAM,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,KAAK;AACrC,cAAI,QAAQ,aAAa,QAAQ,GAAG,SAAS,IAAK,QAAO,KAAK;AAE9D,cAAI;AACF,kBAAM,YAAY,QAAQ,eAAe,YAAY;AACrD,gBAAI,OAAO,MAAM,SAAS,WAAW,OAAO;AAC5C,kBAAM,WAAW,QAAQ,eAAe,WAAW;AACnD,mBAAO,KAAK,QAAQ,eAAe,OAAO,QAAQ,EAAE;AACpD,mBAAO,MAAM,OAAO,mBAAmB,IAAI,KAAK,MAAM,IAAI,WAAW;AACrE,gBAAI,aAAa;AACjB,gBAAI,UAAU,gBAAgB,0BAA0B;AACxD,gBAAI,IAAI,IAAI;AAAA,UACd,SAAS,KAAK;AACZ,iBAAK,GAAG;AAAA,UACV;AAAA,QACF,GAAG;AAAA,MACL,CAAC;AAED,YAAM,SAAS,OAAO,OAAO;AAC7B,YAAM,OAAO,OAAO,OAAO,OAAO,QAAQ;AAC1C,aAAO;AAAA,QACL;AAAA,6EAAsF,IAAI,GAAG,SAAS;AAAA;AAAA,MACxG;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,sBAAQ;","names":[]}
package/package.json ADDED
@@ -0,0 +1,124 @@
1
+ {
2
+ "name": "@three-flatland/devtools",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "Dev-time introspection toolkit for three-flatland — Tweakpane v4 UI plugin, stats monitor, and buffer inspection",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "source": "./src/index.ts",
9
+ "import": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "require": {
14
+ "types": "./dist/index.d.cts",
15
+ "default": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "./react": {
19
+ "source": "./src/react.ts",
20
+ "import": {
21
+ "types": "./dist/react.d.ts",
22
+ "default": "./dist/react.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/react.d.cts",
26
+ "default": "./dist/react.cjs"
27
+ }
28
+ },
29
+ "./react/*": {
30
+ "source": "./src/react/*.ts",
31
+ "import": {
32
+ "types": "./dist/react/*.d.ts",
33
+ "default": "./dist/react/*.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/react/*.d.cts",
37
+ "default": "./dist/react/*.cjs"
38
+ }
39
+ },
40
+ "./vite": {
41
+ "source": "./src/vite-plugin.ts",
42
+ "import": {
43
+ "types": "./dist/vite-plugin.d.ts",
44
+ "default": "./dist/vite-plugin.js"
45
+ },
46
+ "require": {
47
+ "types": "./dist/vite-plugin.d.cts",
48
+ "default": "./dist/vite-plugin.cjs"
49
+ }
50
+ }
51
+ },
52
+ "main": "./dist/index.cjs",
53
+ "module": "./dist/index.js",
54
+ "types": "./dist/index.d.ts",
55
+ "files": [
56
+ "dist"
57
+ ],
58
+ "sideEffects": false,
59
+ "peerDependencies": {
60
+ "@react-three/fiber": "^10.0.0-alpha.2",
61
+ "@tweakpane/plugin-essentials": "^0.2.1",
62
+ "react": "^19.2.0",
63
+ "three": "^0.183.0",
64
+ "three-flatland": "^0.1.0-alpha.4",
65
+ "tweakpane": "^4.0.5",
66
+ "vite": "^5.0.0 || ^6.0.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "react": {
70
+ "optional": true
71
+ },
72
+ "@react-three/fiber": {
73
+ "optional": true
74
+ },
75
+ "three": {
76
+ "optional": true
77
+ },
78
+ "three-flatland": {
79
+ "optional": true
80
+ },
81
+ "vite": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "devDependencies": {
86
+ "@react-three/fiber": "10.0.0-alpha.2",
87
+ "@testing-library/react": "^16.1.0",
88
+ "@tweakpane/core": "^2.0.5",
89
+ "@tweakpane/plugin-essentials": "^0.2.1",
90
+ "@types/react": "^19.0.2",
91
+ "@types/react-dom": "^19.0.2",
92
+ "@types/three": "^0.183.1",
93
+ "happy-dom": "^15.11.7",
94
+ "react": "^19.2.0",
95
+ "react-dom": "^19.2.0",
96
+ "three": "^0.183.1",
97
+ "tweakpane": "^4.0.5",
98
+ "typescript": "^5.7.3",
99
+ "vite": "^6.4.1",
100
+ "three-flatland": "0.1.0-alpha.6"
101
+ },
102
+ "keywords": [
103
+ "three.js",
104
+ "tweakpane",
105
+ "debug",
106
+ "controls",
107
+ "webgpu"
108
+ ],
109
+ "license": "MIT",
110
+ "repository": {
111
+ "type": "git",
112
+ "url": "https://github.com/thejustinwalsh/three-flatland.git",
113
+ "directory": "packages/devtools"
114
+ },
115
+ "scripts": {
116
+ "build": "tsup",
117
+ "build:bundle": "vite build --config vite.config.bundle.ts",
118
+ "dev": "tsup --watch",
119
+ "test": "vitest run",
120
+ "test:watch": "vitest",
121
+ "typecheck": "tsc --noEmit && tsc --noEmit -p src/dashboard/tsconfig.json",
122
+ "clean": "rm -rf dist bundle"
123
+ }
124
+ }