@plastic-software/three 0.183.4 → 0.184.1

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 (277) hide show
  1. package/build/three.cjs +775 -287
  2. package/build/three.core.js +372 -110
  3. package/build/three.core.min.js +1 -1
  4. package/build/three.module.js +428 -181
  5. package/build/three.module.min.js +1 -1
  6. package/build/three.tsl.js +7 -1
  7. package/build/three.tsl.min.js +1 -1
  8. package/build/three.webgpu.js +2979 -1281
  9. package/build/three.webgpu.min.js +1 -1
  10. package/build/three.webgpu.nodes.js +2942 -1281
  11. package/build/three.webgpu.nodes.min.js +1 -1
  12. package/examples/jsm/Addons.js +11 -0
  13. package/examples/jsm/animation/CCDIKSolver.js +5 -1
  14. package/examples/jsm/controls/ArcballControls.js +4 -1
  15. package/examples/jsm/controls/DragControls.js +2 -2
  16. package/examples/jsm/controls/FirstPersonControls.js +58 -54
  17. package/examples/jsm/controls/FlyControls.js +4 -0
  18. package/examples/jsm/controls/OrbitControls.js +2 -2
  19. package/examples/jsm/controls/TrackballControls.js +2 -2
  20. package/examples/jsm/controls/TransformControls.js +34 -2
  21. package/examples/jsm/csm/CSMShadowNode.js +6 -2
  22. package/examples/jsm/exporters/GLTFExporter.js +21 -5
  23. package/examples/jsm/geometries/TextGeometry.js +18 -0
  24. package/examples/jsm/helpers/LightProbeGridHelper.js +221 -0
  25. package/examples/jsm/inspector/Extension.js +13 -0
  26. package/examples/jsm/inspector/Inspector.js +169 -114
  27. package/examples/jsm/inspector/RendererInspector.js +2 -2
  28. package/examples/jsm/inspector/extensions/extensions.json +6 -0
  29. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +916 -0
  30. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +281 -0
  31. package/examples/jsm/inspector/tabs/Memory.js +128 -0
  32. package/examples/jsm/inspector/tabs/Parameters.js +34 -2
  33. package/examples/jsm/inspector/tabs/Performance.js +2 -2
  34. package/examples/jsm/inspector/tabs/Settings.js +264 -0
  35. package/examples/jsm/inspector/tabs/Timeline.js +1611 -0
  36. package/examples/jsm/inspector/tabs/Viewer.js +105 -3
  37. package/examples/jsm/inspector/ui/Graph.js +2 -2
  38. package/examples/jsm/inspector/ui/List.js +1 -1
  39. package/examples/jsm/inspector/ui/Profiler.js +273 -176
  40. package/examples/jsm/inspector/ui/Style.js +64 -10
  41. package/examples/jsm/inspector/ui/Tab.js +39 -7
  42. package/examples/jsm/inspector/ui/Values.js +39 -2
  43. package/examples/jsm/inspector/ui/utils.js +13 -0
  44. package/examples/jsm/interaction/InteractionManager.js +226 -0
  45. package/examples/jsm/libs/meshopt_decoder.module.js +8 -8
  46. package/examples/jsm/lighting/DynamicLighting.js +82 -0
  47. package/examples/jsm/lighting/LightProbeGrid.js +651 -0
  48. package/examples/jsm/lines/LineMaterial.js +1 -1
  49. package/examples/jsm/loaders/EXRLoader.js +682 -43
  50. package/examples/jsm/loaders/FBXLoader.js +233 -33
  51. package/examples/jsm/loaders/GLTFLoader.js +24 -7
  52. package/examples/jsm/loaders/HDRLoader.js +1 -1
  53. package/examples/jsm/loaders/KTX2Loader.js +8 -2
  54. package/examples/jsm/loaders/LDrawLoader.js +39 -47
  55. package/examples/jsm/loaders/SVGLoader.js +1 -1
  56. package/examples/jsm/loaders/VTKLoader.js +5 -1
  57. package/examples/jsm/loaders/collada/ColladaComposer.js +101 -7
  58. package/examples/jsm/loaders/collada/ColladaParser.js +19 -4
  59. package/examples/jsm/loaders/usd/USDAParser.js +6 -0
  60. package/examples/jsm/loaders/usd/USDCParser.js +26 -0
  61. package/examples/jsm/loaders/usd/USDComposer.js +656 -103
  62. package/examples/jsm/misc/GPUComputationRenderer.js +2 -0
  63. package/examples/jsm/misc/RollerCoaster.js +42 -4
  64. package/examples/jsm/modifiers/TessellateModifier.js +1 -1
  65. package/examples/jsm/objects/Reflector.js +73 -25
  66. package/examples/jsm/objects/Sky.js +14 -2
  67. package/examples/jsm/objects/SkyMesh.js +23 -6
  68. package/examples/jsm/renderers/Projector.js +18 -38
  69. package/examples/jsm/renderers/SVGRenderer.js +6 -25
  70. package/examples/jsm/transpiler/GLSLDecoder.js +2 -2
  71. package/examples/jsm/tsl/WebGLNodesHandler.js +605 -0
  72. package/examples/jsm/tsl/display/AfterImageNode.js +10 -0
  73. package/examples/jsm/tsl/display/AnamorphicNode.js +11 -0
  74. package/examples/jsm/tsl/display/BilateralBlurNode.js +10 -0
  75. package/examples/jsm/tsl/display/ChromaticAberrationNode.js +3 -36
  76. package/examples/jsm/tsl/display/FSR1Node.js +477 -0
  77. package/examples/jsm/tsl/display/GTAONode.js +2 -1
  78. package/examples/jsm/tsl/display/GaussianBlurNode.js +10 -0
  79. package/examples/jsm/tsl/display/GodraysNode.js +2 -11
  80. package/examples/jsm/tsl/display/OutlineNode.js +66 -16
  81. package/examples/jsm/tsl/display/SSGINode.js +0 -4
  82. package/examples/jsm/tsl/display/SharpenNode.js +283 -0
  83. package/examples/jsm/tsl/display/TAAUNode.js +835 -0
  84. package/examples/jsm/tsl/display/TRAANode.js +48 -7
  85. package/examples/jsm/tsl/lighting/DynamicLightsNode.js +300 -0
  86. package/examples/jsm/tsl/lighting/data/AmbientLightDataNode.js +61 -0
  87. package/examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js +111 -0
  88. package/examples/jsm/tsl/lighting/data/HemisphereLightDataNode.js +99 -0
  89. package/examples/jsm/tsl/lighting/data/PointLightDataNode.js +134 -0
  90. package/examples/jsm/tsl/lighting/data/SpotLightDataNode.js +161 -0
  91. package/examples/jsm/tsl/math/Bayer.js +13 -2
  92. package/examples/jsm/utils/BufferGeometryUtils.js +2 -3
  93. package/examples/jsm/utils/ColorUtils.js +76 -0
  94. package/examples/jsm/utils/SkeletonUtils.js +14 -8
  95. package/examples/jsm/webxr/XRHandMeshModel.js +36 -10
  96. package/examples/jsm/webxr/XRHandModelFactory.js +2 -1
  97. package/package.json +4 -4
  98. package/src/Three.Core.js +1 -0
  99. package/src/Three.TSL.js +6 -0
  100. package/src/Three.WebGPU.Nodes.js +3 -0
  101. package/src/Three.WebGPU.js +6 -0
  102. package/src/animation/AnimationAction.js +11 -1
  103. package/src/audio/AudioContext.js +2 -2
  104. package/src/constants.js +1 -1
  105. package/src/core/BufferAttribute.js +13 -1
  106. package/src/core/Clock.js +1 -1
  107. package/src/core/Object3D.js +1 -5
  108. package/src/core/RenderTarget.js +1 -0
  109. package/src/extras/curves/CatmullRomCurve3.js +3 -2
  110. package/src/loaders/AudioLoader.js +11 -1
  111. package/src/loaders/DataTextureLoader.js +6 -4
  112. package/src/loaders/FileLoader.js +1 -2
  113. package/src/loaders/ImageBitmapLoader.js +4 -6
  114. package/src/loaders/MaterialLoader.js +1 -1
  115. package/src/loaders/ObjectLoader.js +25 -4
  116. package/src/loaders/nodes/NodeObjectLoader.js +18 -0
  117. package/src/materials/MeshToonMaterial.js +1 -1
  118. package/src/materials/nodes/Line2NodeMaterial.js +27 -0
  119. package/src/materials/nodes/NodeMaterial.js +0 -27
  120. package/src/materials/nodes/manager/NodeMaterialObserver.js +188 -89
  121. package/src/math/Line3.js +3 -0
  122. package/src/math/Matrix2.js +13 -9
  123. package/src/math/Matrix3.js +13 -9
  124. package/src/math/Matrix4.js +13 -9
  125. package/src/math/Plane.js +4 -3
  126. package/src/math/Triangle.js +1 -1
  127. package/src/math/Vector2.js +11 -7
  128. package/src/math/Vector3.js +12 -8
  129. package/src/math/Vector4.js +13 -9
  130. package/src/nodes/Nodes.js +0 -1
  131. package/src/nodes/TSL.js +1 -1
  132. package/src/nodes/accessors/BufferAttributeNode.js +9 -3
  133. package/src/nodes/accessors/CubeTextureNode.js +7 -1
  134. package/src/nodes/accessors/MaterialProperties.js +2 -5
  135. package/src/nodes/accessors/Object3DNode.js +1 -1
  136. package/src/nodes/accessors/ReferenceBaseNode.js +2 -2
  137. package/src/nodes/accessors/ReferenceNode.js +4 -4
  138. package/src/nodes/accessors/SceneProperties.js +2 -8
  139. package/src/nodes/accessors/StorageBufferNode.js +10 -4
  140. package/src/nodes/accessors/StorageTextureNode.js +4 -9
  141. package/src/nodes/accessors/TextureNode.js +10 -2
  142. package/src/nodes/accessors/UniformArrayNode.js +2 -2
  143. package/src/nodes/code/FunctionCallNode.js +1 -1
  144. package/src/nodes/code/FunctionNode.js +1 -1
  145. package/src/nodes/core/ArrayNode.js +1 -1
  146. package/src/nodes/core/AssignNode.js +1 -1
  147. package/src/nodes/core/AttributeNode.js +1 -1
  148. package/src/nodes/core/BypassNode.js +1 -1
  149. package/src/nodes/core/ContextNode.js +1 -1
  150. package/src/nodes/core/IndexNode.js +2 -1
  151. package/src/nodes/core/InputNode.js +1 -1
  152. package/src/nodes/core/InspectorNode.js +1 -1
  153. package/src/nodes/core/IsolateNode.js +1 -1
  154. package/src/nodes/core/Node.js +83 -12
  155. package/src/nodes/core/NodeBuilder.js +117 -16
  156. package/src/nodes/core/NodeUtils.js +1 -1
  157. package/src/nodes/core/OutputStructNode.js +1 -1
  158. package/src/nodes/core/ParameterNode.js +1 -1
  159. package/src/nodes/core/StackNode.js +1 -1
  160. package/src/nodes/core/StructNode.js +1 -1
  161. package/src/nodes/core/StructTypeNode.js +1 -1
  162. package/src/nodes/core/SubBuildNode.js +1 -1
  163. package/src/nodes/core/UniformGroupNode.js +36 -6
  164. package/src/nodes/core/VarNode.js +1 -1
  165. package/src/nodes/core/VaryingNode.js +1 -1
  166. package/src/nodes/display/NormalMapNode.js +2 -2
  167. package/src/nodes/display/PassNode.js +27 -7
  168. package/src/nodes/display/RenderOutputNode.js +4 -4
  169. package/src/nodes/display/ScreenNode.js +1 -1
  170. package/src/nodes/display/ViewportDepthTextureNode.js +11 -15
  171. package/src/nodes/display/ViewportTextureNode.js +18 -7
  172. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +2 -2
  173. package/src/nodes/geometry/RangeNode.js +1 -1
  174. package/src/nodes/gpgpu/AtomicFunctionNode.js +1 -1
  175. package/src/nodes/gpgpu/BarrierNode.js +9 -0
  176. package/src/nodes/gpgpu/ComputeBuiltinNode.js +1 -1
  177. package/src/nodes/gpgpu/ComputeNode.js +69 -44
  178. package/src/nodes/gpgpu/SubgroupFunctionNode.js +1 -1
  179. package/src/nodes/lighting/LightsNode.js +6 -27
  180. package/src/nodes/lighting/ShadowNode.js +24 -2
  181. package/src/nodes/math/BitcastNode.js +1 -1
  182. package/src/nodes/math/ConditionalNode.js +1 -1
  183. package/src/nodes/math/MathNode.js +73 -1
  184. package/src/nodes/math/OperatorNode.js +1 -1
  185. package/src/nodes/math/PackFloatNode.js +1 -1
  186. package/src/nodes/math/UnpackFloatNode.js +1 -1
  187. package/src/nodes/tsl/TSLBase.js +1 -1
  188. package/src/nodes/tsl/TSLCore.js +21 -3
  189. package/src/nodes/utils/ArrayElementNode.js +1 -1
  190. package/src/nodes/utils/ConvertNode.js +1 -1
  191. package/src/nodes/utils/DebugNode.js +1 -1
  192. package/src/nodes/utils/EventNode.js +30 -0
  193. package/src/nodes/utils/FlipNode.js +1 -1
  194. package/src/nodes/utils/FunctionOverloadingNode.js +1 -1
  195. package/src/nodes/utils/JoinNode.js +1 -1
  196. package/src/nodes/utils/MemberNode.js +1 -1
  197. package/src/nodes/utils/Remap.js +48 -0
  198. package/src/nodes/utils/RotateNode.js +1 -1
  199. package/src/nodes/utils/SetNode.js +1 -1
  200. package/src/nodes/utils/SplitNode.js +1 -1
  201. package/src/objects/BatchedMesh.js +17 -2
  202. package/src/objects/InstancedMesh.js +19 -3
  203. package/src/objects/SkinnedMesh.js +26 -9
  204. package/src/renderers/WebGLRenderer.js +147 -48
  205. package/src/renderers/common/Animation.js +3 -3
  206. package/src/renderers/common/Attributes.js +15 -1
  207. package/src/renderers/common/Backend.js +0 -8
  208. package/src/renderers/common/Background.js +2 -2
  209. package/src/renderers/common/BindGroup.js +1 -8
  210. package/src/renderers/common/Bindings.js +2 -2
  211. package/src/renderers/common/ComputePipeline.js +1 -1
  212. package/src/renderers/common/CubeRenderTarget.js +1 -1
  213. package/src/renderers/common/Info.js +333 -4
  214. package/src/renderers/common/InspectorBase.js +6 -1
  215. package/src/renderers/common/Pipelines.js +36 -3
  216. package/src/renderers/common/ReadbackBuffer.js +78 -0
  217. package/src/renderers/common/RenderBundle.js +3 -1
  218. package/src/renderers/common/RenderBundles.js +5 -2
  219. package/src/renderers/common/RenderObject.js +2 -2
  220. package/src/renderers/common/RenderObjects.js +3 -3
  221. package/src/renderers/common/RenderPipeline.js +35 -6
  222. package/src/renderers/common/Renderer.js +232 -53
  223. package/src/renderers/common/Textures.js +72 -3
  224. package/src/renderers/common/UniformsGroup.js +1 -1
  225. package/src/renderers/common/XRManager.js +34 -27
  226. package/src/renderers/common/extras/PMREMGenerator.js +23 -15
  227. package/src/renderers/common/nodes/NodeBuilderState.js +1 -1
  228. package/src/renderers/common/nodes/NodeManager.js +230 -99
  229. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +0 -1
  230. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +1 -1
  231. package/src/renderers/shaders/ShaderChunk/lightprobes_pars_fragment.glsl.js +80 -0
  232. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +8 -0
  233. package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +2 -0
  234. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +1 -3
  235. package/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js +7 -0
  236. package/src/renderers/shaders/ShaderChunk/premultiplied_alpha_fragment.glsl.js +0 -1
  237. package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +12 -2
  238. package/src/renderers/shaders/ShaderChunk.js +2 -0
  239. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +1 -2
  240. package/src/renderers/shaders/ShaderLib.js +0 -1
  241. package/src/renderers/shaders/UniformsLib.js +7 -2
  242. package/src/renderers/shaders/UniformsUtils.js +27 -5
  243. package/src/renderers/webgl/WebGLAnimation.js +2 -1
  244. package/src/renderers/webgl/WebGLBackground.js +13 -13
  245. package/src/renderers/webgl/WebGLBufferRenderer.js +0 -32
  246. package/src/renderers/webgl/WebGLCapabilities.js +6 -0
  247. package/src/renderers/webgl/WebGLIndexedBufferRenderer.js +0 -32
  248. package/src/renderers/webgl/WebGLMaterials.js +12 -13
  249. package/src/renderers/webgl/WebGLOutput.js +4 -1
  250. package/src/renderers/webgl/WebGLProgram.js +4 -0
  251. package/src/renderers/webgl/WebGLPrograms.js +21 -4
  252. package/src/renderers/webgl/WebGLRenderStates.js +13 -2
  253. package/src/renderers/webgl/WebGLState.js +43 -0
  254. package/src/renderers/webgl/WebGLTextures.js +129 -26
  255. package/src/renderers/webgl/WebGLUniformsGroups.js +19 -0
  256. package/src/renderers/webgl-fallback/WebGLBackend.js +106 -65
  257. package/src/renderers/webgl-fallback/WebGLBufferRenderer.js +0 -41
  258. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +29 -51
  259. package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +53 -19
  260. package/src/renderers/webgl-fallback/utils/WebGLCapabilities.js +25 -0
  261. package/src/renderers/webgl-fallback/utils/WebGLState.js +42 -1
  262. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +63 -50
  263. package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +1 -1
  264. package/src/renderers/webgpu/WebGPUBackend.js +160 -146
  265. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +55 -33
  266. package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +103 -17
  267. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +1 -1
  268. package/src/renderers/webgpu/utils/WebGPUCapabilities.js +48 -0
  269. package/src/renderers/webgpu/utils/WebGPUConstants.js +8 -0
  270. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +91 -17
  271. package/src/renderers/webgpu/utils/WebGPUUtils.js +18 -2
  272. package/src/renderers/webxr/WebXRController.js +12 -0
  273. package/src/textures/HTMLTexture.js +74 -0
  274. package/src/textures/Source.js +1 -1
  275. package/src/textures/Texture.js +13 -2
  276. package/src/utils.js +23 -1
  277. package/src/nodes/utils/RemapNode.js +0 -125
@@ -1,9 +1,14 @@
1
+ import { EventDispatcher } from 'three';
1
2
  import { Style } from './Style.js';
3
+ import { getItem, setItem } from '../Inspector.js';
2
4
 
3
- export class Profiler {
5
+ export class Profiler extends EventDispatcher {
4
6
 
5
- constructor() {
7
+ constructor( inspector ) {
6
8
 
9
+ super();
10
+
11
+ this.inspector = inspector;
7
12
  this.tabs = {};
8
13
  this.activeTabId = null;
9
14
  this.isResizing = false;
@@ -11,7 +16,6 @@ export class Profiler {
11
16
  this.lastWidthRight = 450; // Width for right position
12
17
  this.position = 'bottom'; // 'bottom' or 'right'
13
18
  this.detachedWindows = []; // Array to store detached tab windows
14
- this.isMobile = this.detectMobile();
15
19
  this.maxZIndex = 1002; // Track the highest z-index for detached windows (starts at base z-index from CSS)
16
20
  this.nextTabOriginalIndex = 0; // Track the original order of tabs as they are added
17
21
 
@@ -20,15 +24,43 @@ export class Profiler {
20
24
  this.setupShell();
21
25
  this.setupResizing();
22
26
 
27
+ // Setup window resize listener and update mobile status
28
+ this.setupWindowResizeListener();
29
+
23
30
  // Setup orientation change listener for mobile devices
24
- if ( this.isMobile ) {
31
+ this.setupOrientationListener();
32
+
33
+ }
34
+
35
+ getSize() {
36
+
37
+ if ( this.panel.classList.contains( 'visible' ) === false || this.panel.classList.contains( 'no-tabs' ) ) {
25
38
 
26
- this.setupOrientationListener();
39
+ return { width: 0, height: 0 };
27
40
 
28
41
  }
29
42
 
30
- // Setup window resize listener to constrain detached windows
31
- this.setupWindowResizeListener();
43
+ if ( this.position === 'right' ) {
44
+
45
+ return { width: this.panel.offsetWidth, height: 0 };
46
+
47
+ } else {
48
+
49
+ return { width: 0, height: this.panel.offsetHeight };
50
+
51
+ }
52
+
53
+ }
54
+
55
+ get isMobile() {
56
+
57
+ return this.detectMobile();
58
+
59
+ }
60
+
61
+ get isSmallScreen() {
62
+
63
+ return window.innerWidth <= 768;
32
64
 
33
65
  }
34
66
 
@@ -38,9 +70,8 @@ export class Profiler {
38
70
  const userAgent = navigator.userAgent || navigator.vendor || window.opera;
39
71
  const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test( userAgent );
40
72
  const isTouchDevice = ( 'ontouchstart' in window ) || ( navigator.maxTouchPoints > 0 );
41
- const isSmallScreen = window.innerWidth <= 768;
42
73
 
43
- return isMobileUA || ( isTouchDevice && isSmallScreen );
74
+ return isMobileUA || ( isTouchDevice && this.isSmallScreen );
44
75
 
45
76
  }
46
77
 
@@ -48,6 +79,8 @@ export class Profiler {
48
79
 
49
80
  const handleOrientationChange = () => {
50
81
 
82
+ if ( ! this.isMobile ) return;
83
+
51
84
  // Check if device is in landscape or portrait mode
52
85
  const isLandscape = window.innerWidth > window.innerHeight;
53
86
 
@@ -123,6 +156,28 @@ export class Profiler {
123
156
  // Listen for window resize events
124
157
  window.addEventListener( 'resize', () => {
125
158
 
159
+ if ( this.isSmallScreen ) {
160
+
161
+ this.floatingBtn.style.display = 'none';
162
+ this.panel.classList.add( 'hide-position-toggle' );
163
+
164
+ } else {
165
+
166
+ this.floatingBtn.style.display = '';
167
+ this.panel.classList.remove( 'hide-position-toggle' );
168
+
169
+ }
170
+
171
+ if ( this.isMobile ) {
172
+
173
+ this.panel.classList.add( 'is-mobile' );
174
+
175
+ } else {
176
+
177
+ this.panel.classList.remove( 'is-mobile' );
178
+
179
+ }
180
+
126
181
  constrainDetachedWindows();
127
182
  constrainMainPanel();
128
183
 
@@ -210,6 +265,19 @@ export class Profiler {
210
265
 
211
266
  const header = document.createElement( 'div' );
212
267
  header.className = 'profiler-header';
268
+
269
+ // Enable horizontal scrolling with vertical mouse wheel
270
+ header.addEventListener( 'wheel', ( e ) => {
271
+
272
+ if ( e.deltaY !== 0 ) {
273
+
274
+ e.preventDefault();
275
+ header.scrollLeft += e.deltaY * .25;
276
+
277
+ }
278
+
279
+ }, { passive: false } );
280
+
213
281
  this.tabsContainer = document.createElement( 'div' );
214
282
  this.tabsContainer.className = 'profiler-tabs';
215
283
 
@@ -222,14 +290,20 @@ export class Profiler {
222
290
  this.floatingBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="15" y1="3" x2="15" y2="21"></line></svg>';
223
291
  this.floatingBtn.onclick = () => this.togglePosition();
224
292
 
225
- // Hide position toggle button on mobile devices
226
- if ( this.isMobile ) {
293
+ // Hide position toggle button on small screens
294
+ if ( this.isSmallScreen ) {
227
295
 
228
296
  this.floatingBtn.style.display = 'none';
229
297
  this.panel.classList.add( 'hide-position-toggle' );
230
298
 
231
299
  }
232
300
 
301
+ if ( this.isMobile ) {
302
+
303
+ this.panel.classList.add( 'is-mobile' );
304
+
305
+ }
306
+
233
307
  this.maximizeBtn = document.createElement( 'button' );
234
308
  this.maximizeBtn.id = 'maximize-btn';
235
309
  this.maximizeBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>';
@@ -256,6 +330,13 @@ export class Profiler {
256
330
  // Set initial position class
257
331
  this.panel.classList.add( `position-${this.position}` );
258
332
 
333
+ if ( this.position === 'right' ) {
334
+
335
+ this.toggleButton.classList.add( 'position-right' );
336
+ this.miniPanel.classList.add( 'position-right' );
337
+
338
+ }
339
+
259
340
  }
260
341
 
261
342
  setupResizing() {
@@ -303,6 +384,8 @@ export class Profiler {
303
384
 
304
385
  }
305
386
 
387
+ this.dispatchEvent( { type: 'resize' } );
388
+
306
389
  };
307
390
 
308
391
  const onEnd = () => {
@@ -395,6 +478,47 @@ export class Profiler {
395
478
 
396
479
  }
397
480
 
481
+ this.dispatchEvent( { type: 'resize' } );
482
+
483
+ }
484
+
485
+ hide() {
486
+
487
+ this.miniPanel.classList.remove( 'visible' );
488
+
489
+ this.miniPanel.querySelectorAll( '.mini-panel-content' ).forEach( content => {
490
+
491
+ content.style.display = 'none';
492
+
493
+ } );
494
+
495
+ this.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ).forEach( btn => {
496
+
497
+ btn.classList.remove( 'active' );
498
+
499
+ } );
500
+
501
+ }
502
+
503
+ show( tab ) {
504
+
505
+ this.hide();
506
+
507
+ tab.builtinButton.classList.add( 'active' );
508
+
509
+ if ( ! tab.miniContent.firstChild ) {
510
+
511
+ while ( tab.content.firstChild ) {
512
+
513
+ tab.miniContent.appendChild( tab.content.firstChild );
514
+
515
+ }
516
+
517
+ }
518
+
519
+ tab.miniContent.style.display = 'block';
520
+ this.miniPanel.classList.add( 'visible' );
521
+
398
522
  }
399
523
 
400
524
  addTab( tab ) {
@@ -416,7 +540,12 @@ export class Profiler {
416
540
 
417
541
  this.setupTabDragAndDrop( tab );
418
542
 
419
- this.tabsContainer.appendChild( tab.button );
543
+ if ( ! tab.builtin ) {
544
+
545
+ this.tabsContainer.appendChild( tab.button );
546
+
547
+ }
548
+
420
549
  this.contentWrapper.appendChild( tab.content );
421
550
 
422
551
  // Apply the current visibility state to the DOM elements
@@ -437,6 +566,9 @@ export class Profiler {
437
566
  // Update panel size when tabs change
438
567
  this.updatePanelSize();
439
568
 
569
+ // Set profiler reference
570
+ tab.profiler = this;
571
+
440
572
  }
441
573
 
442
574
  addBuiltinTab( tab ) {
@@ -473,107 +605,94 @@ export class Profiler {
473
605
 
474
606
  e.stopPropagation(); // Prevent toggle panel from triggering
475
607
 
476
- const isPanelVisible = this.panel.classList.contains( 'visible' );
477
-
478
- if ( isPanelVisible ) {
608
+ // Toggle mini-panel for this tab
609
+ const isCurrentlyActive = miniContent.style.display !== 'none' && miniContent.children.length > 0;
479
610
 
480
- // Panel is visible - navigate to tab
481
- if ( ! tab.isVisible ) {
611
+ if ( isCurrentlyActive ) {
482
612
 
483
- tab.show();
613
+ this.hide();
484
614
 
485
- }
615
+ } else {
486
616
 
487
- if ( tab.isDetached ) {
617
+ this.show( tab );
488
618
 
489
- // If tab is detached, just bring its window to front
490
- if ( tab.detachedWindow ) {
619
+ }
491
620
 
492
- this.bringWindowToFront( tab.detachedWindow.panel );
621
+ };
493
622
 
494
- }
623
+ this.builtinTabsContainer.appendChild( builtinButton );
495
624
 
496
- } else {
625
+ // Store references
626
+ tab.builtinButton = builtinButton;
627
+ tab.miniContent = miniContent;
497
628
 
498
- // Activate the tab
499
- this.setActiveTab( tab.id );
629
+ // If the tab was hidden before being added, hide the builtin button
630
+ if ( ! tab.isVisible ) {
500
631
 
501
- }
632
+ builtinButton.style.display = 'none';
633
+ miniContent.style.display = 'none';
502
634
 
503
- } else {
635
+ // Hide the builtin-tabs-container if all builtin buttons are hidden
636
+ const hasVisibleBuiltinButtons = Array.from( this.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ) )
637
+ .some( btn => btn.style.display !== 'none' );
504
638
 
505
- // Panel is hidden - toggle mini-panel for this tab
506
- const isCurrentlyActive = miniContent.style.display !== 'none' && miniContent.children.length > 0;
639
+ if ( ! hasVisibleBuiltinButtons ) {
507
640
 
508
- // Hide all other mini-panel contents
509
- this.miniPanel.querySelectorAll( '.mini-panel-content' ).forEach( content => {
641
+ this.builtinTabsContainer.style.display = 'none';
510
642
 
511
- content.style.display = 'none';
643
+ }
512
644
 
513
- } );
645
+ }
514
646
 
515
- // Remove active state from all builtin buttons
516
- this.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ).forEach( btn => {
647
+ }
517
648
 
518
- btn.classList.remove( 'active' );
649
+ removeTab( tab ) {
519
650
 
520
- } );
651
+ if ( ! tab || this.tabs[ tab.id ] === undefined ) return;
521
652
 
522
- if ( isCurrentlyActive ) {
653
+ delete this.tabs[ tab.id ];
523
654
 
524
- // Toggle off - hide mini-panel and move content back
525
- this.miniPanel.classList.remove( 'visible' );
526
- miniContent.style.display = 'none';
655
+ if ( tab.isDetached && tab.detachedWindow ) {
527
656
 
528
- // Move content back to main panel
529
- if ( miniContent.firstChild ) {
657
+ if ( tab.detachedWindow.panel && tab.detachedWindow.panel.parentNode ) {
530
658
 
531
- tab.content.appendChild( miniContent.firstChild );
659
+ tab.detachedWindow.panel.parentNode.removeChild( tab.detachedWindow.panel );
532
660
 
533
- }
661
+ }
534
662
 
535
- } else {
663
+ const index = this.detachedWindows.indexOf( tab.detachedWindow );
536
664
 
537
- // Toggle on - show mini-panel with this tab's content
538
- builtinButton.classList.add( 'active' );
665
+ if ( index !== - 1 ) {
539
666
 
540
- // Move actual content to mini-panel (not clone) if not already there
541
- if ( ! miniContent.firstChild ) {
667
+ this.detachedWindows.splice( index, 1 );
542
668
 
543
- const actualContent = tab.content.querySelector( '.list-scroll-wrapper' ) || tab.content.firstElementChild;
669
+ }
544
670
 
545
- if ( actualContent ) {
671
+ }
546
672
 
547
- miniContent.appendChild( actualContent );
673
+ if ( ! tab.builtin ) {
548
674
 
549
- }
675
+ if ( tab.button && tab.button.parentNode ) {
550
676
 
551
- }
677
+ tab.button.parentNode.removeChild( tab.button );
552
678
 
553
- // Show after content is moved
554
- miniContent.style.display = 'block';
555
- this.miniPanel.classList.add( 'visible' );
679
+ }
556
680
 
557
- }
681
+ } else {
558
682
 
559
- }
683
+ if ( tab.builtinButton && tab.builtinButton.parentNode ) {
560
684
 
561
- };
685
+ tab.builtinButton.parentNode.removeChild( tab.builtinButton );
562
686
 
563
- this.builtinTabsContainer.appendChild( builtinButton );
687
+ }
564
688
 
565
- // Store references
566
- tab.builtinButton = builtinButton;
567
- tab.miniContent = miniContent;
568
- tab.profiler = this;
689
+ if ( tab.miniContent && tab.miniContent.parentNode ) {
569
690
 
570
- // If the tab was hidden before being added, hide the builtin button
571
- if ( ! tab.isVisible ) {
691
+ tab.miniContent.parentNode.removeChild( tab.miniContent );
572
692
 
573
- builtinButton.style.display = 'none';
574
- miniContent.style.display = 'none';
693
+ }
575
694
 
576
- // Hide the builtin-tabs-container if all builtin buttons are hidden
695
+ // Clean up builtin container if empty
577
696
  const hasVisibleBuiltinButtons = Array.from( this.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ) )
578
697
  .some( btn => btn.style.display !== 'none' );
579
698
 
@@ -585,6 +704,38 @@ export class Profiler {
585
704
 
586
705
  }
587
706
 
707
+ if ( tab.content && tab.content.parentNode ) {
708
+
709
+ tab.content.parentNode.removeChild( tab.content );
710
+
711
+ }
712
+
713
+ if ( this.activeTabId === tab.id ) {
714
+
715
+ this.activeTabId = null;
716
+
717
+ // Try to activate another tab
718
+ const remainingTabs = Object.values( this.tabs ).filter( t => ! t.isDetached && t.isVisible );
719
+
720
+ if ( remainingTabs.length > 0 ) {
721
+
722
+ this.setActiveTab( remainingTabs[ 0 ].id );
723
+
724
+ } else {
725
+
726
+ this.updatePanelSize();
727
+
728
+ }
729
+
730
+ } else {
731
+
732
+ this.updatePanelSize();
733
+
734
+ }
735
+
736
+ tab.onVisibilityChange = null;
737
+ tab.profiler = null;
738
+
588
739
  }
589
740
 
590
741
  updatePanelSize() {
@@ -648,32 +799,26 @@ export class Profiler {
648
799
 
649
800
  }
650
801
 
802
+ this.dispatchEvent( { type: 'resize' } );
803
+
651
804
  }
652
805
 
653
806
  setupTabDragAndDrop( tab ) {
654
807
 
655
- // Disable drag and drop on mobile devices
656
- if ( this.isMobile ) {
808
+ // Always handle basic click
809
+ tab.button.addEventListener( 'click', () => {
657
810
 
658
- tab.button.addEventListener( 'click', () => {
811
+ if ( ! isDragging ) {
659
812
 
660
813
  this.setActiveTab( tab.id );
661
814
 
662
- } );
663
-
664
- return;
815
+ }
665
816
 
666
- }
817
+ } );
667
818
 
668
819
  // Disable drag and drop if tab doesn't allow detach
669
820
  if ( tab.allowDetach === false ) {
670
821
 
671
- tab.button.addEventListener( 'click', () => {
672
-
673
- this.setActiveTab( tab.id );
674
-
675
- } );
676
-
677
822
  tab.button.style.cursor = 'default';
678
823
 
679
824
  return;
@@ -778,6 +923,8 @@ export class Profiler {
778
923
 
779
924
  tab.button.addEventListener( 'pointerdown', ( e ) => {
780
925
 
926
+ if ( this.isMobile && e.pointerType !== 'mouse' ) return;
927
+
781
928
  onDragStart( e );
782
929
  tab.button.addEventListener( 'pointermove', onDragMove );
783
930
  tab.button.addEventListener( 'pointerup', onDragEnd );
@@ -1466,92 +1613,18 @@ export class Profiler {
1466
1613
 
1467
1614
  }
1468
1615
 
1616
+ this.saveLayout();
1617
+
1469
1618
  }
1470
1619
 
1471
1620
  togglePanel() {
1472
1621
 
1473
1622
  this.panel.classList.toggle( 'visible' );
1474
- this.toggleButton.classList.toggle( 'hidden' );
1623
+ this.toggleButton.classList.toggle( 'panel-open' );
1624
+ this.miniPanel.classList.toggle( 'panel-open' );
1475
1625
 
1476
1626
  const isVisible = this.panel.classList.contains( 'visible' );
1477
1627
 
1478
- if ( isVisible ) {
1479
-
1480
- // Save mini-panel state before hiding
1481
- this.savedMiniPanelState = {
1482
- isVisible: this.miniPanel.classList.contains( 'visible' ),
1483
- activeTabId: null,
1484
- contentMap: {}
1485
- };
1486
-
1487
- // Find which tab was active in mini-panel
1488
- this.miniPanel.querySelectorAll( '.mini-panel-content' ).forEach( content => {
1489
-
1490
- if ( content.style.display !== 'none' && content.firstChild ) {
1491
-
1492
- // Find the tab that owns this content
1493
- Object.values( this.tabs ).forEach( tab => {
1494
-
1495
- if ( tab.miniContent === content ) {
1496
-
1497
- this.savedMiniPanelState.activeTabId = tab.id;
1498
- // Move content back to main panel
1499
- tab.content.appendChild( content.firstChild );
1500
-
1501
- }
1502
-
1503
- } );
1504
-
1505
- }
1506
-
1507
- } );
1508
-
1509
- // Hide mini-panel temporarily
1510
- this.miniPanel.classList.remove( 'visible' );
1511
-
1512
- // Hide all mini-panel contents
1513
- this.miniPanel.querySelectorAll( '.mini-panel-content' ).forEach( content => {
1514
-
1515
- content.style.display = 'none';
1516
-
1517
- } );
1518
-
1519
- // Remove active state from builtin buttons
1520
- this.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ).forEach( btn => {
1521
-
1522
- btn.classList.remove( 'active' );
1523
-
1524
- } );
1525
-
1526
- } else {
1527
-
1528
- // Restore mini-panel state when minimizing
1529
- if ( this.savedMiniPanelState && this.savedMiniPanelState.isVisible && this.savedMiniPanelState.activeTabId ) {
1530
-
1531
- const tab = this.tabs[ this.savedMiniPanelState.activeTabId ];
1532
-
1533
- if ( tab && tab.miniContent && tab.builtinButton ) {
1534
-
1535
- // Restore mini-panel visibility
1536
- this.miniPanel.classList.add( 'visible' );
1537
- tab.miniContent.style.display = 'block';
1538
- tab.builtinButton.classList.add( 'active' );
1539
-
1540
- // Move content back to mini-panel
1541
- const actualContent = tab.content.querySelector( '.list-scroll-wrapper, .profiler-content > *' );
1542
-
1543
- if ( actualContent ) {
1544
-
1545
- tab.miniContent.appendChild( actualContent );
1546
-
1547
- }
1548
-
1549
- }
1550
-
1551
- }
1552
-
1553
- }
1554
-
1555
1628
  this.detachedWindows.forEach( detachedWindow => {
1556
1629
 
1557
1630
  if ( isVisible ) {
@@ -1570,6 +1643,10 @@ export class Profiler {
1570
1643
 
1571
1644
  } );
1572
1645
 
1646
+ this.dispatchEvent( { type: 'resize' } );
1647
+
1648
+ this.saveLayout();
1649
+
1573
1650
  }
1574
1651
 
1575
1652
  togglePosition() {
@@ -1598,6 +1675,8 @@ export class Profiler {
1598
1675
  // Apply right position styles
1599
1676
  this.panel.classList.remove( 'position-bottom' );
1600
1677
  this.panel.classList.add( 'position-right' );
1678
+ this.toggleButton.classList.add( 'position-right' );
1679
+ this.miniPanel.classList.add( 'position-right' );
1601
1680
  this.panel.style.bottom = '';
1602
1681
  this.panel.style.top = '0';
1603
1682
  this.panel.style.right = '0';
@@ -1626,6 +1705,8 @@ export class Profiler {
1626
1705
  // Apply bottom position styles
1627
1706
  this.panel.classList.remove( 'position-right' );
1628
1707
  this.panel.classList.add( 'position-bottom' );
1708
+ this.toggleButton.classList.remove( 'position-right' );
1709
+ this.miniPanel.classList.remove( 'position-right' );
1629
1710
  this.panel.style.top = '';
1630
1711
  this.panel.style.right = '';
1631
1712
  this.panel.style.bottom = '0';
@@ -1663,12 +1744,15 @@ export class Profiler {
1663
1744
 
1664
1745
  saveLayout() {
1665
1746
 
1747
+ if ( this.isLoadingLayout ) return;
1748
+
1666
1749
  const layout = {
1667
1750
  position: this.position,
1668
1751
  lastHeightBottom: this.lastHeightBottom,
1669
1752
  lastWidthRight: this.lastWidthRight,
1670
1753
  activeTabId: this.activeTabId,
1671
- detachedTabs: []
1754
+ detachedTabs: [],
1755
+ isVisible: this.panel.classList.contains( 'visible' )
1672
1756
  };
1673
1757
 
1674
1758
  // Save detached windows state
@@ -1696,7 +1780,7 @@ export class Profiler {
1696
1780
 
1697
1781
  try {
1698
1782
 
1699
- localStorage.setItem( 'profiler-layout', JSON.stringify( layout ) );
1783
+ setItem( 'layout', layout );
1700
1784
 
1701
1785
  } catch ( e ) {
1702
1786
 
@@ -1708,13 +1792,13 @@ export class Profiler {
1708
1792
 
1709
1793
  loadLayout() {
1710
1794
 
1711
- try {
1795
+ this.isLoadingLayout = true;
1712
1796
 
1713
- const savedLayout = localStorage.getItem( 'profiler-layout' );
1797
+ try {
1714
1798
 
1715
- if ( ! savedLayout ) return;
1799
+ const layout = getItem( 'layout' );
1716
1800
 
1717
- const layout = JSON.parse( savedLayout );
1801
+ if ( Object.keys( layout ).length === 0 ) return;
1718
1802
 
1719
1803
  // Constrain detached tabs positions to current screen bounds
1720
1804
  if ( layout.detachedTabs && layout.detachedTabs.length > 0 ) {
@@ -1827,6 +1911,8 @@ export class Profiler {
1827
1911
 
1828
1912
  this.panel.classList.remove( 'position-bottom' );
1829
1913
  this.panel.classList.add( 'position-right' );
1914
+ this.toggleButton.classList.add( 'position-right' );
1915
+ this.miniPanel.classList.add( 'position-right' );
1830
1916
  this.panel.style.bottom = '';
1831
1917
  this.panel.style.top = '0';
1832
1918
  this.panel.style.right = '0';
@@ -1840,16 +1926,16 @@ export class Profiler {
1840
1926
 
1841
1927
  }
1842
1928
 
1843
- if ( layout.activeTabId ) {
1929
+ if ( layout.isVisible ) {
1844
1930
 
1845
- const willBeDetached = layout.detachedTabs &&
1846
- layout.detachedTabs.some( dt => dt.tabId === layout.activeTabId );
1931
+ this.panel.classList.add( 'visible' );
1932
+ this.toggleButton.classList.add( 'panel-open' );
1847
1933
 
1848
- if ( willBeDetached ) {
1934
+ }
1849
1935
 
1850
- this.setActiveTab( layout.activeTabId );
1936
+ if ( layout.activeTabId ) {
1851
1937
 
1852
- }
1938
+ this.setActiveTab( layout.activeTabId );
1853
1939
 
1854
1940
  }
1855
1941
 
@@ -1863,10 +1949,21 @@ export class Profiler {
1863
1949
  // Update panel size after loading layout
1864
1950
  this.updatePanelSize();
1865
1951
 
1952
+ // Ensure initial open state applies to mini panel as well
1953
+ if ( this.panel.classList.contains( 'visible' ) ) {
1954
+
1955
+ this.miniPanel.classList.add( 'panel-open' );
1956
+
1957
+ }
1958
+
1866
1959
  } catch ( e ) {
1867
1960
 
1868
1961
  console.warn( 'Failed to load profiler layout:', e );
1869
1962
 
1963
+ } finally {
1964
+
1965
+ this.isLoadingLayout = false;
1966
+
1870
1967
  }
1871
1968
 
1872
1969
  }